1
0
Fork 0

Use SIMD for Diopser

It's pretty damn fast now, especially compared to the JUCE version.
This commit is contained in:
Robbert van der Helm 2022-02-15 18:04:26 +01:00
parent 935d952d81
commit d0f1a79279
2 changed files with 34 additions and 23 deletions

View file

@ -11,7 +11,8 @@ like a cartoon laser beam, or a psytrance kickdrum. If you are experimenting
with those kinds of settings, then you may want to consider temporarily placing with those kinds of settings, then you may want to consider temporarily placing
a peak limiter after the plugin in case loud resonances start building up. a peak limiter after the plugin in case loud resonances start building up.
This is a port from https://github.com/robbert-vdh/diopser. This is a port from https://github.com/robbert-vdh/diopser with more features
and much better performance.
<sup id="disperser"> <sup id="disperser">
*Disperser is a trademark of Kilohearts AB. Diopser is in no way related to *Disperser is a trademark of Kilohearts AB. Diopser is in no way related to

View file

@ -22,6 +22,7 @@ use nih_plug::{
}; };
use nih_plug::{BoolParam, FloatParam, IntParam, Params, Range, SmoothingStyle}; use nih_plug::{BoolParam, FloatParam, IntParam, Params, Range, SmoothingStyle};
use nih_plug::{Enum, EnumParam}; use nih_plug::{Enum, EnumParam};
use packed_simd::f32x2;
use std::pin::Pin; use std::pin::Pin;
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc; use std::sync::Arc;
@ -49,9 +50,12 @@ struct Diopser {
/// Needed for computing the filter coefficients. /// Needed for computing the filter coefficients.
sample_rate: f32, sample_rate: f32,
/// All of the all-pass filters, with one array of serial filters per channelq. /// All of the all-pass filters, with vectorized coefficients so they can be calculated for
/// [DiopserParams::num_stages] controls how many filters are actually active. /// multiple channels at once. [DiopserParams::num_stages] controls how many filters are
filters: Vec<[filter::Biquad; MAX_NUM_FILTERS]>, /// actually active.
// FIXME: This was the scalar version, maybe add this back at some point.
// filters: Vec<[filter::Biquad<f32>; MAX_NUM_FILTERS]>,
filters: [filter::Biquad<f32x2>; MAX_NUM_FILTERS],
/// If this is set at the start of the processing cycle, then the filter coefficients should be /// If this is set at the start of the processing cycle, then the filter coefficients should be
/// updated. For the regular filter parameters we can look at the smoothers, but this is needed /// updated. For the regular filter parameters we can look at the smoothers, but this is needed
/// when changing the number of active filters. /// when changing the number of active filters.
@ -111,7 +115,7 @@ impl Default for Diopser {
sample_rate: 1.0, sample_rate: 1.0,
filters: Vec::new(), filters: [filter::Biquad::default(); MAX_NUM_FILTERS],
should_update_filters, should_update_filters,
next_filter_smoothing_in: 1, next_filter_smoothing_in: 1,
} }
@ -216,19 +220,17 @@ impl Plugin for Diopser {
} }
fn accepts_bus_config(&self, config: &BusConfig) -> bool { fn accepts_bus_config(&self, config: &BusConfig) -> bool {
// This works with any symmetrical IO layout // FIXME: The scalar version would work for any IO layout, but this SIMD version can only do
config.num_input_channels == config.num_output_channels && config.num_input_channels > 0 // stereo
config.num_input_channels == config.num_output_channels && config.num_input_channels == 2
} }
fn initialize( fn initialize(
&mut self, &mut self,
bus_config: &BusConfig, _bus_config: &BusConfig,
buffer_config: &BufferConfig, buffer_config: &BufferConfig,
_context: &mut impl ProcessContext, _context: &mut impl ProcessContext,
) -> bool { ) -> bool {
self.filters =
vec![[Default::default(); MAX_NUM_FILTERS]; bus_config.num_input_channels as usize];
// Initialize the filters on the first process call // Initialize the filters on the first process call
self.sample_rate = buffer_config.sample_rate; self.sample_rate = buffer_config.sample_rate;
self.should_update_filters.store(true, Ordering::Release); self.should_update_filters.store(true, Ordering::Release);
@ -250,16 +252,25 @@ impl Plugin for Diopser {
for mut channel_samples in buffer.iter_mut() { for mut channel_samples in buffer.iter_mut() {
self.maybe_update_filters(smoothing_interval); self.maybe_update_filters(smoothing_interval);
// We get better cache locality by iterating over the filters and then over the channels // We can compute the filters for both channels at once. This version thus now only
for filter_idx in 0..self.params.filter_stages.value as usize { // supports stero audio.
for (channel_idx, filters) in self.filters.iter_mut().enumerate() { let mut samples =
// We can also use `channel_samples.iter_mut()`, but the compiler isn't able to f32x2::new(*unsafe { channel_samples.get_unchecked_mut(0) }, *unsafe {
// optmize that iterator away and it would add a ton of overhead over indexing channel_samples.get_unchecked_mut(1)
// the buffer directly });
let sample = unsafe { channel_samples.get_unchecked_mut(channel_idx) };
*sample = filters[filter_idx].process(*sample); // Iterating over the filters and then over the channels would get us better cache
} // locality even in the scalar version
for filter in self
.filters
.iter_mut()
.take(self.params.filter_stages.value as usize)
{
samples = filter.process(samples);
} }
*unsafe { channel_samples.get_unchecked_mut(0) } = samples.extract(0);
*unsafe { channel_samples.get_unchecked_mut(1) } = samples.extract(1);
} }
ProcessStatus::Normal ProcessStatus::Normal
@ -325,11 +336,10 @@ impl Diopser {
} }
.clamp(MIN_FREQUENCY, max_frequency); .clamp(MIN_FREQUENCY, max_frequency);
// In the scalar version we'd update every channel's filter's coefficients gere
let coefficients = let coefficients =
filter::BiquadCoefficients::allpass(self.sample_rate, filter_frequency, resonance); filter::BiquadCoefficients::allpass(self.sample_rate, filter_frequency, resonance);
for channel in self.filters.iter_mut() { self.filters[filter_idx].coefficients = coefficients;
channel[filter_idx].coefficients = coefficients;
}
} }
} }
} }