Skip to main content

aria_kit/voice_activity/
mod.rs

1//! Voice-activity inference over canonical audio frames: which stretches
2//! carry a human voice rather than music, noise, or silence.
3
4mod endpointer;
5#[cfg(feature = "silero")]
6mod silero;
7
8pub use endpointer::{Boundary, Endpointer};
9#[cfg(feature = "silero")]
10pub use silero::{SileroVad, Speech};
11
12pub const SPEECH_THRESHOLD: f32 = 0.5;
13
14/// Frames to keep before a [`Boundary::Start`], so an utterance does not open
15/// mid-syllable. `StopOnSilence` keeps this many.
16pub const LEAD_IN_FRAMES: usize = 4;
17
18pub trait VoiceActivityDetector {
19    /// The exact number of canonical samples `probability` accepts per call.
20    /// At least one.
21    const FRAME_SAMPLES: usize;
22
23    /// Clears state carried across recordings; stateless detectors keep the no-op.
24    fn reset(&mut self) {}
25
26    fn probability(&mut self, samples: &[i16]) -> Result<f32, VoiceActivityError>;
27}
28
29impl<V: VoiceActivityDetector> VoiceActivityDetector for &mut V {
30    const FRAME_SAMPLES: usize = V::FRAME_SAMPLES;
31
32    fn reset(&mut self) {
33        (**self).reset();
34    }
35
36    fn probability(&mut self, samples: &[i16]) -> Result<f32, VoiceActivityError> {
37        (**self).probability(samples)
38    }
39}
40
41#[derive(Debug, thiserror::Error)]
42pub enum VoiceActivityError {
43    #[error("failed to load the bundled voice-activity model")]
44    LoadModel(#[source] Box<dyn std::error::Error + Send + Sync>),
45
46    #[error("voice-activity frame contains {actual_samples} samples; expected {expected_samples}")]
47    InvalidFrameLength {
48        actual_samples: usize,
49        expected_samples: usize,
50    },
51
52    #[error("voice-activity inference failed")]
53    Inference(#[source] Box<dyn std::error::Error + Send + Sync>),
54
55    #[error("voice-activity model returned invalid probability {0}")]
56    InvalidProbability(f32),
57}