pub mod silero;
pub use silero::{SileroVad, FRAME_LEN};
const FRAME_MS: u32 = (FRAME_LEN as u32 * 1000) / 16_000;
const PREROLL_FRAMES: usize = 8;
const MIN_SEGMENT_MS: u32 = 250;
#[derive(Debug, Clone, PartialEq)]
pub enum VadEvent {
SpeechStart,
Segment(Vec<f32>),
}
#[derive(Debug, Clone, Copy)]
enum State {
Listening,
Speaking { silent_frames: u32 },
}
pub struct VadSegmenter {
threshold: f32,
silence_ms: u32,
state: State,
preroll: [[f32; FRAME_LEN]; PREROLL_FRAMES],
preroll_pos: usize,
preroll_len: usize,
segment: Vec<f32>,
preroll_at_onset: usize,
}
impl VadSegmenter {
pub fn new(threshold: f32, silence_ms: u32) -> Self {
Self {
threshold,
silence_ms,
state: State::Listening,
preroll: [[0.0; FRAME_LEN]; PREROLL_FRAMES],
preroll_pos: 0,
preroll_len: 0,
segment: Vec::new(),
preroll_at_onset: 0,
}
}
pub fn push(&mut self, frame: &[f32; FRAME_LEN], prob: f32) -> Option<VadEvent> {
let is_speech = prob >= self.threshold;
match self.state {
State::Listening => {
if is_speech {
self.segment.clear();
let first =
(self.preroll_pos + PREROLL_FRAMES - self.preroll_len) % PREROLL_FRAMES;
for i in 0..self.preroll_len {
let idx = (first + i) % PREROLL_FRAMES;
let preroll_frame = self.preroll[idx];
self.segment.extend_from_slice(&preroll_frame);
}
self.preroll_at_onset = self.preroll_len;
self.segment.extend_from_slice(frame);
self.ring_push(frame);
self.state = State::Speaking { silent_frames: 0 };
Some(VadEvent::SpeechStart)
} else {
self.ring_push(frame);
None
}
}
State::Speaking { silent_frames } => {
self.segment.extend_from_slice(frame);
self.ring_push(frame);
if is_speech {
self.state = State::Speaking { silent_frames: 0 };
return None;
}
let silent_frames = silent_frames + 1;
if silent_frames * FRAME_MS > self.silence_ms {
self.state = State::Listening;
let total_frames = self.segment.len() / FRAME_LEN;
let active_frames = total_frames
.saturating_sub(self.preroll_at_onset)
.saturating_sub(silent_frames as usize);
if (active_frames as u32 * FRAME_MS) >= MIN_SEGMENT_MS {
Some(VadEvent::Segment(std::mem::take(&mut self.segment)))
} else {
self.segment.clear();
None
}
} else {
self.state = State::Speaking { silent_frames };
None
}
}
}
}
fn ring_push(&mut self, frame: &[f32; FRAME_LEN]) {
self.preroll[self.preroll_pos] = *frame;
self.preroll_pos = (self.preroll_pos + 1) % PREROLL_FRAMES;
self.preroll_len = (self.preroll_len + 1).min(PREROLL_FRAMES);
}
}