From 8d70f172c3126b751b907655eb393017865434f7 Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Tue, 7 Jun 2022 01:06:21 +0200 Subject: [PATCH] Add a linear-phase FIR LR24 crossover option --- plugins/crossover/src/crossover/fir.rs | 13 ++--- plugins/crossover/src/lib.rs | 72 ++++++++++++++++++++------ 2 files changed, 60 insertions(+), 25 deletions(-) diff --git a/plugins/crossover/src/crossover/fir.rs b/plugins/crossover/src/crossover/fir.rs index b8090c07..024f2aba 100644 --- a/plugins/crossover/src/crossover/fir.rs +++ b/plugins/crossover/src/crossover/fir.rs @@ -61,7 +61,7 @@ struct FirFilter { /// /// TODO: Profile to see if storing this as f32x2 rather than f32s plus splatting makes any /// 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` /// 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. - 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 // size match self.mode { - FirCrossoverType::LinkwitzRiley24LinearPhase => FILTER_SIZE / 2, + FirCrossoverType::LinkwitzRiley24LinearPhase => (FILTER_SIZE / 2) as u32, } } @@ -129,7 +129,7 @@ impl FirCrossover { &mut self, num_bands: usize, 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 <= NUM_BANDS); @@ -302,11 +302,6 @@ impl FirFilter { 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. pub fn reset(&mut self) { self.delay_buffer.fill(f32x2::default()); diff --git a/plugins/crossover/src/lib.rs b/plugins/crossover/src/lib.rs index bef6618f..367d1a15 100644 --- a/plugins/crossover/src/lib.rs +++ b/plugins/crossover/src/lib.rs @@ -19,6 +19,7 @@ #[cfg(not(feature = "simd"))] compile_error!("Compiling without SIMD support is currently not supported"); +use crossover::fir::{FirCrossover, FirCrossoverType}; use crossover::iir::{IirCrossover, IirCrossoverType}; use nih_plug::buffer::ChannelSamples; use nih_plug::prelude::*; @@ -42,6 +43,8 @@ struct Crossover { /// Provides the LR24 crossover. 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. should_update_filters: Arc, } @@ -69,15 +72,17 @@ struct CrossoverParams { pub crossover_type: EnumParam, } +// The `non_exhaustive` is to prevent adding cases for latency compensation when adding more types +// later #[derive(Debug, Clone, Copy, PartialEq, Eq, Enum)] +#[non_exhaustive] enum CrossoverType { #[id = "lr24"] #[name = "LR24"] LinkwitzRiley24, - // TODO: Add this next - // #[id = "lr24-lp"] - // #[name = "LR24 (LP)"] - // LinkwitzRiley24LinearPhase, + #[id = "lr24-lp"] + #[name = "LR24 (LP)"] + LinkwitzRiley24LinearPhase, } impl CrossoverParams { @@ -100,9 +105,11 @@ impl CrossoverParams { max: NUM_BANDS as i32, }, ) - .with_callback(Arc::new(move |_| { - should_update_filters.store(true, Ordering::Relaxed) - })), + .with_callback({ + let should_update_filters = should_update_filters.clone(); + + Arc::new(move |_| should_update_filters.store(true, Ordering::Relaxed)) + }), // TODO: More sensible default frequencies 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_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), + fir_crossover: FirCrossover::new(FirCrossoverType::LinkwitzRiley24LinearPhase), should_update_filters, } } @@ -187,26 +197,36 @@ impl Plugin for Crossover { &mut self, _bus_config: &BusConfig, buffer_config: &BufferConfig, - _context: &mut impl InitContext, + context: &mut impl InitContext, ) -> bool { self.buffer_config = *buffer_config; // Make sure the filter states match the current parameters 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 } fn reset(&mut self) { self.iir_crossover.reset(); + self.fir_crossover.reset(); } fn process( &mut self, buffer: &mut Buffer, aux: &mut AuxiliaryBuffers, - _context: &mut impl ProcessContext, + context: &mut impl ProcessContext, ) -> ProcessStatus { + // Right now both crossover types only do 24 dB/octave Linkwitz-Riley style crossovers match self.params.crossover_type.value() { CrossoverType::LinkwitzRiley24 => { 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 @@ -297,16 +330,23 @@ impl Crossover { /// Update the filter coefficients for the crossovers. fn update_filters(&mut self) { + let crossover_frequencies = [ + self.params.crossover_1_freq.smoothed.next(), + self.params.crossover_2_freq.smoothed.next(), + self.params.crossover_3_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, - [ - self.params.crossover_1_freq.smoothed.next(), - self.params.crossover_2_freq.smoothed.next(), - self.params.crossover_3_freq.smoothed.next(), - self.params.crossover_4_freq.smoothed.next(), - ], + crossover_frequencies, + ), + CrossoverType::LinkwitzRiley24LinearPhase => self.fir_crossover.update( + self.buffer_config.sample_rate, + self.params.num_bands.value as usize, + crossover_frequencies, ), } }