Add a linear-phase FIR LR24 crossover option
This commit is contained in:
parent
73919b6805
commit
8d70f172c3
|
@ -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());
|
||||||
|
|
|
@ -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) {
|
||||||
|
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() {
|
match self.params.crossover_type.value() {
|
||||||
CrossoverType::LinkwitzRiley24 => self.iir_crossover.update(
|
CrossoverType::LinkwitzRiley24 => self.iir_crossover.update(
|
||||||
self.buffer_config.sample_rate,
|
self.buffer_config.sample_rate,
|
||||||
self.params.num_bands.value as usize,
|
self.params.num_bands.value as usize,
|
||||||
[
|
crossover_frequencies,
|
||||||
self.params.crossover_1_freq.smoothed.next(),
|
),
|
||||||
self.params.crossover_2_freq.smoothed.next(),
|
CrossoverType::LinkwitzRiley24LinearPhase => self.fir_crossover.update(
|
||||||
self.params.crossover_3_freq.smoothed.next(),
|
self.buffer_config.sample_rate,
|
||||||
self.params.crossover_4_freq.smoothed.next(),
|
self.params.num_bands.value as usize,
|
||||||
],
|
crossover_frequencies,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue