use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EchoCancellerConfig {
#[serde(default = "default_true")]
pub enabled: bool,
#[serde(default = "default_tail_ms")]
pub tail_length_ms: u32,
#[serde(default = "default_suppression")]
pub suppression_level: f32,
}
fn default_true() -> bool {
true
}
fn default_tail_ms() -> u32 {
128
}
fn default_suppression() -> f32 {
0.8
}
impl Default for EchoCancellerConfig {
fn default() -> Self {
Self {
enabled: true,
tail_length_ms: 128,
suppression_level: 0.8,
}
}
}
pub struct EchoCanceller {
config: EchoCancellerConfig,
reference_buffer: Vec<f32>,
write_pos: usize,
frames_processed: u64,
}
impl EchoCanceller {
pub fn new(config: EchoCancellerConfig) -> Self {
let buffer_size = (config.tail_length_ms as usize * 16000) / 1000;
Self {
config,
reference_buffer: vec![0.0; buffer_size],
write_pos: 0,
frames_processed: 0,
}
}
pub fn feed_reference(&mut self, samples: &[f32]) {
if !self.config.enabled {
return;
}
for &sample in samples {
self.reference_buffer[self.write_pos] = sample;
self.write_pos = (self.write_pos + 1) % self.reference_buffer.len();
}
}
pub fn process(&mut self, input: &[f32]) -> Vec<f32> {
self.frames_processed += 1;
if !self.config.enabled {
return input.to_vec();
}
input.to_vec()
}
pub fn reset(&mut self) {
self.reference_buffer.fill(0.0);
self.write_pos = 0;
self.frames_processed = 0;
}
pub fn frames_processed(&self) -> u64 {
self.frames_processed
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_creates_with_defaults() {
let ec = EchoCanceller::new(EchoCancellerConfig::default());
assert_eq!(ec.frames_processed(), 0);
assert_eq!(ec.reference_buffer.len(), 2048);
}
#[test]
fn process_passthrough_preserves_input() {
let mut ec = EchoCanceller::new(EchoCancellerConfig::default());
let input = vec![0.1, 0.2, -0.3, 0.4, -0.5];
let output = ec.process(&input);
assert_eq!(input, output);
assert_eq!(ec.frames_processed(), 1);
}
#[test]
fn feed_reference_does_not_panic() {
let mut ec = EchoCanceller::new(EchoCancellerConfig::default());
let reference = vec![0.5; 4096]; ec.feed_reference(&reference);
assert!(ec.write_pos < ec.reference_buffer.len());
}
#[test]
fn reset_clears_state() {
let mut ec = EchoCanceller::new(EchoCancellerConfig::default());
ec.feed_reference(&[1.0; 100]);
ec.process(&[0.5; 10]);
ec.process(&[0.5; 10]);
assert_eq!(ec.frames_processed(), 2);
ec.reset();
assert_eq!(ec.frames_processed(), 0);
assert_eq!(ec.write_pos, 0);
assert!(ec.reference_buffer.iter().all(|&s| s == 0.0));
}
#[test]
fn disabled_skips_reference_and_processing() {
let config = EchoCancellerConfig {
enabled: false,
..Default::default()
};
let mut ec = EchoCanceller::new(config);
ec.feed_reference(&[1.0; 100]);
assert!(ec.reference_buffer.iter().all(|&s| s == 0.0));
let input = vec![0.1, 0.2, 0.3];
let output = ec.process(&input);
assert_eq!(input, output);
assert_eq!(ec.frames_processed(), 1);
}
#[test]
fn config_defaults_are_correct() {
let config = EchoCancellerConfig::default();
assert!(config.enabled);
assert_eq!(config.tail_length_ms, 128);
assert!((config.suppression_level - 0.8).abs() < f32::EPSILON);
}
}