#[derive(Debug, Clone, Copy, PartialEq)]
pub enum DetectionMode {
Peak,
Rms { window_ms: f32 },
}
#[derive(Debug, Clone)]
pub struct LevelDetector {
mode: DetectionMode,
sum_sq: f64,
window_buf: Vec<f32>,
window_pos: usize,
window_len: usize,
sample_rate: u32,
}
impl LevelDetector {
pub fn new(mode: DetectionMode, sample_rate: u32) -> Self {
let window_len = match mode {
DetectionMode::Peak => 0,
DetectionMode::Rms { window_ms } => {
(window_ms * 0.001 * sample_rate as f32).round() as usize
}
}
.max(1);
Self {
mode,
sum_sq: 0.0,
window_buf: if matches!(mode, DetectionMode::Rms { .. }) {
vec![0.0; window_len]
} else {
Vec::new()
},
window_pos: 0,
window_len,
sample_rate,
}
}
#[inline]
pub fn process(&mut self, sample: f32) -> f32 {
let lin = self.process_linear(sample);
if lin < 1e-12 {
-120.0
} else {
20.0 * lin.log10()
}
}
#[inline]
pub fn process_linear(&mut self, sample: f32) -> f32 {
match self.mode {
DetectionMode::Peak => sample.abs(),
DetectionMode::Rms { .. } => {
let sq = (sample * sample) as f64;
let oldest = self.window_buf[self.window_pos] as f64;
self.sum_sq = (self.sum_sq + sq - oldest).max(0.0);
self.window_buf[self.window_pos] = sample * sample;
self.window_pos = (self.window_pos + 1) % self.window_len;
(self.sum_sq / self.window_len as f64).sqrt() as f32
}
}
}
pub fn reset(&mut self) {
self.sum_sq = 0.0;
self.window_buf.fill(0.0);
self.window_pos = 0;
}
pub fn set_mode(&mut self, mode: DetectionMode) {
self.mode = mode;
let new_len = match mode {
DetectionMode::Peak => 1,
DetectionMode::Rms { window_ms } => {
(window_ms * 0.001 * self.sample_rate as f32).round() as usize
}
}
.max(1);
self.window_len = new_len;
if matches!(mode, DetectionMode::Rms { .. }) {
self.window_buf.resize(new_len, 0.0);
}
self.reset();
}
pub fn mode(&self) -> DetectionMode {
self.mode
}
pub fn sample_rate(&self) -> u32 {
self.sample_rate
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_peak_detection() {
let mut det = LevelDetector::new(DetectionMode::Peak, 48000);
let db = det.process(1.0);
assert!((db - 0.0).abs() < 0.01);
let db = det.process(0.1);
assert!((db - (-20.0)).abs() < 0.01);
let db = det.process(-0.5);
let expected = 20.0 * 0.5f32.log10();
assert!((db - expected).abs() < 0.01);
}
#[test]
fn test_rms_detection() {
let mut det = LevelDetector::new(DetectionMode::Rms { window_ms: 10.0 }, 48000);
let window_len = (10.0f32 * 0.001 * 48000.0).round() as usize;
for _ in 0..window_len {
det.process(1.0);
}
let db = det.process(1.0);
assert!((db - 0.0).abs() < 0.1);
}
#[test]
fn test_silence_floor() {
let mut det = LevelDetector::new(DetectionMode::Peak, 48000);
let db = det.process(0.0);
assert!(db <= -120.0);
}
#[test]
fn test_rms_reset() {
let mut det = LevelDetector::new(DetectionMode::Rms { window_ms: 10.0 }, 48000);
for _ in 0..1000 {
det.process(1.0);
}
det.reset();
let db = det.process(0.0);
assert!(db <= -120.0);
}
}