1
0
Fork 0

Add the function to design the FIR band filters

Using the previously added biquad->linear phase FIR conversion function.
This commit is contained in:
Robbert van der Helm 2022-06-07 00:39:18 +02:00
parent f4b3999916
commit 43980ec459
2 changed files with 85 additions and 4 deletions

View file

@ -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;
}
} }
} }

View file

@ -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,