use crate::{AnalysisConfig, Result};
pub struct NoiseAnalyzer {
config: AnalysisConfig,
}
impl NoiseAnalyzer {
#[must_use]
pub fn new(config: AnalysisConfig) -> Self {
Self { config }
}
pub fn analyze_consistency(
&self,
samples: &[f32],
_sample_rate: f32,
) -> Result<NoiseConsistency> {
let segment_size = self.config.fft_size * 10;
let num_segments = samples.len() / segment_size;
if num_segments < 2 {
return Ok(NoiseConsistency {
is_consistent: true,
noise_floor_variation: 0.0,
});
}
let mut noise_floors = Vec::new();
for i in 0..num_segments {
let start = i * segment_size;
let end = ((i + 1) * segment_size).min(samples.len());
let segment = &samples[start..end];
let noise_floor = self.estimate_noise_floor(segment);
noise_floors.push(noise_floor);
}
let mean = noise_floors.iter().sum::<f32>() / noise_floors.len() as f32;
let variance = noise_floors
.iter()
.map(|&x| (x - mean).powi(2))
.sum::<f32>()
/ noise_floors.len() as f32;
let std_dev = variance.sqrt();
let coefficient_of_variation = if mean > 0.0 { std_dev / mean } else { 0.0 };
let is_consistent = coefficient_of_variation < 0.5;
Ok(NoiseConsistency {
is_consistent,
noise_floor_variation: coefficient_of_variation,
})
}
#[allow(clippy::unused_self)]
fn estimate_noise_floor(&self, samples: &[f32]) -> f32 {
if samples.is_empty() {
return 0.0;
}
let mut amplitudes: Vec<f32> = samples.iter().map(|&x| x.abs()).collect();
amplitudes.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
let percentile_idx = (amplitudes.len() as f32 * 0.1) as usize;
amplitudes[percentile_idx]
}
}
#[derive(Debug, Clone)]
pub struct NoiseConsistency {
pub is_consistent: bool,
pub noise_floor_variation: f32,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_noise_analyzer() {
let config = AnalysisConfig::default();
let analyzer = NoiseAnalyzer::new(config);
let mut samples = Vec::new();
for _ in 0..100000 {
samples.push(0.01); }
let result = analyzer.analyze_consistency(&samples, 44100.0);
assert!(result.is_ok());
let consistency = result.expect("expected successful result");
assert!(consistency.is_consistent);
}
#[test]
fn test_noise_floor_estimation() {
let config = AnalysisConfig::default();
let analyzer = NoiseAnalyzer::new(config);
let samples = vec![0.01, 0.02, 0.01, 0.5, 0.8, 0.01]; let floor = analyzer.estimate_noise_floor(&samples);
assert!(floor < 0.1); }
}