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(all(feature = "silero", not(target_family = "wasm")))]
6mod map;
7#[cfg(feature = "silero")]
8mod silero;
9
10pub use endpointer::{Boundary, Endpointer};
11#[cfg(all(feature = "silero", not(target_family = "wasm")))]
12pub(crate) use map::Progress;
13#[cfg(all(feature = "silero", not(target_family = "wasm")))]
14pub use map::{SpeechDetector, SpeechMap};
15#[cfg(feature = "silero")]
16pub use silero::{SileroVad, Speech};
17
18pub const SPEECH_THRESHOLD: f32 = 0.5;
19
20/// The silence that closes an utterance in a [`SpeechMap`].
21#[cfg(all(feature = "silero", not(target_family = "wasm")))]
22pub(crate) const UTTERANCE_GAP: std::time::Duration = std::time::Duration::from_millis(700);
23
24/// Frames to keep before a [`Boundary::Start`], so an utterance does not open
25/// mid-syllable. `StopOnSilence` keeps this many.
26pub const LEAD_IN_FRAMES: usize = 4;
27
28pub trait VoiceActivityDetector {
29    /// The exact number of canonical samples `probability` accepts per call.
30    /// At least one.
31    const FRAME_SAMPLES: usize;
32
33    /// Clears state carried across recordings; stateless detectors keep the no-op.
34    fn reset(&mut self) {}
35
36    fn probability(&mut self, samples: &[i16]) -> Result<f32, VoiceActivityError>;
37}
38
39impl<V: VoiceActivityDetector> VoiceActivityDetector for &mut V {
40    const FRAME_SAMPLES: usize = V::FRAME_SAMPLES;
41
42    fn reset(&mut self) {
43        (**self).reset();
44    }
45
46    fn probability(&mut self, samples: &[i16]) -> Result<f32, VoiceActivityError> {
47        (**self).probability(samples)
48    }
49}
50
51#[derive(Debug, thiserror::Error)]
52pub enum VoiceActivityError {
53    #[error("failed to load the bundled voice-activity model")]
54    LoadModel(#[source] Box<dyn std::error::Error + Send + Sync>),
55
56    #[error("voice-activity frame contains {actual_samples} samples; expected {expected_samples}")]
57    InvalidFrameLength {
58        actual_samples: usize,
59        expected_samples: usize,
60    },
61
62    #[error("voice-activity inference failed")]
63    Inference(#[source] Box<dyn std::error::Error + Send + Sync>),
64
65    #[error("voice-activity model returned invalid probability {0}")]
66    InvalidProbability(f32),
67}