use crate::fundamental_hz;
pub fn dewow(signal: &[f32], sample_rate: u32) -> Vec<f32> {
let n = signal.len();
if n < 2048 || sample_rate == 0 {
return signal.to_vec();
}
let sr = sample_rate as f32;
let f0 = match fundamental_hz(signal, sample_rate) {
Some(f) if f >= 20.0 && f < sr * 0.4 => f,
_ => return signal.to_vec(),
};
let two_pi = 2.0 * std::f32::consts::PI;
let w = two_pi * f0 / sr;
let mut re = vec![0.0f32; n];
let mut im = vec![0.0f32; n];
let mut phase = 0.0f64;
let wd = w as f64;
for i in 0..n {
let (s, c) = phase.sin_cos();
re[i] = signal[i] * c as f32;
im[i] = -signal[i] * s as f32;
phase += wd;
if phase > std::f64::consts::TAU {
phase -= std::f64::consts::TAU;
}
}
let baseband_fc = (f0 * 0.3).clamp(10.0, 60.0);
let lp_b = 1.0 - (-two_pi * baseband_fc / sr).exp();
lpf_zero_phase(&mut re, lp_b);
lpf_zero_phase(&mut im, lp_b);
let mut speed = vec![1.0f32; n];
for i in 1..n {
let dr = re[i] * re[i - 1] + im[i] * im[i - 1];
let di = im[i] * re[i - 1] - re[i] * im[i - 1];
let dphi = di.atan2(dr); let inst = f0 + dphi * sr / two_pi;
speed[i] = (inst / f0).clamp(0.8, 1.2);
}
speed[0] = speed[1];
let sp_fc = 30.0f32;
let lp_s = 1.0 - (-two_pi * sp_fc / sr).exp();
lpf_zero_phase(&mut speed, lp_s);
let mean = speed.iter().sum::<f32>() / n as f32;
if mean.abs() < 1e-6 {
return signal.to_vec();
}
for s in &mut speed {
*s /= mean;
}
let mut phi = vec![0.0f32; n + 1];
for i in 0..n {
phi[i + 1] = phi[i] + speed[i];
}
let m_max = phi[n].floor() as usize;
let mut out = Vec::with_capacity(m_max);
let mut i = 0usize;
for m in 0..m_max {
let u = m as f32;
while i + 1 < n && phi[i + 1] <= u {
i += 1;
}
let t = i as f32 + (u - phi[i]) / speed[i].max(1e-6);
let j = t.floor() as usize;
let frac = t - j as f32;
let a = signal[j.min(n - 1)];
let b = signal[(j + 1).min(n - 1)];
out.push(a * (1.0 - frac) + b * frac);
}
out
}
fn lpf_zero_phase(x: &mut [f32], lam: f32) {
if x.is_empty() {
return;
}
let mut y = x[0];
for v in x.iter_mut() {
y += lam * (*v - y);
*v = y;
}
y = *x.last().unwrap();
for v in x.iter_mut().rev() {
y += lam * (*v - y);
*v = y;
}
}
#[cfg(test)]
mod tests {
use super::*;
fn mag_at(x: &[f32], f: f32, sr: u32) -> f64 {
let two_pi = 2.0 * std::f64::consts::PI;
let (mut re, mut im) = (0.0f64, 0.0f64);
for (i, &v) in x.iter().enumerate() {
let p = two_pi * f as f64 * i as f64 / sr as f64;
re += v as f64 * p.cos();
im -= v as f64 * p.sin();
}
(re * re + im * im).sqrt() / x.len() as f64
}
#[test]
fn flattens_wowed_tone() {
let sr = 48_000u32;
let n = sr as usize * 2;
let f0 = 440.0f32;
let two_pi = 2.0 * std::f32::consts::PI;
let mut phase = 0.0f32;
let mut x = vec![0.0f32; n];
for (i, xi) in x.iter_mut().enumerate() {
let t = i as f32 / sr as f32;
let speed = 1.0 + 0.03 * (two_pi * 3.0 * t).sin();
phase += two_pi * f0 * speed / sr as f32;
*xi = phase.sin();
}
let out = dewow(&x, sr);
let before = mag_at(&x, f0, sr);
let after = mag_at(&out, f0, sr);
assert!(after > before * 1.3, "carrier not restored: {before} -> {after}");
}
#[test]
fn clean_tone_survives() {
let sr = 48_000u32;
let n = sr as usize;
let f0 = 440.0f32;
let x: Vec<f32> = (0..n)
.map(|i| (2.0 * std::f32::consts::PI * f0 * i as f32 / sr as f32).sin())
.collect();
let out = dewow(&x, sr);
let before = mag_at(&x, f0, sr);
let after = mag_at(&out, f0, sr);
assert!(after > before * 0.8, "clean tone degraded: {before} -> {after}");
}
}