1#[derive(Clone, Copy, Debug, Default, PartialEq)]
5#[cfg_attr(feature = "defmt", derive(defmt::Format))]
6pub struct Level {
7 pub peak: f32,
9 pub rms: f32,
11 pub dc: f32,
13}
14
15const FULL_SCALE: f32 = 32768.0;
17
18impl Level {
19 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 pub fn rms_dbfs(&self) -> f32 {
42 to_dbfs(self.rms)
43 }
44
45 pub fn peak_dbfs(&self) -> f32 {
47 to_dbfs(self.peak)
48 }
49}
50
51pub 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 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}