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