pub(super) const SILENCE_ATTACK_NEW: f64 = 0.05;
pub(super) const SILENCE_ATTACK_PREV: f64 = 0.95;
pub(super) const SILENCE_RELEASE_NEW: f64 = 0.01;
pub(super) const SILENCE_RELEASE_PREV: f64 = 0.99;
pub(super) const SILENCE_ALPHA: f64 = 2.0;
pub(super) const SILENCE_BETA: f64 = 4.0;
pub(super) const SILENCE_ENTER_FRAMES: u8 = 5;
pub(super) const SILENCE_EXIT_FRAMES: u8 = 3;
pub(super) const SILENCE_ETA_INIT: f64 = 20000.0;
#[derive(Clone, Copy, Debug)]
pub struct SilenceDetector {
eta: f64,
silent_count: u8,
voice_count: u8,
in_silence: bool,
}
impl SilenceDetector {
pub const fn cold_start() -> Self {
Self {
eta: SILENCE_ETA_INIT,
silent_count: 0,
voice_count: 0,
in_silence: false,
}
}
#[inline]
pub fn is_silent(&self) -> bool {
self.in_silence
}
#[inline]
pub fn noise_floor(&self) -> f64 {
self.eta
}
pub fn update(&mut self, frame_energy: f64) -> bool {
self.eta = if frame_energy < self.eta {
SILENCE_ATTACK_PREV * self.eta + SILENCE_ATTACK_NEW * frame_energy
} else {
SILENCE_RELEASE_PREV * self.eta + SILENCE_RELEASE_NEW * frame_energy
};
if frame_energy < SILENCE_ALPHA * self.eta {
self.silent_count = self.silent_count.saturating_add(1);
self.voice_count = 0;
} else if frame_energy > SILENCE_BETA * self.eta {
self.voice_count = self.voice_count.saturating_add(1);
self.silent_count = 0;
}
if !self.in_silence && self.silent_count >= SILENCE_ENTER_FRAMES {
self.in_silence = true;
} else if self.in_silence && self.voice_count >= SILENCE_EXIT_FRAMES {
self.in_silence = false;
}
self.in_silence
}
}
impl Default for SilenceDetector {
fn default() -> Self {
Self::cold_start()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn silence_detector_cold_start_is_not_silent() {
let d = SilenceDetector::cold_start();
assert!(!d.is_silent());
assert_eq!(d.noise_floor(), SILENCE_ETA_INIT);
}
#[test]
fn silence_detector_exits_silence_after_voice_hysteresis() {
let mut d = SilenceDetector::cold_start();
for _ in 0..20 {
d.update(0.0);
}
assert!(d.is_silent(), "should have entered silence");
d.update(1e12);
d.update(1e12);
assert!(d.is_silent(), "should still be silent after 2 voice frames");
d.update(1e12);
assert!(
!d.is_silent(),
"should have exited silence after {SILENCE_EXIT_FRAMES} voice frames"
);
}
#[test]
fn silence_detector_enters_silence_after_hysteresis() {
let mut d = SilenceDetector::cold_start();
for _ in 0..30 {
d.update(1e8);
}
assert!(!d.is_silent(), "loud frames should not be silent");
for i in 0..SILENCE_ENTER_FRAMES {
assert!(!d.is_silent(), "should not be silent before {i} silent frames");
d.update(0.0);
}
assert!(d.is_silent(), "should have entered silence after enter-hysteresis");
}
}