clawft_plugin/voice/
echo.rs1use serde::{Deserialize, Serialize};
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct EchoCancellerConfig {
14 #[serde(default = "default_true")]
16 pub enabled: bool,
17 #[serde(default = "default_tail_ms")]
20 pub tail_length_ms: u32,
21 #[serde(default = "default_suppression")]
23 pub suppression_level: f32,
24}
25
26fn default_true() -> bool {
27 true
28}
29fn default_tail_ms() -> u32 {
30 128
31}
32fn default_suppression() -> f32 {
33 0.8
34}
35
36impl Default for EchoCancellerConfig {
37 fn default() -> Self {
38 Self {
39 enabled: true,
40 tail_length_ms: 128,
41 suppression_level: 0.8,
42 }
43 }
44}
45
46pub struct EchoCanceller {
48 config: EchoCancellerConfig,
49 reference_buffer: Vec<f32>,
51 write_pos: usize,
53 frames_processed: u64,
55}
56
57impl EchoCanceller {
58 pub fn new(config: EchoCancellerConfig) -> Self {
60 let buffer_size = (config.tail_length_ms as usize * 16000) / 1000;
62 Self {
63 config,
64 reference_buffer: vec![0.0; buffer_size],
65 write_pos: 0,
66 frames_processed: 0,
67 }
68 }
69
70 pub fn feed_reference(&mut self, samples: &[f32]) {
72 if !self.config.enabled {
73 return;
74 }
75 for &sample in samples {
76 self.reference_buffer[self.write_pos] = sample;
77 self.write_pos = (self.write_pos + 1) % self.reference_buffer.len();
78 }
79 }
80
81 pub fn process(&mut self, input: &[f32]) -> Vec<f32> {
84 self.frames_processed += 1;
85 if !self.config.enabled {
86 return input.to_vec();
87 }
88 input.to_vec()
91 }
92
93 pub fn reset(&mut self) {
95 self.reference_buffer.fill(0.0);
96 self.write_pos = 0;
97 self.frames_processed = 0;
98 }
99
100 pub fn frames_processed(&self) -> u64 {
102 self.frames_processed
103 }
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109
110 #[test]
111 fn new_creates_with_defaults() {
112 let ec = EchoCanceller::new(EchoCancellerConfig::default());
113 assert_eq!(ec.frames_processed(), 0);
114 assert_eq!(ec.reference_buffer.len(), 2048);
116 }
117
118 #[test]
119 fn process_passthrough_preserves_input() {
120 let mut ec = EchoCanceller::new(EchoCancellerConfig::default());
121 let input = vec![0.1, 0.2, -0.3, 0.4, -0.5];
122 let output = ec.process(&input);
123 assert_eq!(input, output);
124 assert_eq!(ec.frames_processed(), 1);
125 }
126
127 #[test]
128 fn feed_reference_does_not_panic() {
129 let mut ec = EchoCanceller::new(EchoCancellerConfig::default());
130 let reference = vec![0.5; 4096]; ec.feed_reference(&reference);
132 assert!(ec.write_pos < ec.reference_buffer.len());
134 }
135
136 #[test]
137 fn reset_clears_state() {
138 let mut ec = EchoCanceller::new(EchoCancellerConfig::default());
139 ec.feed_reference(&[1.0; 100]);
140 ec.process(&[0.5; 10]);
141 ec.process(&[0.5; 10]);
142 assert_eq!(ec.frames_processed(), 2);
143
144 ec.reset();
145 assert_eq!(ec.frames_processed(), 0);
146 assert_eq!(ec.write_pos, 0);
147 assert!(ec.reference_buffer.iter().all(|&s| s == 0.0));
148 }
149
150 #[test]
151 fn disabled_skips_reference_and_processing() {
152 let config = EchoCancellerConfig {
153 enabled: false,
154 ..Default::default()
155 };
156 let mut ec = EchoCanceller::new(config);
157
158 ec.feed_reference(&[1.0; 100]);
160 assert!(ec.reference_buffer.iter().all(|&s| s == 0.0));
161
162 let input = vec![0.1, 0.2, 0.3];
164 let output = ec.process(&input);
165 assert_eq!(input, output);
166 assert_eq!(ec.frames_processed(), 1);
167 }
168
169 #[test]
170 fn config_defaults_are_correct() {
171 let config = EchoCancellerConfig::default();
172 assert!(config.enabled);
173 assert_eq!(config.tail_length_ms, 128);
174 assert!((config.suppression_level - 0.8).abs() < f32::EPSILON);
175 }
176}