pub const DC_OFFSET_THRESHOLD: f32 = 0.01;
pub fn measure(samples: &[i16]) -> f32 {
if samples.is_empty() {
return 0.0;
}
let sum: f64 = samples.iter().map(|&s| s as f64).sum();
(sum / samples.len() as f64 / i16::MAX as f64) as f32
}
pub fn has_offset(samples: &[i16]) -> bool {
measure(samples).abs() > DC_OFFSET_THRESHOLD
}
pub fn remove(samples: &mut [i16]) {
if samples.is_empty() {
return;
}
let mean: f64 = samples.iter().map(|&s| s as f64).sum::<f64>() / samples.len() as f64;
for s in samples.iter_mut() {
let corrected = (*s as f64 - mean)
.round()
.clamp(i16::MIN as f64, i16::MAX as f64);
*s = corrected as i16;
}
}
#[cfg(test)]
mod tests {
use super::*;
fn sine(amplitude: f32, offset: i16, n: usize) -> Vec<i16> {
(0..n)
.map(|i| {
let v = amplitude * (2.0 * std::f32::consts::PI * 440.0 * i as f32 / 24000.0).sin();
(v as i16).saturating_add(offset)
})
.collect()
}
#[test]
fn zero_offset_sine_not_flagged() {
let samples = sine(1000.0, 0, 24000);
assert!(measure(&samples).abs() < DC_OFFSET_THRESHOLD);
assert!(!has_offset(&samples));
}
#[test]
fn large_offset_detected() {
let samples = sine(500.0, 1000, 24000);
assert!(has_offset(&samples));
}
#[test]
fn removal_brings_offset_to_near_zero() {
let mut samples = sine(500.0, 1000, 24000);
remove(&mut samples);
assert!(
measure(&samples).abs() < DC_OFFSET_THRESHOLD,
"After removal, offset is {:.4}",
measure(&samples)
);
}
#[test]
fn constant_dc_signal_fully_removed() {
let mut samples = vec![500i16; 1000];
remove(&mut samples);
for &s in &samples {
assert!(s.abs() <= 1, "Expected ~0 after removal, got {}", s);
}
}
#[test]
fn empty_slice_is_safe() {
let mut empty: Vec<i16> = vec![];
assert_eq!(measure(&empty), 0.0);
remove(&mut empty);
}
#[test]
fn negative_offset_detected_and_removed() {
let mut samples = sine(500.0, -1200, 24000);
assert!(has_offset(&samples));
remove(&mut samples);
assert!(!has_offset(&samples));
}
}