Files
bass-foundry/plugins/weld/core/tests/group_delay.rs
T

77 lines
2.4 KiB
Rust

use weld_core::{Fit, Section, SMEAR_AUDIBLE_MS};
const RATE: f32 = 48000.;
fn fit(sections: &[(f32, f32)], flip: bool) -> Fit {
let mut fit = Fit {
flip,
count: sections.len(),
..Fit::NONE
};
for (s, &(fc, q)) in fit.sections.iter_mut().zip(sections) {
*s = Section { fc, q };
}
fit
}
/// Phase of the fit at `f`, from its complex response.
fn phase(fit: &Fit, f: f64) -> f64 {
let (re, im) = fit.response(f as f32, RATE);
(im as f64).atan2(re as f64)
}
#[test]
fn group_delay_matches_the_phase_slope() {
let fit = fit(&[(180., 0.9), (1200., 0.6), (3500., 2.)], false);
for f in [80., 250., 700., 1500., 3000., 8000.] {
let df = 0.5;
let mut dphi = phase(&fit, f + df) - phase(&fit, f - df);
while dphi > std::f64::consts::PI {
dphi -= std::f64::consts::TAU;
}
while dphi < -std::f64::consts::PI {
dphi += std::f64::consts::TAU;
}
let numeric = -dphi / (std::f64::consts::TAU * 2. * df);
let analytic = fit.group_delay(f as f32, RATE) as f64;
assert!(
(numeric - analytic).abs() < 2e-6 + 0.01 * numeric.abs(),
"{f} Hz: numeric {numeric}, analytic {analytic}"
);
}
}
#[test]
fn one_section_peaks_near_the_analog_value() {
// A second-order analog allpass delays 4·Q/ω₀ at its centre.
let (fc, q) = (1000., 0.7);
let delay = fit(&[(fc, q)], false).group_delay(fc, RATE);
let analog = 4. * q / (std::f32::consts::TAU * fc);
assert!(
(delay - analog).abs() < 0.05 * analog,
"{delay} vs {analog}"
);
}
#[test]
fn no_sections_and_polarity_do_not_smear() {
assert_eq!(Fit::NONE.smear_ms(RATE), 0.);
assert_eq!(fit(&[], true).smear_ms(RATE), 0.);
let one = fit(&[(1000., 0.7)], false);
assert_eq!(
one.smear_ms(RATE),
fit(&[(1000., 0.7)], true).smear_ms(RATE)
);
}
#[test]
fn smear_separates_gentle_and_sharp_fits() {
// Above 500 Hz a low section has only its tail left: well under the threshold.
let low = fit(&[(80., 0.7)], false).smear_ms(RATE);
assert!(low < 0.25 * SMEAR_AUDIBLE_MS, "{low}");
// A high-Q section at 1 kHz puts a 2.5 ms peak inside the range.
let sharp = fit(&[(1000., 4.)], false).smear_ms(RATE);
assert!(sharp > SMEAR_AUDIBLE_MS, "{sharp}");
assert!(sharp > 2. && sharp < 2.7, "{sharp}");
}