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
/// 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());

View file

@ -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<AtomicBool>,
}
@ -69,15 +72,17 @@ struct CrossoverParams {
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)]
#[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,
),
}
}