Allow compiling Diopser without SIMD
This commit is contained in:
parent
15f27e2746
commit
2f803915eb
4
.github/workflows/test.yml
vendored
4
.github/workflows/test.yml
vendored
|
@ -51,7 +51,9 @@ jobs:
|
||||||
- name: Set up Rust toolchain
|
- name: Set up Rust toolchain
|
||||||
uses: actions-rs/toolchain@v1
|
uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
# FIXME: Needed for SIMD
|
# FIXME: Needed for SIMD. Diopser can be compiled without SIMD support
|
||||||
|
# though, we'd actually need to test whether both versions
|
||||||
|
# compile
|
||||||
toolchain: nightly
|
toolchain: nightly
|
||||||
profile: minimal
|
profile: minimal
|
||||||
default: true
|
default: true
|
||||||
|
|
|
@ -46,7 +46,7 @@ great but it has only been tested under Wine with
|
||||||
|
|
||||||
NIH-plug works with the latest stable Rust compiler.
|
NIH-plug works with the latest stable Rust compiler.
|
||||||
|
|
||||||
After installing [Rust](https://rustup.rs/) you can compile any of the plugins
|
After installing [Rust](https://rustup.rs/), you can compile any of the plugins
|
||||||
in the `plugins` directory in the following way, replacing `gain` with the name
|
in the `plugins` directory in the following way, replacing `gain` with the name
|
||||||
of the plugin:
|
of the plugin:
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,12 @@ license = "GPL-3.0-or-later"
|
||||||
[lib]
|
[lib]
|
||||||
crate-type = ["cdylib"]
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["simd"]
|
||||||
|
# Make it go fast, vroom
|
||||||
|
simd = ["packed_simd"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nih_plug = { path = "../../", features = ["assert_process_allocs"] }
|
nih_plug = { path = "../../", features = ["assert_process_allocs"] }
|
||||||
|
|
||||||
packed_simd = { version = "0.3.6", package = "packed_simd_2" }
|
packed_simd = { version = "0.3.6", package = "packed_simd_2", optional = true }
|
||||||
|
|
|
@ -19,6 +19,22 @@ and much better performance.
|
||||||
Disperser or Kilohearts AB.
|
Disperser or Kilohearts AB.
|
||||||
</sup>
|
</sup>
|
||||||
|
|
||||||
|
### Building
|
||||||
|
|
||||||
|
After installing [Rust](https://rustup.rs/) you can compile Diopser as follows
|
||||||
|
|
||||||
|
```shell
|
||||||
|
cargo xtask bundle gain --release --bundle-vst3
|
||||||
|
```
|
||||||
|
|
||||||
|
If you don't have access to a nightly compiler (`rustup default nightly && rustup update`),
|
||||||
|
then you can compile a version without SIMD at a 2x penalty by disabling the
|
||||||
|
SIMD feature:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
cargo xtask bundle gain --release --bundle-vst3 --no-default-features
|
||||||
|
```
|
||||||
|
|
||||||
## Download
|
## Download
|
||||||
|
|
||||||
There are currently no prebuilt downloads available. Check this repository's
|
There are currently no prebuilt downloads available. Check this repository's
|
||||||
|
|
|
@ -14,10 +14,12 @@
|
||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use packed_simd::f32x2;
|
|
||||||
use std::f32::consts;
|
use std::f32::consts;
|
||||||
use std::ops::{Add, Mul, Sub};
|
use std::ops::{Add, Mul, Sub};
|
||||||
|
|
||||||
|
#[cfg(feature = "simd")]
|
||||||
|
use packed_simd::f32x2;
|
||||||
|
|
||||||
/// A simple biquad filter with functions for generating coefficients for an all-pass filter.
|
/// A simple biquad filter with functions for generating coefficients for an all-pass filter.
|
||||||
///
|
///
|
||||||
/// Based on <https://en.wikipedia.org/wiki/Digital_biquad_filter#Transposed_direct_forms>.
|
/// Based on <https://en.wikipedia.org/wiki/Digital_biquad_filter#Transposed_direct_forms>.
|
||||||
|
@ -127,6 +129,7 @@ impl SimdType for f32 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "simd")]
|
||||||
impl SimdType for f32x2 {
|
impl SimdType for f32x2 {
|
||||||
fn from_f32(value: f32) -> Self {
|
fn from_f32(value: f32) -> Self {
|
||||||
f32x2::splat(value)
|
f32x2::splat(value)
|
||||||
|
|
|
@ -22,11 +22,13 @@ 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;
|
||||||
|
|
||||||
|
#[cfg(feature = "simd")]
|
||||||
|
use packed_simd::f32x2;
|
||||||
|
|
||||||
mod filter;
|
mod filter;
|
||||||
|
|
||||||
/// How many all-pass filters we can have in series at most. The filter stages parameter determines
|
/// How many all-pass filters we can have in series at most. The filter stages parameter determines
|
||||||
|
@ -44,6 +46,9 @@ const MAX_AUTOMATION_STEP_SIZE: u32 = 512;
|
||||||
// - A GUI
|
// - A GUI
|
||||||
// - A panic switch (maybe also as a trigger-like parameter) to reset all filter states may also be
|
// - A panic switch (maybe also as a trigger-like parameter) to reset all filter states may also be
|
||||||
// useful
|
// useful
|
||||||
|
//
|
||||||
|
// TODO: Decide on whether to keep the scalar version or to just only support SIMD. Issue is that
|
||||||
|
// packed_simd requires a nightly compiler.
|
||||||
struct Diopser {
|
struct Diopser {
|
||||||
params: Pin<Box<DiopserParams>>,
|
params: Pin<Box<DiopserParams>>,
|
||||||
|
|
||||||
|
@ -53,9 +58,11 @@ struct Diopser {
|
||||||
/// All of the all-pass filters, with vectorized coefficients so they can be calculated for
|
/// All of the all-pass filters, with vectorized coefficients so they can be calculated for
|
||||||
/// multiple channels at once. [DiopserParams::num_stages] controls how many filters are
|
/// multiple channels at once. [DiopserParams::num_stages] controls how many filters are
|
||||||
/// actually active.
|
/// actually active.
|
||||||
// FIXME: This was the scalar version, maybe add this back at some point.
|
#[cfg(feature = "simd")]
|
||||||
// filters: Vec<[filter::Biquad<f32>; MAX_NUM_FILTERS]>,
|
|
||||||
filters: [filter::Biquad<f32x2>; MAX_NUM_FILTERS],
|
filters: [filter::Biquad<f32x2>; MAX_NUM_FILTERS],
|
||||||
|
#[cfg(not(feature = "simd"))]
|
||||||
|
filters: Vec<[filter::Biquad<f32>; 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.
|
||||||
|
@ -115,7 +122,11 @@ impl Default for Diopser {
|
||||||
|
|
||||||
sample_rate: 1.0,
|
sample_rate: 1.0,
|
||||||
|
|
||||||
|
#[cfg(feature = "simd")]
|
||||||
filters: [filter::Biquad::default(); MAX_NUM_FILTERS],
|
filters: [filter::Biquad::default(); MAX_NUM_FILTERS],
|
||||||
|
#[cfg(not(feature = "simd"))]
|
||||||
|
filters: Vec::new(),
|
||||||
|
|
||||||
should_update_filters,
|
should_update_filters,
|
||||||
next_filter_smoothing_in: 1,
|
next_filter_smoothing_in: 1,
|
||||||
}
|
}
|
||||||
|
@ -220,9 +231,17 @@ impl Plugin for Diopser {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn accepts_bus_config(&self, config: &BusConfig) -> bool {
|
fn accepts_bus_config(&self, config: &BusConfig) -> bool {
|
||||||
// FIXME: The scalar version would work for any IO layout, but this SIMD version can only do
|
// The scalar version can handle any channel config, while the SIMD version can only do
|
||||||
// stereo
|
// stereo
|
||||||
config.num_input_channels == config.num_output_channels && config.num_input_channels == 2
|
#[cfg(feature = "simd")]
|
||||||
|
{
|
||||||
|
config.num_input_channels == config.num_output_channels
|
||||||
|
&& config.num_input_channels == 2
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "simd"))]
|
||||||
|
{
|
||||||
|
config.num_input_channels == config.num_output_channels && config.num_input_channels > 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn initialize(
|
fn initialize(
|
||||||
|
@ -231,6 +250,14 @@ impl Plugin for Diopser {
|
||||||
buffer_config: &BufferConfig,
|
buffer_config: &BufferConfig,
|
||||||
_context: &mut impl ProcessContext,
|
_context: &mut impl ProcessContext,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
|
#[cfg(not(feature = "simd"))]
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
@ -252,15 +279,15 @@ 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 can compute the filters for both channels at once. This version thus now only
|
// We can compute the filters for both channels at once. The SIMD version thus now only
|
||||||
// supports stero audio.
|
// supports steroo audio.
|
||||||
|
#[cfg(feature = "simd")]
|
||||||
|
{
|
||||||
let mut samples =
|
let mut samples =
|
||||||
f32x2::new(*unsafe { channel_samples.get_unchecked_mut(0) }, *unsafe {
|
f32x2::new(*unsafe { channel_samples.get_unchecked_mut(0) }, *unsafe {
|
||||||
channel_samples.get_unchecked_mut(1)
|
channel_samples.get_unchecked_mut(1)
|
||||||
});
|
});
|
||||||
|
|
||||||
// Iterating over the filters and then over the channels would get us better cache
|
|
||||||
// locality even in the scalar version
|
|
||||||
for filter in self
|
for filter in self
|
||||||
.filters
|
.filters
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
|
@ -273,6 +300,19 @@ impl Plugin for Diopser {
|
||||||
*unsafe { channel_samples.get_unchecked_mut(1) } = samples.extract(1);
|
*unsafe { channel_samples.get_unchecked_mut(1) } = samples.extract(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "simd"))]
|
||||||
|
// We get better cache locality by iterating over the filters and then over the channels
|
||||||
|
for filter_idx in 0..self.params.filter_stages.value as usize {
|
||||||
|
for (channel_idx, filters) in self.filters.iter_mut().enumerate() {
|
||||||
|
// We can also use `channel_samples.iter_mut()`, but the compiler isn't able to
|
||||||
|
// optmize that iterator away and it would add a ton of overhead over indexing
|
||||||
|
// the buffer directly
|
||||||
|
let sample = unsafe { channel_samples.get_unchecked_mut(channel_idx) };
|
||||||
|
*sample = filters[filter_idx].process(*sample);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ProcessStatus::Normal
|
ProcessStatus::Normal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -336,11 +376,19 @@ 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);
|
||||||
|
|
||||||
|
#[cfg(feature = "simd")]
|
||||||
|
{
|
||||||
self.filters[filter_idx].coefficients = coefficients;
|
self.filters[filter_idx].coefficients = coefficients;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "simd"))]
|
||||||
|
for channel in self.filters.iter_mut() {
|
||||||
|
channel[filter_idx].coefficients = coefficients;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue