feat(common-dsp): second-order SVF allpass
SvfCoef::allpass has the phase of a Linkwitz-Riley split at the same frequency, so a crossover tree can keep its lower bands in phase with the upper ones. The test checks it against a real LR4 split sample by sample and for flat magnitude.
This commit is contained in:
1 parent
53bc26d14d
commit
22870127c8
2 files changed
+40
No files matched your search
@@ -70,6 +70,21 @@ impl SvfCoef {
|
||||
m2: -1.,
|
||||
}
|
||||
}
|
||||
/// Second-order allpass. With `q` = 1/√2 it has the phase of a Linkwitz–Riley crossover
|
||||
/// built from two lowpass and two highpass stages at the same frequency, so it keeps
|
||||
/// the bands of a crossover tree in phase.
|
||||
pub fn allpass(rate: f32, hz: f32, q: f32) -> Self {
|
||||
let k = 1. / q.max(1e-3);
|
||||
let (a1, a2, a3) = Self::base(rate, hz, k, 1.);
|
||||
Self {
|
||||
a1,
|
||||
a2,
|
||||
a3,
|
||||
m0: 1.,
|
||||
m1: -2. * k,
|
||||
m2: 0.,
|
||||
}
|
||||
}
|
||||
pub fn high_shelf(rate: f32, hz: f32, q: f32, gain_db: f32) -> Self {
|
||||
let a = 10f32.powf(gain_db / 40.);
|
||||
let k = 1. / q.max(1e-3);
|
||||
|
||||
@@ -46,6 +46,31 @@ fn svf_bell_at_zero_db_is_an_exact_identity() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn svf_allpass_matches_a_linkwitz_riley_split() {
|
||||
let q = std::f32::consts::FRAC_1_SQRT_2;
|
||||
let (low, high, all) = (
|
||||
SvfCoef::lowpass(RATE, 300., q),
|
||||
SvfCoef::highpass(RATE, 300., q),
|
||||
SvfCoef::allpass(RATE, 300., q),
|
||||
);
|
||||
let mut s = [Svf::default(); 5];
|
||||
let mut saw = Saw::new(RATE, 55.);
|
||||
for _ in 0..9600 {
|
||||
let x = saw.sample();
|
||||
let l = s[0].process(&low, x);
|
||||
let l = s[1].process(&low, l);
|
||||
let h = s[2].process(&high, x);
|
||||
let h = s[3].process(&high, h);
|
||||
let a = s[4].process(&all, x);
|
||||
assert!((l + h - a).abs() < 1e-4, "{} vs {a}", l + h);
|
||||
}
|
||||
let mut s = Svf::default();
|
||||
for hz in [30., 300., 3000.] {
|
||||
assert!(sine_gain(|x| s.process(&all, x), hz).abs() < 0.01);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn svf_survives_retuning_every_sample() {
|
||||
let mut s = Svf::default();
|
||||
|
||||
Reference in new issue
Block a user