pub const SAMPLE_RATE: usize = 16_000;
pub const VAD_WINDOW_MS: usize = 200;
pub const VAD_THRESHOLD: f32 = 0.018;
pub const VAD_SILENCE_MS: usize = 1500;
const WINDOW: usize = SAMPLE_RATE * VAD_WINDOW_MS / 1000;
#[derive(Debug, Default)]
pub struct Vad {
carry: Vec<f32>,
speech_ms: usize,
silence_ms: usize,
}
impl Vad {
pub fn feed(&mut self, samples: &[f32]) -> bool {
self.carry.extend_from_slice(samples);
let whole = self.carry.len() / WINDOW * WINDOW;
for window in self.carry[..whole].chunks(WINDOW) {
let rms = (window.iter().map(|x| x * x).sum::<f32>() / WINDOW as f32).sqrt();
if rms >= VAD_THRESHOLD {
self.speech_ms += VAD_WINDOW_MS;
self.silence_ms = 0;
} else if self.speech_ms > 0 {
self.silence_ms += VAD_WINDOW_MS;
}
}
self.carry.drain(..whole);
self.speech_ms > 0 && self.silence_ms >= VAD_SILENCE_MS
}
}
#[cfg(test)]
mod tests {
use super::*;
fn tone(ms: usize, amplitude: f32) -> Vec<f32> {
(0..ms * 16)
.map(|i| if i % 2 == 0 { amplitude } else { -amplitude })
.collect()
}
#[test]
fn silence_alone_never_ends_an_utterance() {
let mut vad = Vad::default();
assert!(!vad.feed(&tone(5000, 0.0)));
}
#[test]
fn speech_then_enough_silence_ends_it() {
let mut vad = Vad::default();
assert!(!vad.feed(&tone(600, 0.2)));
assert!(!vad.feed(&tone(1400, 0.0)), "1.4 s is not yet enough");
assert!(
vad.feed(&tone(200, 0.0)),
"1.6 s of silence after speech ends it"
);
}
#[test]
fn speech_resets_the_silence_run() {
let mut vad = Vad::default();
vad.feed(&tone(400, 0.2));
vad.feed(&tone(1000, 0.0));
vad.feed(&tone(200, 0.2));
assert!(!vad.feed(&tone(1400, 0.0)));
assert!(vad.feed(&tone(200, 0.0)));
}
#[test]
fn windows_span_chunk_boundaries() {
let mut vad = Vad::default();
for _ in 0..8 {
vad.feed(&tone(50, 0.2));
}
let mut ended = false;
for _ in 0..40 {
ended |= vad.feed(&tone(50, 0.0));
}
assert!(ended);
}
#[test]
fn quiet_noise_below_the_threshold_is_silence() {
let mut vad = Vad::default();
assert!(!vad.feed(&tone(2000, VAD_THRESHOLD * 0.5)));
}
}