1
0
Fork 0

Add a linear-phase FIR LR24 crossover option

This commit is contained in:
Robbert van der Helm 2022-06-07 01:06:21 +02:00
parent 73919b6805
commit 8d70f172c3
2 changed files with 60 additions and 25 deletions

View file

@ -61,7 +61,7 @@ struct FirFilter {
/// ///
/// TODO: Profile to see if storing this as f32x2 rather than f32s plus splatting makes any /// TODO: Profile to see if storing this as f32x2 rather than f32s plus splatting makes any
/// difference in performance at all /// difference in performance at all
coefficients: FirCoefficients, pub coefficients: FirCoefficients,
/// A ring buffer storing the last `FILTER_SIZE - 1` samples. The capacity is `FILTER_SIZE` /// A ring buffer storing the last `FILTER_SIZE - 1` samples. The capacity is `FILTER_SIZE`
/// rounded up to the next power of two. /// rounded up to the next power of two.
@ -114,11 +114,11 @@ impl FirCrossover {
} }
/// Get the current latency in samples. This depends on the selected mode. /// Get the current latency in samples. This depends on the selected mode.
pub fn latency(&self) -> usize { pub fn latency(&self) -> u32 {
// Actually, that's a lie, since we currently only do linear-phase filters with a constant // Actually, that's a lie, since we currently only do linear-phase filters with a constant
// size // size
match self.mode { match self.mode {
FirCrossoverType::LinkwitzRiley24LinearPhase => FILTER_SIZE / 2, FirCrossoverType::LinkwitzRiley24LinearPhase => (FILTER_SIZE / 2) as u32,
} }
} }
@ -129,7 +129,7 @@ impl FirCrossover {
&mut self, &mut self,
num_bands: usize, num_bands: usize,
main_io: &ChannelSamples, main_io: &ChannelSamples,
mut band_outputs: [ChannelSamples; NUM_BANDS], band_outputs: [ChannelSamples; NUM_BANDS],
) { ) {
nih_debug_assert!(num_bands >= 2); nih_debug_assert!(num_bands >= 2);
nih_debug_assert!(num_bands <= NUM_BANDS); nih_debug_assert!(num_bands <= NUM_BANDS);
@ -302,11 +302,6 @@ impl FirFilter {
result result
} }
/// Update the coefficients for all filters in the crossover.
pub fn update_coefficients(&mut self, coefs: FirCoefficients) {
self.coefficients = coefs;
}
/// Reset the internal filter state. /// Reset the internal filter state.
pub fn reset(&mut self) { pub fn reset(&mut self) {
self.delay_buffer.fill(f32x2::default()); self.delay_buffer.fill(f32x2::default());

View file

@ -19,6 +19,7 @@
#[cfg(not(feature = "simd"))] #[cfg(not(feature = "simd"))]
compile_error!("Compiling without SIMD support is currently not supported"); compile_error!("Compiling without SIMD support is currently not supported");
use crossover::fir::{FirCrossover, FirCrossoverType};
use crossover::iir::{IirCrossover, IirCrossoverType}; use crossover::iir::{IirCrossover, IirCrossoverType};
use nih_plug::buffer::ChannelSamples; use nih_plug::buffer::ChannelSamples;
use nih_plug::prelude::*; use nih_plug::prelude::*;
@ -42,6 +43,8 @@ struct Crossover {
/// Provides the LR24 crossover. /// Provides the LR24 crossover.
iir_crossover: IirCrossover, iir_crossover: IirCrossover,
/// Provides the linear-phase LR24 crossover.
fir_crossover: FirCrossover,
/// Set when the number of bands has changed and the filters must be updated. /// Set when the number of bands has changed and the filters must be updated.
should_update_filters: Arc<AtomicBool>, should_update_filters: Arc<AtomicBool>,
} }
@ -69,15 +72,17 @@ struct CrossoverParams {
pub crossover_type: EnumParam<CrossoverType>, pub crossover_type: EnumParam<CrossoverType>,
} }
// The `non_exhaustive` is to prevent adding cases for latency compensation when adding more types
// later
#[derive(Debug, Clone, Copy, PartialEq, Eq, Enum)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Enum)]
#[non_exhaustive]
enum CrossoverType { enum CrossoverType {
#[id = "lr24"] #[id = "lr24"]
#[name = "LR24"] #[name = "LR24"]
LinkwitzRiley24, LinkwitzRiley24,
// TODO: Add this next #[id = "lr24-lp"]
// #[id = "lr24-lp"] #[name = "LR24 (LP)"]
// #[name = "LR24 (LP)"] LinkwitzRiley24LinearPhase,
// LinkwitzRiley24LinearPhase,
} }
impl CrossoverParams { impl CrossoverParams {
@ -100,9 +105,11 @@ impl CrossoverParams {
max: NUM_BANDS as i32, max: NUM_BANDS as i32,
}, },
) )
.with_callback(Arc::new(move |_| { .with_callback({
should_update_filters.store(true, Ordering::Relaxed) let should_update_filters = should_update_filters.clone();
})),
Arc::new(move |_| should_update_filters.store(true, Ordering::Relaxed))
}),
// TODO: More sensible default frequencies // TODO: More sensible default frequencies
crossover_1_freq: FloatParam::new("Crossover 1", 200.0, crossover_range) crossover_1_freq: FloatParam::new("Crossover 1", 200.0, crossover_range)
@ -122,7 +129,9 @@ impl CrossoverParams {
.with_value_to_string(crossover_value_to_string.clone()) .with_value_to_string(crossover_value_to_string.clone())
.with_string_to_value(crossover_string_to_value.clone()), .with_string_to_value(crossover_string_to_value.clone()),
crossover_type: EnumParam::new("Type", CrossoverType::LinkwitzRiley24), crossover_type: EnumParam::new("Type", CrossoverType::LinkwitzRiley24).with_callback(
Arc::new(move |_| should_update_filters.store(true, Ordering::Relaxed)),
),
} }
} }
} }
@ -142,6 +151,7 @@ impl Default for Crossover {
}, },
iir_crossover: IirCrossover::new(IirCrossoverType::LinkwitzRiley24), iir_crossover: IirCrossover::new(IirCrossoverType::LinkwitzRiley24),
fir_crossover: FirCrossover::new(FirCrossoverType::LinkwitzRiley24LinearPhase),
should_update_filters, should_update_filters,
} }
} }
@ -187,26 +197,36 @@ impl Plugin for Crossover {
&mut self, &mut self,
_bus_config: &BusConfig, _bus_config: &BusConfig,
buffer_config: &BufferConfig, buffer_config: &BufferConfig,
_context: &mut impl InitContext, context: &mut impl InitContext,
) -> bool { ) -> bool {
self.buffer_config = *buffer_config; self.buffer_config = *buffer_config;
// Make sure the filter states match the current parameters // Make sure the filter states match the current parameters
self.update_filters(); self.update_filters();
// The FIR filters are linear-phase and introduce latency
match self.params.crossover_type.value() {
CrossoverType::LinkwitzRiley24 => (),
CrossoverType::LinkwitzRiley24LinearPhase => {
context.set_latency_samples(self.fir_crossover.latency())
}
}
true true
} }
fn reset(&mut self) { fn reset(&mut self) {
self.iir_crossover.reset(); self.iir_crossover.reset();
self.fir_crossover.reset();
} }
fn process( fn process(
&mut self, &mut self,
buffer: &mut Buffer, buffer: &mut Buffer,
aux: &mut AuxiliaryBuffers, aux: &mut AuxiliaryBuffers,
_context: &mut impl ProcessContext, context: &mut impl ProcessContext,
) -> ProcessStatus { ) -> ProcessStatus {
// Right now both crossover types only do 24 dB/octave Linkwitz-Riley style crossovers
match self.params.crossover_type.value() { match self.params.crossover_type.value() {
CrossoverType::LinkwitzRiley24 => { CrossoverType::LinkwitzRiley24 => {
Self::do_process(buffer, aux, |main_channel_samples, bands| { Self::do_process(buffer, aux, |main_channel_samples, bands| {
@ -220,6 +240,19 @@ impl Plugin for Crossover {
); );
}) })
} }
CrossoverType::LinkwitzRiley24LinearPhase => {
context.set_latency_samples(self.fir_crossover.latency());
Self::do_process(buffer, aux, |main_channel_samples, bands| {
self.maybe_update_filters();
self.fir_crossover.process(
self.params.num_bands.value as usize,
main_channel_samples,
bands,
);
})
}
} }
ProcessStatus::Normal ProcessStatus::Normal
@ -297,16 +330,23 @@ impl Crossover {
/// Update the filter coefficients for the crossovers. /// Update the filter coefficients for the crossovers.
fn update_filters(&mut self) { fn update_filters(&mut self) {
match self.params.crossover_type.value() { let crossover_frequencies = [
CrossoverType::LinkwitzRiley24 => self.iir_crossover.update(
self.buffer_config.sample_rate,
self.params.num_bands.value as usize,
[
self.params.crossover_1_freq.smoothed.next(), self.params.crossover_1_freq.smoothed.next(),
self.params.crossover_2_freq.smoothed.next(), self.params.crossover_2_freq.smoothed.next(),
self.params.crossover_3_freq.smoothed.next(), self.params.crossover_3_freq.smoothed.next(),
self.params.crossover_4_freq.smoothed.next(), self.params.crossover_4_freq.smoothed.next(),
], ];
match self.params.crossover_type.value() {
CrossoverType::LinkwitzRiley24 => self.iir_crossover.update(
self.buffer_config.sample_rate,
self.params.num_bands.value as usize,
crossover_frequencies,
),
CrossoverType::LinkwitzRiley24LinearPhase => self.fir_crossover.update(
self.buffer_config.sample_rate,
self.params.num_bands.value as usize,
crossover_frequencies,
), ),
} }
} }