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::simd::{f32x2, StdFloat};
|
||||
|
||||
use crate::biquad::{Biquad, BiquadCoefficients};
|
||||
use crate::biquad::{Biquad, BiquadCoefficients, NEUTRAL_Q};
|
||||
use crate::NUM_BANDS;
|
||||
|
||||
// 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(
|
||||
&mut self,
|
||||
sample_rate: f32,
|
||||
|
@ -153,7 +154,86 @@ impl FirCrossover {
|
|||
frequencies: [f32; NUM_BANDS - 1],
|
||||
) {
|
||||
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(
|
||||
&mut self,
|
||||
sample_rate: f32,
|
||||
|
|
Loading…
Reference in a new issue