use crate::util::hann_window;
use realfft::RealFftPlanner;
pub fn dehum(signal: &[f32], sample_rate: u32, base_freq: f32, num_harmonics: usize) -> Vec<f32> {
let mut output = signal.to_vec();
for h in 1..=num_harmonics {
let freq = base_freq * h as f32;
if freq >= sample_rate as f32 * 0.45 {
break;
}
notch_filter(&mut output, freq, sample_rate, 30.0);
}
output
}
fn notch_filter(signal: &mut [f32], freq: f32, sample_rate: u32, q: f32) {
let w0 = 2.0 * std::f32::consts::PI * freq / sample_rate as f32;
let alpha = w0.sin() / (2.0 * q);
let b0 = 1.0;
let b1 = -2.0 * w0.cos();
let b2 = 1.0;
let a0 = 1.0 + alpha;
let a1 = -2.0 * w0.cos();
let a2 = 1.0 - alpha;
let b0 = b0 / a0;
let b1 = b1 / a0;
let b2 = b2 / a0;
let a1 = a1 / a0;
let a2 = a2 / a0;
let (mut x1, mut x2, mut y1, mut y2) = (0.0f32, 0.0f32, 0.0f32, 0.0f32);
for s in signal.iter_mut() {
let x0 = *s;
let y0 = b0 * x0 + b1 * x1 + b2 * x2 - a1 * y1 - a2 * y2;
x2 = x1;
x1 = x0;
y2 = y1;
y1 = y0;
*s = y0;
}
}
fn highpass_biquad(signal: &mut [f32], freq: f32, sample_rate: u32, q: f32) {
let w0 = 2.0 * std::f32::consts::PI * freq / sample_rate as f32;
let cos = w0.cos();
let alpha = w0.sin() / (2.0 * q);
let a0 = 1.0 + alpha;
let b0 = ((1.0 + cos) / 2.0) / a0;
let b1 = (-(1.0 + cos)) / a0;
let b2 = ((1.0 + cos) / 2.0) / a0;
let a1 = (-2.0 * cos) / a0;
let a2 = (1.0 - alpha) / a0;
let (mut x1, mut x2, mut y1, mut y2) = (0.0f32, 0.0f32, 0.0f32, 0.0f32);
for s in signal.iter_mut() {
let x0 = *s;
let y0 = b0 * x0 + b1 * x1 + b2 * x2 - a1 * y1 - a2 * y2;
x2 = x1;
x1 = x0;
y2 = y1;
y1 = y0;
*s = y0;
}
}
pub fn dewind(signal: &[f32], sample_rate: u32, cutoff_hz: f32) -> Vec<f32> {
let mut out = signal.to_vec();
for q in [0.541_196_1, 1.306_563] {
highpass_biquad(&mut out, cutoff_hz, sample_rate, q);
}
out
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum DeclickMethod {
#[default]
Ar,
Cubic,
}
pub fn declick(signal: &[f32], threshold: f32, window: usize) -> Vec<f32> {
declick_with_method(signal, threshold, window, DeclickMethod::default())
}
pub fn declick_with_method(
signal: &[f32],
threshold: f32,
window: usize,
method: DeclickMethod,
) -> Vec<f32> {
let n = signal.len();
let half = window / 2;
let mut output = signal.to_vec();
if half == 0 || n <= window {
return output;
}
let rms = local_rms(signal, window);
let mut i = half;
while i + half < n {
if signal[i].abs() > threshold * rms[i] {
let mut start = i;
while start > half && signal[start - 1].abs() > threshold * rms[start - 1] {
start -= 1;
}
let mut end = i + 1;
while end + half < n && signal[end].abs() > threshold * rms[end] {
end += 1;
}
let pad = half.clamp(2, 8);
let gap_start = start.saturating_sub(pad);
let gap_end = (end + pad).min(n);
let gap_len = gap_end.saturating_sub(gap_start);
if gap_len >= 2 && gap_start > 0 && gap_end < n {
match method {
DeclickMethod::Ar => {
output = crate::inpaint::inpaint_gap(&output, gap_start, gap_len, 3);
}
DeclickMethod::Cubic => {
cubic_interpolate(&mut output, gap_start, gap_end - 1);
}
}
} else if gap_end > gap_start + 2 {
cubic_interpolate(&mut output, gap_start, (gap_end - 1).min(n - 1));
}
i = end.max(i + 1) + half.saturating_sub(1);
continue;
}
i += 1;
}
output
}
fn local_rms(signal: &[f32], window: usize) -> Vec<f32> {
let n = signal.len();
let half = window / 2;
let mut rms = vec![0.0f32; n];
let mut sum_sq = 0.0f32;
let mut count = 0usize;
for s in signal.iter().take(half.min(n)) {
sum_sq += s * s;
count += 1;
}
for i in 0..n {
if i >= half {
let out = i - half;
sum_sq -= signal[out] * signal[out];
count -= 1;
}
if i + half < n {
sum_sq += signal[i + half] * signal[i + half];
count += 1;
}
rms[i] = (sum_sq / count as f32).sqrt().max(1e-10);
}
rms
}
fn cubic_interpolate(signal: &mut [f32], start: usize, end: usize) {
if end - start < 4 {
return;
}
let y0 = signal[start];
let y1 = signal[end];
let len = (end - start) as f32;
for (i, s) in signal.iter_mut().enumerate().skip(start + 1).take(end - start - 1) {
let t = (i - start) as f32 / len;
let t2 = t * t;
let t3 = t2 * t;
*s = y0 * (1.0 - 3.0 * t2 + 2.0 * t3) + y1 * (3.0 * t2 - 2.0 * t3);
}
}
pub fn dereverb(signal: &[f32], sample_rate: u32, strength: f32) -> Vec<f32> {
let fft_size = 2048;
let hop_size = 512;
let n = signal.len();
if n < fft_size {
return signal.to_vec();
}
let mut planner = RealFftPlanner::<f32>::new();
let r2c = planner.plan_fft_forward(fft_size);
let c2r = planner.plan_fft_inverse(fft_size);
let hann = hann_window(fft_size);
let scale = 1.0f32 / (fft_size as f32);
let n_bins = fft_size / 2 + 1;
let frames = n / hop_size;
let attack_coeff = (-2.0f32 / (sample_rate as f32 * 0.008)).exp(); let release_coeff = (-2.0f32 / (sample_rate as f32 * 0.050)).exp();
let mut env = vec![0.0f32; n_bins];
let mut in_buf = r2c.make_input_vec();
let mut out_buf = r2c.make_output_vec();
let mut reverb_floor = vec![f32::MAX; n_bins];
for fi in 0..frames {
let offset = fi * hop_size;
if offset + fft_size > n {
break;
}
for i in 0..fft_size {
in_buf[i] = signal[offset + i] * hann[i];
}
r2c.process(&mut in_buf, &mut out_buf).unwrap();
for k in 0..n_bins {
let mag = (out_buf[k].re * out_buf[k].re + out_buf[k].im * out_buf[k].im).sqrt();
if mag > env[k] {
env[k] = attack_coeff * env[k] + (1.0 - attack_coeff) * mag;
} else {
env[k] = release_coeff * env[k] + (1.0 - release_coeff) * mag;
}
reverb_floor[k] = reverb_floor[k].min(env[k]);
}
}
for item in reverb_floor.iter_mut().take(n_bins) {
*item *= 1.5;
}
let threshold_db = strength * 6.0;
let threshold_linear = 10.0f32.powf(threshold_db / 20.0);
env.fill(0.0);
let mut output = vec![0.0f32; n + fft_size];
let mut in_buf2 = r2c.make_input_vec();
let mut out_buf2 = r2c.make_output_vec();
for fi in 0..frames {
let offset = fi * hop_size;
if offset + fft_size > n {
break;
}
for i in 0..fft_size {
in_buf2[i] = signal[offset + i] * hann[i];
}
r2c.process(&mut in_buf2, &mut out_buf2).unwrap();
for k in 0..n_bins {
let mag = (out_buf2[k].re * out_buf2[k].re + out_buf2[k].im * out_buf2[k].im).sqrt();
if mag > env[k] {
env[k] = attack_coeff * env[k] + (1.0 - attack_coeff) * mag;
} else {
env[k] = release_coeff * env[k] + (1.0 - release_coeff) * mag;
}
let ratio = env[k] / reverb_floor[k].max(1e-10);
let gate_gain = if ratio < threshold_linear {
(ratio / threshold_linear).powf(2.0).max(0.01)
} else {
1.0
};
out_buf2[k].re *= gate_gain;
out_buf2[k].im *= gate_gain;
}
c2r.process(&mut out_buf2, &mut in_buf2).unwrap();
for i in 0..fft_size {
output[offset + i] += in_buf2[i] * hann[i] * scale;
}
}
output.truncate(n);
output
}
pub fn spectral_repair(signal: &[f32], strength: f32) -> Vec<f32> {
let fft_size = 2048;
let hop_size = 512;
let n = signal.len();
if n < fft_size {
return signal.to_vec();
}
let mut planner = RealFftPlanner::<f32>::new();
let r2c = planner.plan_fft_forward(fft_size);
let c2r = planner.plan_fft_inverse(fft_size);
let hann = hann_window(fft_size);
let scale = 1.0f32 / fft_size as f32;
let n_bins = fft_size / 2 + 1;
let mut spectra = Vec::new();
let mut in_buf = r2c.make_input_vec();
let mut out_buf = r2c.make_output_vec();
let mut offset = 0;
while offset + fft_size <= n {
for i in 0..fft_size {
in_buf[i] = signal[offset + i] * hann[i];
}
r2c.process(&mut in_buf, &mut out_buf).unwrap();
spectra.push(out_buf.clone());
offset += hop_size;
}
let frames = spectra.len();
if frames == 0 {
return signal.to_vec();
}
let mags: Vec<Vec<f32>> = spectra
.iter()
.map(|fr| fr.iter().map(|c| (c.re * c.re + c.im * c.im).sqrt()).collect())
.collect();
let ratio = 1.0 + 8.0 / strength.max(0.1);
const T: usize = 4; for k in 0..n_bins {
for t in 0..frames {
let mag = mags[t][k];
let lo = t.saturating_sub(T);
let hi = (t + T).min(frames - 1);
let mut nb: Vec<f32> = (lo..=hi).filter(|&s| s != t).map(|s| mags[s][k]).collect();
if nb.is_empty() {
continue;
}
nb.sort_by(|a, b| a.partial_cmp(b).unwrap());
let med = nb[nb.len() / 2];
if mag > ratio * med.max(1e-9) {
let g = med / mag;
spectra[t][k].re *= g;
spectra[t][k].im *= g;
}
}
}
let mut output = vec![0.0f32; n + fft_size];
let mut wsum = vec![0.0f32; n + fft_size];
let mut spec_buf = c2r.make_input_vec();
let mut time_buf = c2r.make_output_vec();
for (t, frame) in spectra.iter().enumerate() {
spec_buf.copy_from_slice(frame);
c2r.process(&mut spec_buf, &mut time_buf).unwrap();
let off = t * hop_size;
for i in 0..fft_size {
output[off + i] += time_buf[i] * hann[i] * scale;
wsum[off + i] += hann[i] * hann[i];
}
}
for i in 0..n {
if wsum[i] > 1e-6 {
output[i] /= wsum[i];
}
}
output.truncate(n);
output
}
fn attenuate_band_transients(
signal: &[f32],
sample_rate: u32,
lo_hz: f32,
hi_hz: f32,
strength: f32,
) -> Vec<f32> {
let fft_size = 2048;
let hop_size = 512;
let n = signal.len();
if n < fft_size {
return signal.to_vec();
}
let mut planner = RealFftPlanner::<f32>::new();
let r2c = planner.plan_fft_forward(fft_size);
let c2r = planner.plan_fft_inverse(fft_size);
let hann = hann_window(fft_size);
let scale = 1.0f32 / fft_size as f32;
let n_bins = fft_size / 2 + 1;
let mut spectra = Vec::new();
let mut in_buf = r2c.make_input_vec();
let mut out_buf = r2c.make_output_vec();
let mut offset = 0;
while offset + fft_size <= n {
for i in 0..fft_size {
in_buf[i] = signal[offset + i] * hann[i];
}
r2c.process(&mut in_buf, &mut out_buf).unwrap();
spectra.push(out_buf.clone());
offset += hop_size;
}
let frames = spectra.len();
if frames == 0 {
return signal.to_vec();
}
let bin =
|hz: f32| ((hz * fft_size as f32 / sample_rate as f32).round() as usize).min(n_bins - 1);
let (lo, hi) = (bin(lo_hz), bin(hi_hz).max(bin(lo_hz)));
let band: Vec<f32> = spectra
.iter()
.map(|fr| fr[lo..=hi].iter().map(|c| c.re * c.re + c.im * c.im).sum::<f32>())
.collect();
let ratio = 1.0 + 8.0 / strength.max(0.1);
const T: usize = 6;
for t in 0..frames {
let a = t.saturating_sub(T);
let b = (t + T).min(frames - 1);
let mut nb: Vec<f32> = (a..=b).filter(|&s| s != t).map(|s| band[s]).collect();
if nb.is_empty() {
continue;
}
nb.sort_by(|x, y| x.partial_cmp(y).unwrap());
let med = nb[nb.len() / 2];
if band[t] > ratio * med.max(1e-12) {
let g = (med / band[t]).sqrt().clamp(0.0, 1.0);
for c in spectra[t][lo..=hi].iter_mut() {
c.re *= g;
c.im *= g;
}
}
}
let mut output = vec![0.0f32; n + fft_size];
let mut wsum = vec![0.0f32; n + fft_size];
let mut spec_buf = c2r.make_input_vec();
let mut time_buf = c2r.make_output_vec();
for (t, frame) in spectra.iter().enumerate() {
spec_buf.copy_from_slice(frame);
c2r.process(&mut spec_buf, &mut time_buf).unwrap();
let off = t * hop_size;
for i in 0..fft_size {
output[off + i] += time_buf[i] * hann[i] * scale;
wsum[off + i] += hann[i] * hann[i];
}
}
for i in 0..n {
if wsum[i] > 1e-6 {
output[i] /= wsum[i];
}
}
output.truncate(n);
output
}
pub fn deplosive(signal: &[f32], sample_rate: u32, strength: f32) -> Vec<f32> {
attenuate_band_transients(signal, sample_rate, 0.0, 250.0, strength)
}
pub fn derustle(signal: &[f32], sample_rate: u32, strength: f32) -> Vec<f32> {
attenuate_band_transients(signal, sample_rate, 1500.0, 6000.0, strength)
}