//! DC offset removal: compute mean amplitude and subtract it from all samples. /// Remove DC offset from interleaved audio samples. /// /// Computes the mean sample value (the DC component) and subtracts it, /// centering the waveform around zero. Operates per-channel to handle /// stereo files where channels may have different offsets. pub fn apply_remove_dc_offset(samples: &mut [f32], channels: u16) { if samples.is_empty() || channels == 0 { return; } let ch = channels as usize; let num_frames = samples.len() / ch; if num_frames == 0 { return; } // Compute per-channel mean let mut sums = vec![0.0f64; ch]; for frame in 0..num_frames { for c in 0..ch { sums[c] += samples[frame * ch + c] as f64; } } let means: Vec = sums.iter().map(|s| (*s / num_frames as f64) as f32).collect(); // Subtract per-channel mean for frame in 0..num_frames { for c in 0..ch { samples[frame * ch + c] -= means[c]; } } } #[cfg(test)] mod tests { use super::*; #[test] fn removes_dc_offset_mono() { let mut samples = vec![0.5, 0.6, 0.4, 0.5]; // mean = 0.5 apply_remove_dc_offset(&mut samples, 1); let mean: f32 = samples.iter().sum::() / samples.len() as f32; assert!(mean.abs() < 1e-6, "mean should be ~0, got {mean}"); assert!((samples[0] - 0.0).abs() < 1e-6); assert!((samples[1] - 0.1).abs() < 1e-6); } #[test] fn removes_dc_offset_stereo() { // L channel: mean 0.5, R channel: mean -0.5 let mut samples = vec![0.5, -0.5, 0.5, -0.5]; apply_remove_dc_offset(&mut samples, 2); // After removal, both channels centered at 0 assert!((samples[0]).abs() < 1e-6); assert!((samples[1]).abs() < 1e-6); } #[test] fn noop_on_already_centered() { let original = vec![0.1, -0.1, 0.2, -0.2]; let mut samples = original.clone(); apply_remove_dc_offset(&mut samples, 1); for (a, b) in samples.iter().zip(original.iter()) { assert!((a - b).abs() < 1e-6); } } #[test] fn empty_input() { let mut samples: Vec = vec![]; apply_remove_dc_offset(&mut samples, 1); assert!(samples.is_empty()); } }