A game framework written with osu! in mind.
1// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
2// See the LICENCE file in the repository root for full licence text.
3
4using System;
5using ManagedBass;
6
7namespace osu.Framework.Audio
8{
9 /// <summary>
10 /// A helper class for translating relative frequency values to absolute hertz values based on the initial channel frequency.
11 /// Also handles zero frequency value by requesting the component to pause the channel and maintain that until it's set back from zero.
12 /// </summary>
13 internal class BassRelativeFrequencyHandler
14 {
15 private int channel;
16 private float initialFrequency;
17
18 /// <summary>
19 /// Invoked when frequency changes from non-zero to zero via <see cref="SetFrequency"/>.
20 /// Allows the component using this instance to pause instead of changing frequency to zero
21 /// (which is not supported in BASS).
22 /// </summary>
23 public Action FrequencyChangedToZero;
24
25 /// <summary>
26 /// Invoked when frequency changes from zero to non-zero via <see cref="SetFrequency"/>.
27 /// Allows the component using this instance to revert any changes in its state
28 /// done in <see cref="FrequencyChangedToZero"/>.
29 /// </summary>
30 public Action FrequencyChangedFromZero;
31
32 /// <summary>
33 /// Whether the last <see cref="SetFrequency"/> call specified a zero relative frequency.
34 /// </summary>
35 public bool IsFrequencyZero { get; private set; }
36
37 /// <summary>
38 /// Sets the component's BASS channel handle.
39 /// </summary>
40 /// <param name="channel">The channel handle.</param>
41 public void SetChannel(int channel)
42 {
43 if (channel == 0)
44 throw new ArgumentException("Invalid channel handle specified.", nameof(channel));
45
46 this.channel = channel;
47 IsFrequencyZero = false;
48
49 Bass.ChannelGetAttribute(this.channel, ChannelAttribute.Frequency, out initialFrequency);
50 }
51
52 /// <summary>
53 /// Sets the channel's frequency based on the given <paramref name="relativeFrequency"/>.
54 /// </summary>
55 /// <remarks>
56 /// Callers should ensure to <see cref="SetChannel"/> first before attempting to change channel frequency.
57 /// </remarks>
58 /// <param name="relativeFrequency">The desired frequency value, relative to the channel's initial frequency.</param>
59 /// <example>
60 /// A <c>SetFrequency(0.5)</c> call is equivalent to the following ManagedBASS call:
61 /// <code>BASS.ChannelSetAttribute(ChannelAttribute.Frequency, channel, initialFrequency * 0.5);</code>
62 /// </example>
63 public void SetFrequency(double relativeFrequency)
64 {
65 if (channel == 0)
66 throw new InvalidOperationException("Attempted to set the channel frequency without calling SetChannel() first.");
67
68 // http://bass.radio42.com/help/html/ff7623f0-6e9f-6be8-c8a7-17d3a6dc6d51.htm (BASS_ATTRIB_FREQ's description)
69 // Above documentation shows the frequency limits which the constants (min_bass_freq, max_bass_freq) came from.
70 const int min_bass_freq = 100;
71 const int max_bass_freq = 100000;
72
73 int channelFrequency = (int)Math.Clamp(Math.Abs(initialFrequency * relativeFrequency), min_bass_freq, max_bass_freq);
74 Bass.ChannelSetAttribute(channel, ChannelAttribute.Frequency, channelFrequency);
75
76 // Maintain internal pause on zero frequency due to BASS not supporting them (0 is took for original rate in BASS API)
77 if (!IsFrequencyZero && relativeFrequency == 0)
78 {
79 FrequencyChangedToZero?.Invoke();
80 IsFrequencyZero = true;
81 }
82 else if (IsFrequencyZero && relativeFrequency > 0)
83 {
84 IsFrequencyZero = false;
85 FrequencyChangedFromZero?.Invoke();
86 }
87 }
88 }
89}