1
0
Fork 0

Allow compiling Diopser without SIMD

This commit is contained in:
Robbert van der Helm 2022-02-15 18:30:45 +01:00
parent 15f27e2746
commit 2f803915eb
6 changed files with 103 additions and 29 deletions

View file

@ -51,7 +51,9 @@ jobs:
- name: Set up Rust toolchain
uses: actions-rs/toolchain@v1
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
profile: minimal
default: true

View file

@ -46,7 +46,7 @@ great but it has only been tested under Wine with
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
of the plugin:

View file

@ -8,7 +8,12 @@ license = "GPL-3.0-or-later"
[lib]
crate-type = ["cdylib"]
[features]
default = ["simd"]
# Make it go fast, vroom
simd = ["packed_simd"]
[dependencies]
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 }

View file

@ -19,6 +19,22 @@ and much better performance.
Disperser or Kilohearts AB.
</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
There are currently no prebuilt downloads available. Check this repository's

View file

@ -14,10 +14,12 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use packed_simd::f32x2;
use std::f32::consts;
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.
///
/// 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 {
fn from_f32(value: f32) -> Self {
f32x2::splat(value)

View file

@ -22,11 +22,13 @@ use nih_plug::{
};
use nih_plug::{BoolParam, FloatParam, IntParam, Params, Range, SmoothingStyle};
use nih_plug::{Enum, EnumParam};
use packed_simd::f32x2;
use std::pin::Pin;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
#[cfg(feature = "simd")]
use packed_simd::f32x2;
mod filter;
/// 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 panic switch (maybe also as a trigger-like parameter) to reset all filter states may also be
// 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 {
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
/// multiple channels at once. [DiopserParams::num_stages] controls how many filters are
/// actually active.
// FIXME: This was the scalar version, maybe add this back at some point.
// filters: Vec<[filter::Biquad<f32>; MAX_NUM_FILTERS]>,
#[cfg(feature = "simd")]
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
/// updated. For the regular filter parameters we can look at the smoothers, but this is needed
/// when changing the number of active filters.
@ -115,7 +122,11 @@ impl Default for Diopser {
sample_rate: 1.0,
#[cfg(feature = "simd")]
filters: [filter::Biquad::default(); MAX_NUM_FILTERS],
#[cfg(not(feature = "simd"))]
filters: Vec::new(),
should_update_filters,
next_filter_smoothing_in: 1,
}
@ -220,9 +231,17 @@ impl Plugin for Diopser {
}
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
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(
@ -231,6 +250,14 @@ impl Plugin for Diopser {
buffer_config: &BufferConfig,
_context: &mut impl ProcessContext,
) -> 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
self.sample_rate = buffer_config.sample_rate;
self.should_update_filters.store(true, Ordering::Release);
@ -252,15 +279,15 @@ impl Plugin for Diopser {
for mut channel_samples in buffer.iter_mut() {
self.maybe_update_filters(smoothing_interval);
// We can compute the filters for both channels at once. This version thus now only
// supports stero audio.
// We can compute the filters for both channels at once. The SIMD version thus now only
// supports steroo audio.
#[cfg(feature = "simd")]
{
let mut samples =
f32x2::new(*unsafe { channel_samples.get_unchecked_mut(0) }, *unsafe {
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
.filters
.iter_mut()
@ -273,6 +300,19 @@ impl Plugin for Diopser {
*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
}
}
@ -336,11 +376,19 @@ impl Diopser {
}
.clamp(MIN_FREQUENCY, max_frequency);
// In the scalar version we'd update every channel's filter's coefficients gere
let coefficients =
filter::BiquadCoefficients::allpass(self.sample_rate, filter_frequency, resonance);
#[cfg(feature = "simd")]
{
self.filters[filter_idx].coefficients = coefficients;
}
#[cfg(not(feature = "simd"))]
for channel in self.filters.iter_mut() {
channel[filter_idx].coefficients = coefficients;
}
}
}
}