use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NoiseSuppressorConfig {
#[serde(default = "default_true")]
pub enabled: bool,
#[serde(default = "default_aggressiveness")]
pub aggressiveness: u8,
}
fn default_true() -> bool {
true
}
fn default_aggressiveness() -> u8 {
2
}
impl Default for NoiseSuppressorConfig {
fn default() -> Self {
Self {
enabled: true,
aggressiveness: 2,
}
}
}
pub struct NoiseSuppressor {
config: NoiseSuppressorConfig,
noise_floor: f32,
frames_processed: u64,
}
impl NoiseSuppressor {
pub fn new(config: NoiseSuppressorConfig) -> Self {
Self {
config,
noise_floor: 0.0,
frames_processed: 0,
}
}
pub fn process(&mut self, input: &[f32]) -> Vec<f32> {
self.frames_processed += 1;
if !self.config.enabled {
return input.to_vec();
}
if !input.is_empty() {
let rms = (input.iter().map(|s| s * s).sum::<f32>()
/ input.len() as f32)
.sqrt();
self.noise_floor = 0.95 * self.noise_floor + 0.05 * rms;
}
input.to_vec()
}
pub fn noise_floor(&self) -> f32 {
self.noise_floor
}
pub fn frames_processed(&self) -> u64 {
self.frames_processed
}
pub fn reset(&mut self) {
self.noise_floor = 0.0;
self.frames_processed = 0;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_creates_with_defaults() {
let ns = NoiseSuppressor::new(NoiseSuppressorConfig::default());
assert_eq!(ns.frames_processed(), 0);
assert!((ns.noise_floor() - 0.0).abs() < f32::EPSILON);
}
#[test]
fn process_passthrough_preserves_input() {
let mut ns = NoiseSuppressor::new(NoiseSuppressorConfig::default());
let input = vec![0.1, 0.2, -0.3, 0.4, -0.5];
let output = ns.process(&input);
assert_eq!(input, output);
assert_eq!(ns.frames_processed(), 1);
}
#[test]
fn noise_floor_updates_on_process() {
let mut ns = NoiseSuppressor::new(NoiseSuppressorConfig::default());
let input = vec![0.5; 160]; ns.process(&input);
let floor = ns.noise_floor();
assert!(floor > 0.0, "Noise floor should be positive after processing");
assert!(floor < 0.5, "Noise floor should not have reached signal level yet");
for _ in 0..200 {
ns.process(&input);
}
let floor = ns.noise_floor();
assert!(
(floor - 0.5).abs() < 0.05,
"Noise floor should converge near 0.5, got {}",
floor
);
}
#[test]
fn disabled_passthrough_no_noise_floor_update() {
let config = NoiseSuppressorConfig {
enabled: false,
..Default::default()
};
let mut ns = NoiseSuppressor::new(config);
let input = vec![0.5; 160];
let output = ns.process(&input);
assert_eq!(input, output);
assert!((ns.noise_floor() - 0.0).abs() < f32::EPSILON);
assert_eq!(ns.frames_processed(), 1);
}
#[test]
fn reset_clears_state() {
let mut ns = NoiseSuppressor::new(NoiseSuppressorConfig::default());
ns.process(&[0.5; 160]);
ns.process(&[0.5; 160]);
assert!(ns.noise_floor() > 0.0);
assert_eq!(ns.frames_processed(), 2);
ns.reset();
assert!((ns.noise_floor() - 0.0).abs() < f32::EPSILON);
assert_eq!(ns.frames_processed(), 0);
}
#[test]
fn config_defaults_are_correct() {
let config = NoiseSuppressorConfig::default();
assert!(config.enabled);
assert_eq!(config.aggressiveness, 2);
}
}