rawdio/utility/
level.rs

1pub const MINUS_INFINITY_DECIBELS: f64 = -128.0;
2
3/// A level that can be used to convert from linear to decibel representation
4#[derive(Debug, Clone, Copy, Default, PartialEq, PartialOrd)]
5pub struct Level {
6    linear: f64,
7}
8
9impl Level {
10    /// Unity gain (1.0 / 0 dB)
11    pub fn unity() -> Self {
12        Level::from_linear(1.0)
13    }
14
15    /// Zero gain (0.0 / -inf dB)
16    pub fn zero() -> Self {
17        Level::from_linear(0.0)
18    }
19
20    /// Create a level from dB
21    pub fn from_db(level_in_db: f64) -> Self {
22        if level_in_db <= MINUS_INFINITY_DECIBELS {
23            Self { linear: 0.0 }
24        } else {
25            Self {
26                linear: 10.0_f64.powf(level_in_db / 20.0),
27            }
28        }
29    }
30
31    /// Create a level from dB
32    pub fn from_db_f32(level_in_db: f32) -> Self {
33        Self::from_db(level_in_db as f64)
34    }
35
36    /// Convert to dB
37    pub fn as_db(&self) -> f64 {
38        if self.linear <= 1e-9 {
39            MINUS_INFINITY_DECIBELS
40        } else {
41            20.0 * self.linear.log10()
42        }
43    }
44
45    /// Create a level from linear gain
46    pub fn from_linear(linear_gain: f64) -> Self {
47        Self {
48            linear: linear_gain,
49        }
50    }
51
52    /// Convert to linear gain
53    pub fn as_linear(&self) -> f64 {
54        self.linear
55    }
56
57    /// Convert to linear gain
58    pub fn as_linear_f32(&self) -> f32 {
59        self.linear as f32
60    }
61
62    /// Clamp the value between two other levels
63    pub fn clamp(&self, min_value: &Self, max_value: &Self) -> Self {
64        Self {
65            linear: self.linear.clamp(min_value.linear, max_value.linear),
66        }
67    }
68
69    /// Check if the value represents zero gain
70    pub fn is_zero(&self) -> bool {
71        relative_eq!(self.linear, 0.0)
72    }
73
74    /// Check if the value represents unity gain
75    pub fn is_unity(&self) -> bool {
76        relative_eq!(self.linear, 1.0)
77    }
78}
79
80#[cfg(test)]
81mod tests {
82    use approx::assert_relative_eq;
83
84    use super::*;
85
86    #[test]
87    fn db_to_linear() {
88        let epsilon = 1e-2;
89        assert_relative_eq!(Level::from_db(0.0).as_linear(), 1.0, epsilon = epsilon);
90        assert_relative_eq!(Level::from_db(-6.0).as_linear(), 0.5, epsilon = epsilon);
91        assert_relative_eq!(Level::from_db(-12.0).as_linear(), 0.25, epsilon = epsilon);
92        assert_relative_eq!(Level::from_db(-200.0).as_linear(), 0.0, epsilon = epsilon);
93    }
94
95    #[test]
96    fn linear_to_db() {
97        let epsilon = 0.1;
98        assert_relative_eq!(Level::from_linear(1.0).as_db(), 0.0, epsilon = epsilon);
99        assert_relative_eq!(Level::from_linear(0.5).as_db(), -6.0, epsilon = epsilon);
100        assert_relative_eq!(Level::from_linear(0.25).as_db(), -12.0, epsilon = epsilon);
101        assert_relative_eq!(
102            Level::from_linear(0.0).as_db(),
103            MINUS_INFINITY_DECIBELS,
104            epsilon = epsilon
105        );
106    }
107}