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}"); }