pub trait BargeInDetector {
fn detect(&self, user_audio: &[f32], tts_playing: bool) -> bool;
}
#[derive(Debug, Clone, Copy)]
pub struct EnergyBargeInDetector {
energy_threshold: f32,
}
impl Default for EnergyBargeInDetector {
fn default() -> Self {
Self {
energy_threshold: 0.02,
}
}
}
impl EnergyBargeInDetector {
#[must_use]
pub const fn new(energy_threshold: f32) -> Self {
Self { energy_threshold }
}
#[inline(always)]
#[must_use]
pub const fn energy_threshold(&self) -> f32 {
self.energy_threshold
}
#[must_use]
pub fn with_energy_threshold(self, energy_threshold: f32) -> Self {
Self { energy_threshold }
}
#[must_use]
pub fn rms(samples: &[f32]) -> f32 {
if samples.is_empty() {
return 0.0;
}
let sum_sq: f32 = samples.iter().map(|s| s * s).sum();
(sum_sq / samples.len() as f32).sqrt()
}
}
impl BargeInDetector for EnergyBargeInDetector {
fn detect(&self, user_audio: &[f32], tts_playing: bool) -> bool {
if !tts_playing {
return false;
}
let rms = Self::rms(user_audio);
rms >= self.energy_threshold
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn high_energy_with_tts_playing_is_barge_in() {
let detector = EnergyBargeInDetector::default();
let loud: Vec<f32> = (0..256).map(|i| 0.5 * ((i as f32).sin())).collect();
assert!(detector.detect(&loud, true));
}
#[test]
fn high_energy_without_tts_is_not_barge_in() {
let detector = EnergyBargeInDetector::default();
let loud: Vec<f32> = (0..256).map(|i| 0.5 * ((i as f32).sin())).collect();
assert!(!detector.detect(&loud, false));
}
#[test]
fn low_energy_with_tts_is_not_barge_in() {
let detector = EnergyBargeInDetector::default();
let quiet: Vec<f32> = vec![1e-4; 256];
assert!(!detector.detect(&quiet, true));
}
#[test]
fn empty_audio_is_not_barge_in() {
let detector = EnergyBargeInDetector::default();
assert!(!detector.detect(&[], true));
assert!(!detector.detect(&[], false));
}
#[test]
fn rms_of_constant_signal_equals_amplitude() {
let samples = vec![0.3_f32; 512];
let rms = EnergyBargeInDetector::rms(&samples);
assert!((rms - 0.3).abs() < 1e-6, "expected ≈0.3, got {rms}");
}
#[test]
fn custom_threshold_honored() {
let detector = EnergyBargeInDetector::new(0.5);
let medium: Vec<f32> = vec![0.3; 256];
assert!(!detector.detect(&medium, true));
let loud: Vec<f32> = vec![0.7; 256];
assert!(detector.detect(&loud, true));
}
}