math_audio_dsp/
detector.rs1#[derive(Debug, Clone, Copy, PartialEq)]
7pub enum DetectionMode {
8 Peak,
10 Rms { window_ms: f32 },
12}
13
14#[derive(Debug, Clone)]
19pub struct LevelDetector {
20 mode: DetectionMode,
21 sum_sq: f64,
23 window_buf: Vec<f32>,
25 window_pos: usize,
27 window_len: usize,
29 sample_rate: u32,
30}
31
32impl LevelDetector {
33 pub fn new(mode: DetectionMode, sample_rate: u32) -> Self {
34 let window_len = match mode {
35 DetectionMode::Peak => 0,
36 DetectionMode::Rms { window_ms } => {
37 (window_ms * 0.001 * sample_rate as f32).round() as usize
38 }
39 }
40 .max(1);
41
42 Self {
43 mode,
44 sum_sq: 0.0,
45 window_buf: if matches!(mode, DetectionMode::Rms { .. }) {
46 vec![0.0; window_len]
47 } else {
48 Vec::new()
49 },
50 window_pos: 0,
51 window_len,
52 sample_rate,
53 }
54 }
55
56 #[inline]
58 pub fn process(&mut self, sample: f32) -> f32 {
59 let lin = self.process_linear(sample);
60 if lin < 1e-12 {
61 -120.0
62 } else {
63 20.0 * lin.log10()
64 }
65 }
66
67 #[inline]
69 pub fn process_linear(&mut self, sample: f32) -> f32 {
70 match self.mode {
71 DetectionMode::Peak => sample.abs(),
72 DetectionMode::Rms { .. } => {
73 let sq = (sample * sample) as f64;
74 let oldest = self.window_buf[self.window_pos] as f64;
75 self.sum_sq = (self.sum_sq + sq - oldest).max(0.0);
76 self.window_buf[self.window_pos] = sample * sample;
77 self.window_pos = (self.window_pos + 1) % self.window_len;
78
79 (self.sum_sq / self.window_len as f64).sqrt() as f32
80 }
81 }
82 }
83
84 pub fn reset(&mut self) {
85 self.sum_sq = 0.0;
86 self.window_buf.fill(0.0);
87 self.window_pos = 0;
88 }
89
90 pub fn set_mode(&mut self, mode: DetectionMode) {
92 self.mode = mode;
93 let new_len = match mode {
94 DetectionMode::Peak => 1,
95 DetectionMode::Rms { window_ms } => {
96 (window_ms * 0.001 * self.sample_rate as f32).round() as usize
97 }
98 }
99 .max(1);
100
101 self.window_len = new_len;
102 if matches!(mode, DetectionMode::Rms { .. }) {
103 self.window_buf.resize(new_len, 0.0);
104 }
105 self.reset();
106 }
107
108 pub fn mode(&self) -> DetectionMode {
109 self.mode
110 }
111
112 pub fn sample_rate(&self) -> u32 {
113 self.sample_rate
114 }
115}
116
117#[cfg(test)]
118mod tests {
119 use super::*;
120
121 #[test]
122 fn test_peak_detection() {
123 let mut det = LevelDetector::new(DetectionMode::Peak, 48000);
124 let db = det.process(1.0);
126 assert!((db - 0.0).abs() < 0.01);
127
128 let db = det.process(0.1);
130 assert!((db - (-20.0)).abs() < 0.01);
131
132 let db = det.process(-0.5);
134 let expected = 20.0 * 0.5f32.log10();
135 assert!((db - expected).abs() < 0.01);
136 }
137
138 #[test]
139 fn test_rms_detection() {
140 let mut det = LevelDetector::new(DetectionMode::Rms { window_ms: 10.0 }, 48000);
141 let window_len = (10.0f32 * 0.001 * 48000.0).round() as usize;
143 for _ in 0..window_len {
144 det.process(1.0);
145 }
146 let db = det.process(1.0);
148 assert!((db - 0.0).abs() < 0.1);
149 }
150
151 #[test]
152 fn test_silence_floor() {
153 let mut det = LevelDetector::new(DetectionMode::Peak, 48000);
154 let db = det.process(0.0);
155 assert!(db <= -120.0);
156 }
157
158 #[test]
159 fn test_rms_reset() {
160 let mut det = LevelDetector::new(DetectionMode::Rms { window_ms: 10.0 }, 48000);
161 for _ in 0..1000 {
162 det.process(1.0);
163 }
164 det.reset();
165 let db = det.process(0.0);
166 assert!(db <= -120.0);
167 }
168}