Skip to main content

oxstack_audio/
level.rs

1//! Signal level measurement.
2
3/// Peak and RMS level of a block of samples, relative to full scale.
4#[derive(Clone, Copy, Debug, Default, PartialEq)]
5#[cfg_attr(feature = "defmt", derive(defmt::Format))]
6pub struct Level {
7    /// Largest absolute sample, `0.0..=1.0` of full scale.
8    pub peak: f32,
9    /// Root-mean-square, `0.0..=1.0` of full scale.
10    pub rms: f32,
11    /// Mean sample value (DC offset), `-1.0..=1.0` of full scale.
12    pub dc: f32,
13}
14
15/// Full scale for signed 16-bit PCM (|i16::MIN|).
16const FULL_SCALE: f32 = 32768.0;
17
18impl Level {
19    /// Measures every `stride`-th sample starting at `offset`. Use
20    /// `(0, 2)` for the left channel of interleaved stereo, `(0, 1)` for mono.
21    pub fn measure(samples: &[i16], offset: usize, stride: usize) -> Self {
22        let (mut peak, mut sum, mut sum_sq, mut n) = (0u32, 0i64, 0u64, 0u32);
23        for &s in samples.iter().skip(offset).step_by(stride.max(1)) {
24            peak = peak.max(s.unsigned_abs().into());
25            sum += i64::from(s);
26            sum_sq += (i64::from(s) * i64::from(s)) as u64;
27            n += 1;
28        }
29        if n == 0 {
30            return Self::default();
31        }
32        let n = n as f32;
33        Self {
34            peak: peak as f32 / FULL_SCALE,
35            rms: libm::sqrtf(sum_sq as f32 / n) / FULL_SCALE,
36            dc: sum as f32 / n / FULL_SCALE,
37        }
38    }
39
40    /// RMS in dBFS (0 dBFS = full scale); `-inf` for digital silence.
41    pub fn rms_dbfs(&self) -> f32 {
42        to_dbfs(self.rms)
43    }
44
45    /// Peak in dBFS.
46    pub fn peak_dbfs(&self) -> f32 {
47        to_dbfs(self.peak)
48    }
49}
50
51/// Converts a linear `0.0..=1.0` level to dBFS.
52pub fn to_dbfs(linear: f32) -> f32 {
53    20.0 * libm::log10f(linear)
54}
55
56#[cfg(test)]
57mod tests {
58    use super::*;
59
60    #[test]
61    fn silence_and_full_scale() {
62        let l = Level::measure(&[0; 64], 0, 1);
63        assert_eq!((l.peak, l.rms, l.dc), (0.0, 0.0, 0.0));
64        assert!(l.rms_dbfs().is_infinite(), "silence is -inf dBFS");
65
66        let l = Level::measure(&[i16::MIN; 8], 0, 1);
67        assert_eq!(l.peak, 1.0);
68        assert!(l.rms_dbfs().abs() < 1e-3, "full scale is 0 dBFS");
69    }
70
71    #[test]
72    fn stride_selects_one_channel() {
73        // Left = 16384 (-6 dBFS), right = 0.
74        let lr = [16384, 0, -16384, 0, 16384, 0, -16384, 0];
75        let left = Level::measure(&lr, 0, 2);
76        let right = Level::measure(&lr, 1, 2);
77        assert!((left.rms_dbfs() + 6.02).abs() < 0.01);
78        assert_eq!(left.dc, 0.0);
79        assert_eq!(right.peak, 0.0);
80    }
81}