[TSM.ID].[11031972] PXE : Platform X Ecosystem I [144 Module] +25 Missing Matrix Modules

This commit is contained in:
TSM.ID
2026-05-25 06:08:39 +07:00
parent 1b367871f0
commit 4e0d00b4bd
52 changed files with 1969 additions and 3 deletions
+8
View File
@@ -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]
+57
View File
@@ -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); }
}