use crate::error::GigasttError;
use crate::vad::{SileroVad, VadConfig, VadSegmenter};
use super::stream::{FileWindows, PcmWindow, PcmWindows, WindowCursor, WindowSpec};
const PULL_SAMPLES: usize = 32_000;
const ABORT_POLL_BLOCKS: usize = 16;
pub(crate) struct VadWindows<'a> {
raw: FileWindows,
vad: &'a SileroVad,
seg: VadSegmenter,
abort: Option<&'a dyn Fn() -> bool>,
buf: Vec<f32>,
buf_start_abs: usize,
compressed_total: usize,
eof: bool,
vad_failed: bool,
blocks: usize,
cursor: WindowCursor,
}
impl<'a> VadWindows<'a> {
pub(crate) fn new(
raw: FileWindows,
vad: &'a SileroVad,
cfg: &VadConfig,
spec: WindowSpec,
abort: Option<&'a dyn Fn() -> bool>,
) -> Self {
Self {
raw,
vad,
seg: VadSegmenter::new(cfg),
abort,
buf: Vec::new(),
buf_start_abs: 0,
compressed_total: 0,
eof: false,
vad_failed: false,
blocks: 0,
cursor: WindowCursor::new(spec),
}
}
pub(crate) fn pull_spec() -> WindowSpec {
WindowSpec::new(0, PULL_SAMPLES, 0)
}
pub(crate) fn regions(&self) -> &[(usize, usize)] {
self.seg.regions()
}
pub(crate) fn total_16k_samples(&self) -> usize {
self.raw.total_16k_samples()
}
pub(crate) fn needs_fallback(&self) -> bool {
self.vad_failed || (self.regions().is_empty() && self.total_16k_samples() > 0)
}
fn fill_to(&mut self, target: usize) -> Result<(), GigasttError> {
let Self {
raw,
vad,
seg,
abort,
buf,
compressed_total,
eof,
vad_failed,
blocks,
..
} = self;
while !*eof && *compressed_total < target {
if let Some(abort) = abort {
*blocks += 1;
if *blocks >= ABORT_POLL_BLOCKS {
*blocks = 0;
if abort() {
return Err(GigasttError::Cancelled);
}
}
}
let before = buf.len();
let scanned = match raw.next_window()? {
Some(w) => seg.push(vad, w.samples, buf),
None => {
*eof = true;
seg.finish(vad, raw.total_16k_samples(), buf)
}
};
if let Err(e) = scanned {
tracing::warn!("VAD failed mid-stream, decoding full audio: {e:#}");
*vad_failed = true;
*eof = true;
buf.truncate(before);
return Ok(());
}
*compressed_total += buf.len() - before;
}
Ok(())
}
}
impl PcmWindows for VadWindows<'_> {
fn spec(&self) -> WindowSpec {
self.cursor.spec()
}
fn next_window(&mut self) -> Result<Option<PcmWindow<'_>>, GigasttError> {
if self.cursor.is_done() {
return Ok(None);
}
let drop = self
.cursor
.next_start()
.saturating_sub(self.buf_start_abs)
.min(self.buf.len());
if drop > 0 {
self.buf.drain(0..drop);
self.buf_start_abs += drop;
}
self.fill_to(self.cursor.fill_target())?;
if self.eof && self.compressed_total == 0 {
return Ok(None);
}
let Some((start, end)) = self.cursor.take(self.compressed_total, self.eof) else {
return Ok(None);
};
let s = start - self.buf_start_abs;
let e = end - self.buf_start_abs;
Ok(Some(PcmWindow {
start_sample: start,
samples: &self.buf[s..e],
}))
}
}
#[cfg(test)]
mod tests;