use crate::error::{Error, Result};
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct VadSegmentOptions {
pub threshold: f32,
pub neg_threshold: Option<f32>,
pub min_speech_duration_ms: u32,
pub max_speech_duration_s: f32,
pub min_silence_duration_ms: u32,
pub speech_pad_ms: u32,
pub min_silence_at_max_speech_ms: u32,
pub use_max_possible_silence_at_max_speech: bool,
}
impl Default for VadSegmentOptions {
fn default() -> Self {
Self {
threshold: 0.5,
neg_threshold: None,
min_speech_duration_ms: 250,
max_speech_duration_s: f32::INFINITY,
min_silence_duration_ms: 100,
speech_pad_ms: 30,
min_silence_at_max_speech_ms: 98,
use_max_possible_silence_at_max_speech: true,
}
}
}
impl VadSegmentOptions {
pub fn validate(&self) -> Result<()> {
if !self.threshold.is_finite() || self.threshold <= 0.0 || self.threshold > 1.0 {
return Err(Error::InvalidArgument {
arg: "opts.threshold",
reason: format!("must be finite and in (0, 1], got {}", self.threshold),
});
}
if let Some(neg) = self.neg_threshold
&& (!neg.is_finite() || neg <= 0.0 || neg >= self.threshold)
{
return Err(Error::InvalidArgument {
arg: "opts.neg_threshold",
reason: format!(
"must be finite and in (0, threshold), got {neg} against a threshold of {}",
self.threshold
),
});
}
if self.max_speech_duration_s.is_nan() || self.max_speech_duration_s <= 0.0 {
return Err(Error::InvalidArgument {
arg: "opts.max_speech_duration_s",
reason: format!(
"must be positive (or infinite for no cap), got {}",
self.max_speech_duration_s
),
});
}
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SpeechSegment {
pub start: usize,
pub end: usize,
}
impl SpeechSegment {
pub fn len(&self) -> usize {
self.end.saturating_sub(self.start)
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn duration_secs(&self, sample_rate: usize) -> f64 {
if sample_rate == 0 {
return 0.0;
}
self.len() as f64 / sample_rate as f64
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_segment_reports_its_length_and_duration() {
let seg = SpeechSegment {
start: 4640,
end: 15840,
};
assert_eq!(seg.len(), 11200);
assert!(!seg.is_empty());
assert!((seg.duration_secs(16000) - 0.7).abs() < 1e-9);
assert_eq!(seg.duration_secs(0), 0.0);
let inverted = SpeechSegment { start: 10, end: 5 };
assert_eq!(inverted.len(), 0);
assert!(inverted.is_empty());
}
#[test]
fn a_threshold_outside_the_unit_interval_is_rejected() {
for threshold in [0.0, -0.5, 1.5, f32::NAN, f32::INFINITY] {
let bad = VadSegmentOptions {
threshold,
..VadSegmentOptions::default()
};
assert!(
bad.validate().is_err(),
"threshold {threshold} must be refused"
);
}
let edge = VadSegmentOptions {
threshold: 1.0,
..VadSegmentOptions::default()
};
assert!(edge.validate().is_ok());
}
#[test]
fn a_nonsensical_neg_threshold_is_rejected() {
for neg in [f32::NAN, 0.0, -0.1, 1.5] {
let bad = VadSegmentOptions {
neg_threshold: Some(neg),
..VadSegmentOptions::default()
};
assert!(
bad.validate().is_err(),
"neg_threshold {neg} must be refused"
);
}
let bad = VadSegmentOptions {
threshold: 0.5,
neg_threshold: Some(0.5),
..VadSegmentOptions::default()
};
assert!(bad.validate().is_err());
let good = VadSegmentOptions {
neg_threshold: Some(0.35),
..VadSegmentOptions::default()
};
assert!(good.validate().is_ok());
assert!(VadSegmentOptions::default().validate().is_ok());
}
#[test]
fn a_nonsensical_max_speech_duration_is_rejected() {
for cap in [f32::NAN, 0.0, -1.0] {
let bad = VadSegmentOptions {
max_speech_duration_s: cap,
..VadSegmentOptions::default()
};
assert!(
bad.validate().is_err(),
"max_speech_duration_s {cap} must be refused"
);
}
assert!(
VadSegmentOptions::default()
.max_speech_duration_s
.is_infinite()
);
assert!(VadSegmentOptions::default().validate().is_ok());
}
}