use anyhow::Result;
use super::{SileroVad, VAD_FRAME_SAMPLES, VAD_STATE_LEN, VadConfig};
pub struct VadEndpointer {
state: [f32; VAD_STATE_LEN],
leftover: Vec<f32>,
hangover: Hangover,
}
impl VadEndpointer {
pub fn new(cfg: &VadConfig) -> Self {
Self {
state: [0.0f32; VAD_STATE_LEN],
leftover: Vec::with_capacity(VAD_FRAME_SAMPLES),
hangover: Hangover::new(cfg),
}
}
pub fn push(&mut self, vad: &SileroVad, samples: &[f32]) -> Result<bool> {
self.leftover.extend_from_slice(samples);
let mut endpoint = false;
let mut off = 0;
while off + VAD_FRAME_SAMPLES <= self.leftover.len() {
let prob = vad.run_frame(
&self.leftover[off..off + VAD_FRAME_SAMPLES],
&mut self.state,
)?;
off += VAD_FRAME_SAMPLES;
if self.hangover.update(prob, VAD_FRAME_SAMPLES) {
endpoint = true;
}
}
if off > 0 {
self.leftover.drain(..off);
}
Ok(endpoint)
}
}
#[derive(Debug)]
pub struct Hangover {
threshold: f32,
min_silence_samples: usize,
seen_speech: bool,
trailing_silence: usize,
armed: bool,
}
impl Hangover {
pub(crate) fn new(cfg: &VadConfig) -> Self {
Self {
threshold: cfg.threshold,
min_silence_samples: VadConfig::ms_to_samples(cfg.min_silence_ms),
seen_speech: false,
trailing_silence: 0,
armed: false,
}
}
pub(crate) fn update(&mut self, prob: f32, frame_samples: usize) -> bool {
if prob >= self.threshold {
self.seen_speech = true;
self.armed = true;
self.trailing_silence = 0;
return false;
}
if !self.seen_speech {
return false;
}
self.trailing_silence += frame_samples;
if self.armed && self.trailing_silence >= self.min_silence_samples {
self.armed = false; return true;
}
false
}
}