Skip to main content

firewheel_core/dsp/
volume.rs

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