firewheel_core/dsp/
volume.rs

1#[cfg(not(feature = "std"))]
2use num_traits::Float;
3
4pub const DEFAULT_AMP_EPSILON: f32 = 0.00001;
5pub const DEFAULT_DB_EPSILON: f32 = -100.0;
6
7/// A value representing a volume (gain) applied to an audio signal.
8#[derive(Debug, Clone, Copy, PartialEq)]
9#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
10pub enum Volume {
11    /// Volume in a linear scale, where `0.0` is silence and `1.0` is unity gain.
12    Linear(f32),
13    /// Volume in decibels, where `0.0` is unity gain and `f32::NEG_INFINITY` is silence.
14    Decibels(f32),
15}
16
17impl Volume {
18    /// Unity gain (the resulting volume is the same as the source signal)
19    pub const UNITY_GAIN: Self = Self::Linear(1.0);
20    /// Silence
21    pub const SILENT: Self = Self::Linear(0.0);
22
23    /// Get the volume in raw amplitude for use in DSP.
24    pub fn amp(&self) -> f32 {
25        match *self {
26            Self::Linear(volume) => linear_volume_to_amp_clamped(volume, 0.0),
27            Self::Decibels(db) => db_to_amp(db),
28        }
29    }
30
31    /// Get the volume in raw amplitude for use in DSP.
32    ///
33    /// If the resulting amplitude is `<= amp_epsilon`, then `0.0` (silence) will be returned.
34    pub fn amp_clamped(&self, amp_epsilon: f32) -> f32 {
35        match *self {
36            Self::Linear(volume) => linear_volume_to_amp_clamped(volume, amp_epsilon),
37            Self::Decibels(db) => {
38                if db == f32::NEG_INFINITY {
39                    0.0
40                } else {
41                    let amp = db_to_amp(db);
42                    if amp <= amp_epsilon {
43                        0.0
44                    } else {
45                        amp
46                    }
47                }
48            }
49        }
50    }
51
52    /// Get the volume in decibles.
53    pub fn decibels(&self) -> f32 {
54        match *self {
55            Self::Linear(volume) => {
56                if volume == 0.0 {
57                    f32::NEG_INFINITY
58                } else {
59                    let amp = linear_volume_to_amp_clamped(volume, 0.0);
60                    amp_to_db(amp)
61                }
62            }
63            Self::Decibels(db) => db,
64        }
65    }
66
67    /// Get the volume in decibles.
68    ///
69    /// If the resulting decibel value is `<= db_epsilon`, then `f32::NEG_INFINITY` (silence)
70    /// will be returned.
71    pub fn decibels_clamped(&self, db_epsilon: f32) -> f32 {
72        match *self {
73            Self::Linear(volume) => {
74                if volume == 0.0 {
75                    f32::NEG_INFINITY
76                } else {
77                    let amp = linear_volume_to_amp_clamped(volume, 0.0);
78                    let db = amp_to_db(amp);
79                    if db <= db_epsilon {
80                        f32::NEG_INFINITY
81                    } else {
82                        db
83                    }
84                }
85            }
86            Self::Decibels(db) => {
87                if db <= db_epsilon {
88                    f32::NEG_INFINITY
89                } else {
90                    db
91                }
92            }
93        }
94    }
95
96    /// Get the volume in linear units, where `0.0` is silence and `1.0` is unity gain.
97    pub fn linear(&self) -> f32 {
98        match *self {
99            Self::Linear(volume) => volume,
100            Self::Decibels(db) => amp_to_linear_volume_clamped(db_to_amp(db), 0.0),
101        }
102    }
103
104    /// Get the value as a [`Volume::Linear`] value.
105    pub fn as_linear_variant(&self) -> Self {
106        Self::Linear(self.linear())
107    }
108
109    /// Get the value as a [`Volume::Decibels`] value.
110    pub fn as_decibel_variant(&self) -> Self {
111        Self::Decibels(self.decibels())
112    }
113}
114
115impl Default for Volume {
116    fn default() -> Self {
117        Self::UNITY_GAIN
118    }
119}
120
121impl core::ops::Add<Self> for Volume {
122    type Output = Self;
123
124    fn add(self, rhs: Self) -> Self {
125        use Volume::{Decibels, Linear};
126
127        match (self, rhs) {
128            (Linear(a), Linear(b)) => Linear(a + b),
129            (Decibels(a), Decibels(b)) => Decibels(amp_to_db(db_to_amp(a) + db_to_amp(b))),
130            // {Linear, Decibels} favors the left hand side of the operation by
131            // first converting the right hand side to the same type as the left
132            // hand side and then performing the operation.
133            (Linear(..), Decibels(..)) => self + rhs.as_linear_variant(),
134            (Decibels(..), Linear(..)) => self + rhs.as_decibel_variant(),
135        }
136    }
137}
138
139impl core::ops::Sub<Self> for Volume {
140    type Output = Self;
141
142    fn sub(self, rhs: Self) -> Self {
143        use Volume::{Decibels, Linear};
144
145        match (self, rhs) {
146            (Linear(a), Linear(b)) => Linear(a - b),
147            (Decibels(a), Decibels(b)) => Decibels(amp_to_db(db_to_amp(a) - db_to_amp(b))),
148            // {Linear, Decibels} favors the left hand side of the operation by
149            // first converting the right hand side to the same type as the left
150            // hand side and then performing the operation.
151            (Linear(..), Decibels(..)) => self - rhs.as_linear_variant(),
152            (Decibels(..), Linear(..)) => self - rhs.as_decibel_variant(),
153        }
154    }
155}
156
157impl core::ops::Mul<Self> for Volume {
158    type Output = Self;
159
160    fn mul(self, rhs: Self) -> Self {
161        use Volume::{Decibels, Linear};
162
163        match (self, rhs) {
164            (Linear(a), Linear(b)) => Linear(a * b),
165            (Decibels(a), Decibels(b)) => Decibels(amp_to_db(db_to_amp(a) * db_to_amp(b))),
166            // {Linear, Decibels} favors the left hand side of the operation by
167            // first converting the right hand side to the same type as the left
168            // hand side and then performing the operation.
169            (Linear(..), Decibels(..)) => self * rhs.as_linear_variant(),
170            (Decibels(..), Linear(..)) => self * rhs.as_decibel_variant(),
171        }
172    }
173}
174
175impl core::ops::Div<Self> for Volume {
176    type Output = Self;
177
178    fn div(self, rhs: Self) -> Self {
179        use Volume::{Decibels, Linear};
180
181        match (self, rhs) {
182            (Linear(a), Linear(b)) => Linear(a / b),
183            (Decibels(a), Decibels(b)) => Decibels(amp_to_db(db_to_amp(a) / db_to_amp(b))),
184            // {Linear, Decibels} favors the left hand side of the operation by
185            // first converting the right hand side to the same type as the left
186            // hand side and then performing the operation.
187            (Linear(..), Decibels(..)) => self / rhs.as_linear_variant(),
188            (Decibels(..), Linear(..)) => self / rhs.as_decibel_variant(),
189        }
190    }
191}
192
193impl core::ops::AddAssign<Self> for Volume {
194    fn add_assign(&mut self, rhs: Self) {
195        *self = *self + rhs;
196    }
197}
198
199impl core::ops::SubAssign<Self> for Volume {
200    fn sub_assign(&mut self, rhs: Self) {
201        *self = *self - rhs;
202    }
203}
204
205impl core::ops::MulAssign<Self> for Volume {
206    fn mul_assign(&mut self, rhs: Self) {
207        *self = *self * rhs;
208    }
209}
210
211impl core::ops::DivAssign<Self> for Volume {
212    fn div_assign(&mut self, rhs: Self) {
213        *self = *self / rhs;
214    }
215}
216
217/// Returns the raw amplitude from the given decibel value.
218#[inline]
219pub fn db_to_amp(db: f32) -> f32 {
220    if db == f32::NEG_INFINITY {
221        0.0
222    } else {
223        10.0f32.powf(0.05 * db)
224    }
225}
226
227/// Returns the decibel value from the given raw amplitude.
228#[inline]
229pub fn amp_to_db(amp: f32) -> f32 {
230    if amp == 0.0 {
231        f32::NEG_INFINITY
232    } else {
233        20.0 * amp.log10()
234    }
235}
236
237/// Returns the raw amplitude from the given decibel value.
238///
239/// If `db == f32::NEG_INFINITY || db <= db_epsilon`, then `0.0` (silence) will be
240/// returned.
241#[inline]
242pub fn db_to_amp_clamped(db: f32, db_epsilon: f32) -> f32 {
243    if db == f32::NEG_INFINITY || db <= db_epsilon {
244        0.0
245    } else {
246        db_to_amp(db)
247    }
248}
249
250/// Returns the decibel value from the given raw amplitude.
251///
252/// If `amp <= amp_epsilon`, then `f32::NEG_INFINITY` (silence) will be returned.
253#[inline]
254pub fn amp_to_db_clamped(amp: f32, amp_epsilon: f32) -> f32 {
255    if amp <= amp_epsilon {
256        f32::NEG_INFINITY
257    } else {
258        amp_to_db(amp)
259    }
260}
261
262/// Map the linear volume (where `0.0` means mute and `1.0` means unity
263/// gain) to the corresponding raw amplitude value (not decibels) for use in
264/// DSP. Values above `1.0` are allowed.
265///
266/// If the resulting amplitude is `<= amp_epsilon`, then `0.0` (silence) will be
267/// returned.
268#[inline]
269pub fn linear_volume_to_amp_clamped(linear_volume: f32, amp_epsilon: f32) -> f32 {
270    let v = linear_volume * linear_volume;
271    if v <= amp_epsilon {
272        0.0
273    } else {
274        v
275    }
276}
277
278/// Map the raw amplitude (where `0.0` means mute and `1.0` means unity
279/// gain) to the corresponding linear volume.
280///
281/// If the amplitude is `<= amp_epsilon`, then `0.0` (silence) will be
282/// returned.
283#[inline]
284pub fn amp_to_linear_volume_clamped(amp: f32, amp_epsilon: f32) -> f32 {
285    if amp <= amp_epsilon {
286        0.0
287    } else {
288        amp.sqrt()
289    }
290}
291
292/// A struct that converts a value in decibels to a normalized range used in
293/// meters.
294#[derive(Debug, Clone, Copy, PartialEq)]
295pub struct DbMeterNormalizer {
296    min_db: f32,
297    range_recip: f32,
298    factor: f32,
299}
300
301impl DbMeterNormalizer {
302    /// * `min_db` - The minimum decibel value shown in the meter.
303    /// * `max_db` - The maximum decibel value shown in the meter.
304    /// * `center_db` - The decibel value that will appear halfway (0.5) in the
305    /// normalized range. For example, if you had `min_db` as `-100.0` and
306    /// `max_db` as `0.0`, then a good `center_db` value would be `-22`.
307    pub fn new(min_db: f32, max_db: f32, center_db: f32) -> Self {
308        assert!(max_db > min_db);
309        assert!(center_db > min_db && center_db < max_db);
310
311        let range_recip = (max_db - min_db).recip();
312        let center_normalized = ((center_db - min_db) * range_recip).clamp(0.0, 1.0);
313
314        Self {
315            min_db,
316            range_recip,
317            factor: 0.5_f32.log(center_normalized),
318        }
319    }
320
321    #[inline]
322    pub fn normalize(&self, db: f32) -> f32 {
323        ((db - self.min_db) * self.range_recip)
324            .clamp(0.0, 1.0)
325            .powf(self.factor)
326    }
327}
328
329impl Default for DbMeterNormalizer {
330    fn default() -> Self {
331        Self::new(-100.0, 0.0, -22.0)
332    }
333}