use anyhow::Result;
use super::{SileroVad, VAD_FRAME_SAMPLES, VAD_STATE_LEN, VadConfig};
#[cfg(feature = "file-decode")]
pub(crate) struct VadSegmenter {
threshold: f32,
min_silence: usize,
min_speech: usize,
pad: usize,
state: [f32; VAD_STATE_LEN],
raw: Vec<f32>,
raw_start: usize,
pos: usize,
in_run: bool,
merged: Option<(usize, usize)>,
open_idx: Option<usize>,
regions: Vec<(usize, usize)>,
out_idx: usize,
copied_to: usize,
}
#[cfg(feature = "file-decode")]
impl VadSegmenter {
pub(crate) fn new(cfg: &VadConfig) -> Self {
Self {
threshold: cfg.threshold,
min_silence: VadConfig::ms_to_samples(cfg.min_silence_ms),
min_speech: VadConfig::ms_to_samples(cfg.min_speech_ms),
pad: VadConfig::ms_to_samples(cfg.speech_pad_ms),
state: [0.0; VAD_STATE_LEN],
raw: Vec::new(),
raw_start: 0,
pos: 0,
in_run: false,
merged: None,
open_idx: None,
regions: Vec::new(),
out_idx: 0,
copied_to: 0,
}
}
pub(crate) fn regions(&self) -> &[(usize, usize)] {
&self.regions
}
pub(crate) fn push(
&mut self,
vad: &SileroVad,
samples: &[f32],
out: &mut Vec<f32>,
) -> Result<()> {
self.push_with(samples, out, |frame, state| vad.run_frame(frame, state))
}
pub(crate) fn finish(
&mut self,
vad: &SileroVad,
total: usize,
out: &mut Vec<f32>,
) -> Result<()> {
self.finish_with(total, out, |frame, state| vad.run_frame(frame, state))
}
pub(crate) fn push_with<F>(
&mut self,
samples: &[f32],
out: &mut Vec<f32>,
mut score: F,
) -> Result<()>
where
F: FnMut(&[f32], &mut [f32; VAD_STATE_LEN]) -> Result<f32>,
{
self.raw.extend_from_slice(samples);
let avail = self.raw_start + self.raw.len();
while self.pos + VAD_FRAME_SAMPLES <= avail {
let off = self.pos - self.raw_start;
let prob = score(&self.raw[off..off + VAD_FRAME_SAMPLES], &mut self.state)?;
self.step(prob, self.pos + VAD_FRAME_SAMPLES);
}
self.flush(out);
self.trim();
Ok(())
}
pub(crate) fn finish_with<F>(
&mut self,
total: usize,
out: &mut Vec<f32>,
mut score: F,
) -> Result<()>
where
F: FnMut(&[f32], &mut [f32; VAD_STATE_LEN]) -> Result<f32>,
{
while self.pos < total {
let off = self.pos - self.raw_start;
let end = (off + VAD_FRAME_SAMPLES).min(self.raw.len());
let prob = score(&self.raw[off..end], &mut self.state)?;
self.step(prob, (self.pos + VAD_FRAME_SAMPLES).min(total));
}
self.close(total);
self.flush(out);
Ok(())
}
fn close(&mut self, total: usize) {
if self.in_run
&& let Some(m) = self.merged.as_mut()
{
m.1 = total;
self.in_run = false;
}
if let Some((ms, me)) = self.merged.take() {
self.finalize(ms, me);
}
for r in &mut self.regions {
r.1 = r.1.min(total);
}
self.pos = self.pos.max(total);
}
fn step(&mut self, prob: f32, end: usize) {
let start = self.pos;
if prob >= self.threshold {
if !self.in_run {
match self.merged {
Some((_, me)) if start.saturating_sub(me) < self.min_silence => {}
Some((ms, me)) => {
self.finalize(ms, me);
self.merged = None;
}
None => {}
}
if self.merged.is_none() {
self.merged = Some((start, start));
}
self.in_run = true;
}
if let Some(m) = self.merged.as_mut() {
m.1 = end;
}
if let Some((ms, me)) = self.merged {
match self.open_idx {
Some(i) => self.regions[i].1 = self.regions[i].1.max(me),
None if me - ms >= self.min_speech => {
self.open_idx = Some(self.push_padded(ms.saturating_sub(self.pad), me));
}
None => {}
}
}
} else {
self.in_run = false;
if let Some((ms, me)) = self.merged
&& end.saturating_sub(me) >= self.min_silence
{
self.finalize(ms, me);
self.merged = None;
}
}
self.pos = end;
}
fn push_padded(&mut self, ps: usize, pe: usize) -> usize {
match self.regions.last_mut() {
Some(last) if ps <= last.1 => last.1 = last.1.max(pe),
_ => self.regions.push((ps, pe)),
}
self.regions.len() - 1
}
fn finalize(&mut self, ms: usize, me: usize) {
let idx = self.open_idx.take();
if me - ms < self.min_speech {
return;
}
let pe = me + self.pad;
match idx {
Some(i) => self.regions[i].1 = self.regions[i].1.max(pe),
None => {
self.push_padded(ms.saturating_sub(self.pad), pe);
}
}
}
fn decided_to(&self) -> usize {
let base = self.regions.last().map_or(0, |r| r.1);
let bound = match self.merged {
Some(_) if self.open_idx.is_some() => base,
Some((ms, _)) => base.max(ms.saturating_sub(self.pad)),
None => base.max(self.pos.saturating_sub(self.pad)),
};
bound.min(self.pos)
}
fn flush(&mut self, out: &mut Vec<f32>) {
let decided = self.decided_to().min(self.raw_start + self.raw.len());
while self.out_idx < self.regions.len() {
let (s, e) = self.regions[self.out_idx];
let from = self.copied_to.max(s);
let to = e.min(decided);
if to > from {
debug_assert!(
from >= self.raw_start,
"released PCM at {from} below the retained start {}",
self.raw_start
);
out.extend_from_slice(&self.raw[from - self.raw_start..to - self.raw_start]);
self.copied_to = to;
}
if self.out_idx + 1 < self.regions.len() {
self.out_idx += 1;
} else {
break;
}
}
}
fn trim(&mut self) {
let decided = self.decided_to();
let keep_from = match self.merged {
Some((ms, _)) if self.open_idx.is_none() => decided.min(ms.saturating_sub(self.pad)),
_ => decided,
};
let drop = keep_from.saturating_sub(self.raw_start).min(self.raw.len());
if drop > 0 {
self.raw.drain(..drop);
self.raw_start += drop;
}
}
#[cfg(test)]
pub(crate) fn retained(&self) -> usize {
self.raw.len()
}
}