58 lines
2.7 KiB
Rust
58 lines
2.7 KiB
Rust
#![deny(warnings)]
|
|
#![allow(dead_code)]
|
|
//! [TSM.ID].[11031972] -- Platform X Ecosystem
|
|
//! xcu-noise-cancellation -- Spectral Subtraction Noise Reducer
|
|
use std::f64::consts::PI;
|
|
#[derive(Debug)] pub enum NoiseError { InvalidInput(String) }
|
|
impl std::fmt::Display for NoiseError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::InvalidInput(e) => write!(f, "{e}") } } }
|
|
impl std::error::Error for NoiseError {}
|
|
pub fn dft(signal: &[f64]) -> Vec<(f64, f64)> {
|
|
let n = signal.len();
|
|
(0..n).map(|k| {
|
|
let (mut re, mut im) = (0.0, 0.0);
|
|
for (t, &x) in signal.iter().enumerate() { let angle = 2.0 * PI * k as f64 * t as f64 / n as f64; re += x * angle.cos(); im -= x * angle.sin(); }
|
|
(re, im)
|
|
}).collect()
|
|
}
|
|
pub fn idft(spectrum: &[(f64, f64)]) -> Vec<f64> {
|
|
let n = spectrum.len();
|
|
(0..n).map(|t| {
|
|
let mut sum = 0.0;
|
|
for (k, &(re, im)) in spectrum.iter().enumerate() { let angle = 2.0 * PI * k as f64 * t as f64 / n as f64; sum += re * angle.cos() - im * angle.sin(); }
|
|
sum / n as f64
|
|
}).collect()
|
|
}
|
|
pub struct NoiseProfile { pub magnitude: Vec<f64> }
|
|
impl NoiseProfile {
|
|
pub fn estimate(noise_frames: &[Vec<f64>]) -> Result<Self, NoiseError> {
|
|
if noise_frames.is_empty() { return Err(NoiseError::InvalidInput("Empty".into())); }
|
|
let n = noise_frames[0].len();
|
|
let mut avg_mag = vec![0.0; n];
|
|
for frame in noise_frames {
|
|
let spec = dft(frame);
|
|
for (i, (re, im)) in spec.iter().enumerate() { avg_mag[i] += (re*re + im*im).sqrt(); }
|
|
}
|
|
for m in avg_mag.iter_mut() { *m /= noise_frames.len() as f64; }
|
|
Ok(Self { magnitude: avg_mag })
|
|
}
|
|
}
|
|
pub struct SpectralSubtractor { pub gain_floor: f64 }
|
|
impl SpectralSubtractor {
|
|
pub fn new(gain_floor: f64) -> Self { Self { gain_floor } }
|
|
pub fn process(&self, signal: &[f64], noise: &NoiseProfile) -> Vec<f64> {
|
|
let spec = dft(signal);
|
|
let cleaned: Vec<(f64, f64)> = spec.iter().enumerate().map(|(i, &(re, im))| {
|
|
let mag = (re*re + im*im).sqrt();
|
|
let noise_mag = if i < noise.magnitude.len() { noise.magnitude[i] } else { 0.0 };
|
|
let gain = if mag > noise_mag { (mag - noise_mag) / mag } else { self.gain_floor };
|
|
(re * gain, im * gain)
|
|
}).collect();
|
|
idft(&cleaned)
|
|
}
|
|
}
|
|
#[cfg(test)] mod tests {
|
|
use super::*;
|
|
#[test] fn test_dft_idft() { let sig = vec![1.0, 0.0, -1.0, 0.0]; let spec = dft(&sig); let rec = idft(&spec); for (a, b) in sig.iter().zip(rec.iter()) { assert!((a - b).abs() < 1e-10); } }
|
|
#[test] fn test_noise_profile() { let frames = vec![vec![0.1, -0.1, 0.05, -0.05]]; let p = NoiseProfile::estimate(&frames).unwrap(); assert_eq!(p.magnitude.len(), 4); }
|
|
}
|