[TSM.ID].[11031972] PXE : Platform X Ecosystem I [144 Module] +25 Missing Matrix Modules
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
[package]
|
||||
name = "xcu-echo-killer"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
authors = ["TSM.ID <tsm@tsm.id>"]
|
||||
description = "[TSM.ID].[11031972] Acoustic Echo Cancellation NLMS"
|
||||
|
||||
[dependencies]
|
||||
@@ -0,0 +1,57 @@
|
||||
#![deny(warnings)]
|
||||
#![allow(dead_code)]
|
||||
//! [TSM.ID].[11031972] -- Platform X Ecosystem
|
||||
//! xcu-echo-killer -- Acoustic Echo Cancellation (NLMS Adaptive Filter)
|
||||
#[derive(Debug)] pub enum EchoError { InvalidConfig(String) }
|
||||
impl std::fmt::Display for EchoError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::InvalidConfig(e) => write!(f, "{e}") } } }
|
||||
impl std::error::Error for EchoError {}
|
||||
pub struct NlmsFilter { weights: Vec<f64>, mu: f64 }
|
||||
impl NlmsFilter {
|
||||
pub fn new(taps: usize, step_size: f64) -> Result<Self, EchoError> {
|
||||
if taps == 0 { return Err(EchoError::InvalidConfig("taps=0".into())); }
|
||||
Ok(Self { weights: vec![0.0; taps], mu: step_size })
|
||||
}
|
||||
pub fn predict(&self, x: &[f64]) -> f64 {
|
||||
self.weights.iter().zip(x.iter()).map(|(w, xi)| w * xi).sum()
|
||||
}
|
||||
pub fn update(&mut self, x: &[f64], error: f64) {
|
||||
let power: f64 = x.iter().map(|xi| xi * xi).sum::<f64>() + 1e-10;
|
||||
let norm_step = self.mu / power;
|
||||
for (w, xi) in self.weights.iter_mut().zip(x.iter()) { *w += norm_step * error * xi; }
|
||||
}
|
||||
}
|
||||
pub struct EchoCanceller { filter: NlmsFilter, buffer: Vec<f64> }
|
||||
impl EchoCanceller {
|
||||
pub fn new(filter_length: usize, step_size: f64) -> Result<Self, EchoError> {
|
||||
Ok(Self { filter: NlmsFilter::new(filter_length, step_size)?, buffer: vec![0.0; filter_length] })
|
||||
}
|
||||
pub fn process_sample(&mut self, far_end: f64, near_end: f64) -> f64 {
|
||||
self.buffer.insert(0, far_end);
|
||||
self.buffer.truncate(self.filter.weights.len());
|
||||
let echo_estimate = self.filter.predict(&self.buffer);
|
||||
let error = near_end - echo_estimate;
|
||||
self.filter.update(&self.buffer, error);
|
||||
error // output = near_end minus estimated echo
|
||||
}
|
||||
pub fn process_block(&mut self, far_end: &[f64], near_end: &[f64]) -> Vec<f64> {
|
||||
far_end.iter().zip(near_end.iter()).map(|(&f, &n)| self.process_sample(f, n)).collect()
|
||||
}
|
||||
}
|
||||
pub fn cross_correlate(a: &[f64], b: &[f64], max_lag: usize) -> usize {
|
||||
let mut best_lag = 0; let mut best_corr = f64::MIN;
|
||||
for lag in 0..max_lag.min(a.len()) {
|
||||
let corr: f64 = a.iter().skip(lag).zip(b.iter()).map(|(x, y)| x * y).sum();
|
||||
if corr > best_corr { best_corr = corr; best_lag = lag; }
|
||||
}
|
||||
best_lag
|
||||
}
|
||||
#[cfg(test)] mod tests {
|
||||
use super::*;
|
||||
#[test] fn test_nlms_convergence() {
|
||||
let mut aec = EchoCanceller::new(8, 0.5).unwrap();
|
||||
for i in 0..100 { let far = (i as f64 * 0.1).sin(); let echo = far * 0.6; let out = aec.process_sample(far, echo); let _ = out; }
|
||||
let far = 1.0; let echo = far * 0.6; let residual = aec.process_sample(far, echo);
|
||||
assert!(residual.abs() < 0.5); // echo should be reduced
|
||||
}
|
||||
#[test] fn test_cross_corr() { let a = vec![0.0, 0.0, 1.0, 0.0]; let b = vec![1.0, 0.0, 0.0, 0.0]; let lag = cross_correlate(&a, &b, 4); assert_eq!(lag, 2); }
|
||||
}
|
||||
Reference in New Issue
Block a user