pub fn rms_energy(samples: &[i16]) -> f64 {
if samples.is_empty() {
return 0.0;
}
let sum_squares: f64 = samples.iter().map(|&s| (s as f64) * (s as f64)).sum();
let rms = (sum_squares / samples.len() as f64).sqrt();
rms / i16::MAX as f64
}
pub fn is_silent(samples: &[i16], threshold: f64) -> bool {
rms_energy(samples) < threshold
}
pub struct AutoStopDetector {
threshold: f64,
silent_samples: u64,
timeout_samples: u64,
speech_detected: bool,
}
impl AutoStopDetector {
pub fn new(threshold: f64, timeout_ms: u64, sample_rate: u32) -> Self {
let timeout_samples = (timeout_ms * sample_rate as u64) / 1000;
Self {
threshold,
silent_samples: 0,
timeout_samples,
speech_detected: false,
}
}
pub fn feed(&mut self, samples: &[i16]) -> bool {
if is_silent(samples, self.threshold) {
self.silent_samples += samples.len() as u64;
} else {
self.silent_samples = 0;
self.speech_detected = true;
}
self.speech_detected && self.silent_samples >= self.timeout_samples
}
pub fn reset(&mut self) {
self.silent_samples = 0;
self.speech_detected = false;
}
pub fn has_speech(&self) -> bool {
self.speech_detected
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn silence_is_zero_rms() {
let silence = vec![0i16; 1600];
assert_eq!(rms_energy(&silence), 0.0);
assert!(is_silent(&silence, 0.01));
}
#[test]
fn empty_slice_is_zero_rms() {
assert_eq!(rms_energy(&[]), 0.0);
}
#[test]
fn loud_signal_has_high_rms() {
let loud: Vec<i16> = vec![i16::MAX; 1600];
let rms = rms_energy(&loud);
assert!(rms > 0.9, "loud signal RMS should be near 1.0, got {rms}");
assert!(!is_silent(&loud, 0.01));
}
#[test]
fn quiet_signal_is_detected() {
let quiet: Vec<i16> = (0..1600).map(|i| ((i % 100) as i16) - 50).collect();
let rms = rms_energy(&quiet);
assert!(rms < 0.01, "quiet signal RMS should be low, got {rms}");
assert!(is_silent(&quiet, 0.01));
}
#[test]
fn medium_signal() {
let medium: Vec<i16> = (0..1600)
.map(|i| ((i as f64 * 0.1).sin() * 16000.0) as i16)
.collect();
let rms = rms_energy(&medium);
assert!(rms > 0.1, "medium signal should have noticeable RMS");
assert!(!is_silent(&medium, 0.01));
}
#[test]
fn auto_stop_not_triggered_without_speech() {
let mut detector = AutoStopDetector::new(0.01, 2000, 16_000);
let silence = vec![0i16; 16_000]; assert!(!detector.feed(&silence));
assert!(!detector.feed(&silence));
assert!(!detector.feed(&silence));
assert!(!detector.has_speech());
}
#[test]
fn auto_stop_triggered_after_speech_then_silence() {
let mut detector = AutoStopDetector::new(0.01, 2000, 16_000);
let loud: Vec<i16> = vec![10000; 16_000]; assert!(!detector.feed(&loud));
assert!(detector.has_speech());
let silence = vec![0i16; 16_000];
assert!(!detector.feed(&silence));
assert!(detector.feed(&silence));
}
#[test]
fn auto_stop_resets_on_speech() {
let mut detector = AutoStopDetector::new(0.01, 2000, 16_000);
let loud: Vec<i16> = vec![10000; 16_000];
let silence = vec![0i16; 16_000];
detector.feed(&loud);
detector.feed(&silence);
let half_sec = vec![0i16; 8_000];
assert!(!detector.feed(&half_sec));
assert!(!detector.feed(&loud));
detector.feed(&silence);
assert!(!detector.feed(&half_sec));
assert!(detector.feed(&half_sec));
}
#[test]
fn auto_stop_reset() {
let mut detector = AutoStopDetector::new(0.01, 2000, 16_000);
let loud: Vec<i16> = vec![10000; 16_000];
detector.feed(&loud);
assert!(detector.has_speech());
detector.reset();
assert!(!detector.has_speech());
}
#[test]
fn auto_stop_exact_threshold() {
let mut detector = AutoStopDetector::new(0.01, 100, 16_000);
let loud: Vec<i16> = vec![10000; 1600];
detector.feed(&loud);
let silence = vec![0i16; 1600];
assert!(detector.feed(&silence));
}
}