Add the function to design the FIR band filters
Using the previously added biquad->linear phase FIR conversion function.
This commit is contained in:
parent
f4b3999916
commit
43980ec459
|
@ -19,7 +19,7 @@ use nih_plug::debug::*;
|
||||||
use std::f32;
|
use std::f32;
|
||||||
use std::simd::{f32x2, StdFloat};
|
use std::simd::{f32x2, StdFloat};
|
||||||
|
|
||||||
use crate::biquad::{Biquad, BiquadCoefficients};
|
use crate::biquad::{Biquad, BiquadCoefficients, NEUTRAL_Q};
|
||||||
use crate::NUM_BANDS;
|
use crate::NUM_BANDS;
|
||||||
|
|
||||||
// TODO: These filters would be more efficient when processing four samples at a time instead of
|
// TODO: These filters would be more efficient when processing four samples at a time instead of
|
||||||
|
@ -145,7 +145,8 @@ impl FirCrossover {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update the crossover frequencies for all filters.
|
/// Update the crossover frequencies for all filters. `num_bands` is assumed to be in `[2,
|
||||||
|
/// NUM_BANDS]`.
|
||||||
pub fn update(
|
pub fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
sample_rate: f32,
|
sample_rate: f32,
|
||||||
|
@ -153,7 +154,86 @@ impl FirCrossover {
|
||||||
frequencies: [f32; NUM_BANDS - 1],
|
frequencies: [f32; NUM_BANDS - 1],
|
||||||
) {
|
) {
|
||||||
match self.mode {
|
match self.mode {
|
||||||
FirCrossoverType::LinkwitzRiley24LinearPhase => todo!(),
|
FirCrossoverType::LinkwitzRiley24LinearPhase => {
|
||||||
|
// The goal here is to design 2-5 filters with the same frequency response
|
||||||
|
// magnitudes as the split bands in the IIR LR24 crossover version with the same
|
||||||
|
// center frequencies would have. The algorithm works in two stages. First, the IIR
|
||||||
|
// low-pass filters for the 1-4 crossovers used in the equivalent IIR LR24 version
|
||||||
|
// are computed and converted to equivalent linear-phase FIR filters using the
|
||||||
|
// algorithm described below in `FirCoefficients`. Then these are used to build the
|
||||||
|
// coefficients for the 2-5 bands:
|
||||||
|
//
|
||||||
|
// - The first band is always simply the first band's
|
||||||
|
// low-pass filter.
|
||||||
|
// - The middle bands are band-pass filters. These are created by taking the next
|
||||||
|
// crossover's low-pass filter and subtracting the accumulated band impulse
|
||||||
|
// response up to that point. The accumulated band impulse response is initialized
|
||||||
|
// with the first band's low-pass filter, and the band-pass filter for every band
|
||||||
|
// after that gets added to it.
|
||||||
|
// - The final band is a high-pass filter that's computed through spectral inversion
|
||||||
|
// from the accumulated band impulse response.
|
||||||
|
|
||||||
|
// As explained above, we'll start with the low-pass band
|
||||||
|
nih_debug_assert!(num_bands >= 2);
|
||||||
|
let iir_coefs = BiquadCoefficients::lowpass(sample_rate, frequencies[0], NEUTRAL_Q);
|
||||||
|
let lp_fir_coefs =
|
||||||
|
FirCoefficients::design_fourth_order_linear_phase_low_pass_from_biquad(
|
||||||
|
iir_coefs,
|
||||||
|
);
|
||||||
|
self.band_filters[0].coefficients = lp_fir_coefs;
|
||||||
|
|
||||||
|
// For the band-pass filters and the final high-pass filter, we need to keep track
|
||||||
|
// of the accumulated impulse response
|
||||||
|
let mut accumulated_ir = self.band_filters[0].coefficients.clone();
|
||||||
|
for (split_frequency, band_filter) in frequencies
|
||||||
|
.iter()
|
||||||
|
.zip(self.band_filters.iter_mut())
|
||||||
|
// There are `num_bands` bands, so there are `num_bands - 1` crossovers. The
|
||||||
|
// last band is formed from the accumulated impulse response.
|
||||||
|
.take(num_bands - 1)
|
||||||
|
// And the first band is already taken care of
|
||||||
|
.skip(1)
|
||||||
|
{
|
||||||
|
let iir_coefs =
|
||||||
|
BiquadCoefficients::lowpass(sample_rate, *split_frequency, NEUTRAL_Q);
|
||||||
|
let lp_fir_coefs =
|
||||||
|
FirCoefficients::design_fourth_order_linear_phase_low_pass_from_biquad(
|
||||||
|
iir_coefs,
|
||||||
|
);
|
||||||
|
|
||||||
|
// We want the band between the accumulated frequency response and the next
|
||||||
|
// crossover's low-pass filter
|
||||||
|
let mut fir_bp_coefs = lp_fir_coefs;
|
||||||
|
for (bp_coef, accumulated_coef) in
|
||||||
|
fir_bp_coefs.0.iter_mut().zip(accumulated_ir.0.iter_mut())
|
||||||
|
{
|
||||||
|
// At this poing `bp_coef` is the low-pass filter
|
||||||
|
*bp_coef -= *accumulated_coef;
|
||||||
|
|
||||||
|
// And the accumulated coefficients for the next band/for the high-pass
|
||||||
|
// filter should contain this band-pass filter. This becomes a bit weirder
|
||||||
|
// to read when it's a single loop, but essentially this is what's going on
|
||||||
|
// here:
|
||||||
|
//
|
||||||
|
// fir_bp_coefs = fir_lp_coefs - accumulated_ir
|
||||||
|
// accumulated_ir += fir_bp_coefs
|
||||||
|
|
||||||
|
*accumulated_coef += *bp_coef;
|
||||||
|
}
|
||||||
|
|
||||||
|
band_filter.coefficients = fir_bp_coefs;
|
||||||
|
}
|
||||||
|
|
||||||
|
// And finally we can do a spectral inversion of the accumulated IR to the the last
|
||||||
|
// band's high-pass filter
|
||||||
|
let mut fir_hp_coefs = accumulated_ir;
|
||||||
|
for coef in fir_hp_coefs.0.iter_mut() {
|
||||||
|
*coef = -*coef;
|
||||||
|
}
|
||||||
|
fir_hp_coefs.0[FILTER_SIZE / 2] += f32x2::splat(1.0);
|
||||||
|
|
||||||
|
self.band_filters[num_bands].coefficients = fir_hp_coefs;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -126,7 +126,8 @@ impl IirCrossover {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update the crossover frequencies for all filters.
|
/// Update the crossover frequencies for all filters. `num_bands` is assumed to be in `[2,
|
||||||
|
/// NUM_BANDS]`.
|
||||||
pub fn update(
|
pub fn update(
|
||||||
&mut self,
|
&mut self,
|
||||||
sample_rate: f32,
|
sample_rate: f32,
|
||||||
|
|
Loading…
Reference in a new issue