diff --git a/plugins/gain/src/lib.rs b/plugins/gain/src/lib.rs index cdab3497..f9bfcd8f 100644 --- a/plugins/gain/src/lib.rs +++ b/plugins/gain/src/lib.rs @@ -21,7 +21,7 @@ use nih_plug::{ context::ProcessContext, formatters, param::{BoolParam, FloatParam, Param, Params, Range}, - plugin::{BufferConfig, BusConfig, Plugin, ProcessStatus, Vst3Plugin}, + plugin::{Buffer, BufferConfig, BusConfig, Plugin, ProcessStatus, Vst3Plugin}, util, }; use parking_lot::RwLock; @@ -118,34 +118,14 @@ impl Plugin for Gain { true } - fn process( + fn process<'samples>( &mut self, - samples: &mut [&mut [f32]], + buffer: &'samples mut Buffer<'_, 'samples>, _context: &dyn ProcessContext, ) -> ProcessStatus { - if samples.is_empty() { - return ProcessStatus::Error("Empty buffers"); - } - // TODO: The wrapper should set FTZ if not yet enabled, mention ths in the process fuctnion - // TODO: Move this iterator to an adapter - let num_channels = samples.len(); - let num_samples = samples[0].len(); - for channel in &samples[1..] { - nih_debug_assert_eq!(channel.len(), num_samples); - if channel.len() != num_samples { - return ProcessStatus::Error("Mismatching channel buffer sizes"); - } - } - - for sample_idx in 0..num_samples { - for channel_idx in 0..num_channels { - let sample = unsafe { - samples - .get_unchecked_mut(channel_idx) - .get_unchecked_mut(sample_idx) - }; - + for samples in buffer.iter_mut() { + for sample in samples { // TODO: Smoothing *sample *= util::db_to_gain(self.params.gain.value); } diff --git a/plugins/sine/src/lib.rs b/plugins/sine/src/lib.rs index 5d30bd8f..4cfcba4e 100644 --- a/plugins/sine/src/lib.rs +++ b/plugins/sine/src/lib.rs @@ -21,7 +21,7 @@ use nih_plug::{ context::ProcessContext, formatters, param::{FloatParam, Param, Params, Range}, - plugin::{BufferConfig, BusConfig, Plugin, ProcessStatus, Vst3Plugin}, + plugin::{Buffer, BufferConfig, BusConfig, Plugin, ProcessStatus, Vst3Plugin}, util, }; use std::f32::consts; @@ -118,27 +118,13 @@ impl Plugin for Sine { true } - fn process( + fn process<'samples>( &mut self, - samples: &mut [&mut [f32]], + buffer: &'samples mut Buffer<'_, 'samples>, _context: &dyn ProcessContext, ) -> ProcessStatus { - if samples.is_empty() { - return ProcessStatus::Error("Empty buffers"); - } - - // TODO: Move this iterator to an adapter - let num_channels = samples.len(); - let num_samples = samples[0].len(); - for channel in &samples[1..] { - nih_debug_assert_eq!(channel.len(), num_samples); - if channel.len() != num_samples { - return ProcessStatus::Error("Mismatching channel buffer sizes"); - } - } - let phase_delta = self.params.frequency.value / self.sample_rate; - for sample_idx in 0..num_samples { + for samples in buffer.iter_mut() { let sine = (self.phase * consts::TAU).sin(); self.phase = self.phase + phase_delta; @@ -146,13 +132,7 @@ impl Plugin for Sine { self.phase -= 1.0; } - for channel_idx in 0..num_channels { - let sample = unsafe { - samples - .get_unchecked_mut(channel_idx) - .get_unchecked_mut(sample_idx) - }; - + for sample in samples { // TODO: Parameter smoothing *sample = sine * util::db_to_gain(self.params.gain.value); } diff --git a/src/plugin.rs b/src/plugin.rs index 16cb0d22..a0ba6378 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -88,14 +88,13 @@ pub trait Plugin: Default + Send + Sync { /// been copied to the output buffers if they are not handling buffers in place (most hosts do /// however). All channels are also guarenteed to contain the same number of samples. /// - /// TODO: &mut [&mut [f32]] may not be the correct type here /// TODO: Provide a way to access auxiliary input channels if the IO configuration is /// assymetric /// TODO: Handle FTZ stuff on the wrapper side and mention that this has been handled /// TODO: Pass transport and other context information to the plugin - fn process( + fn process<'samples>( &mut self, - samples: &mut [&mut [f32]], + buffer: &'samples mut Buffer<'_, 'samples>, context: &dyn ProcessContext, ) -> ProcessStatus; } @@ -145,3 +144,108 @@ pub enum ProcessStatus { /// infite tail. KeepAlive, } + +/// The audio buffers used during processing. This contains the output audio output buffers with the +/// inputs already copied to the outputs. You can either use the iterator adapters to conveniently +/// and efficiently iterate over the samples, or you can do your own thing using the raw audio +/// buffers. +pub struct Buffer<'outer, 'inner> { + /// The raw output buffers. + raw: &'outer mut [&'inner mut [f32]], +} + +impl<'outer, 'inner> Buffer<'outer, 'inner> { + /// Construct a new buffer adapter based on a set of audio buffers. + /// + /// # Safety + /// + /// The rest of this object assumes all channel lengths are equal. Panics will likely occur if + /// this is not the case. + pub unsafe fn unchecked_new(buffers: &'outer mut [&'inner mut [f32]]) -> Self { + Self { raw: buffers } + } + + /// Returns true if this buffer does not contain any samples. + /// + /// TODO: Right now we guarantee that empty buffers never make it to the process function, + /// should we keep this? + pub fn is_empty(&self) -> bool { + self.raw.is_empty() || self.raw[0].is_empty() + } + + /// Obtain the raw audio buffers. + pub fn as_raw(&'outer mut self) -> &'outer mut [&'inner mut [f32]] { + self.raw + } + + /// Iterate over the samples, returning a channel iterator for each sample. + pub fn iter_mut(&'outer mut self) -> Samples<'outer, 'inner> { + Samples { + buffers: &mut self.raw, + current_sample: 0, + } + } +} + +/// An iterator over all samples in the buffer, yielding iterators over each channel for every +/// sample. This iteration order offers good cache locality for per-sample access. +pub struct Samples<'outer, 'inner> { + /// The raw output buffers. + pub(self) buffers: &'outer mut [&'inner mut [f32]], + pub(self) current_sample: usize, +} + +impl<'outer, 'inner> Iterator for Samples<'outer, 'inner> { + type Item = Channels<'outer, 'inner>; + + fn next(&mut self) -> Option { + if self.current_sample < self.buffers[0].len() { + // SAFETY: We guarantee that each sample is only mutably borrowed once in the channels + // iterator + let buffers: &'outer mut _ = unsafe { &mut *(self.buffers as *mut _) }; + let channels = Channels { + buffers, + current_sample: self.current_sample, + current_channel: 0, + }; + + self.current_sample += 1; + + Some(channels) + } else { + None + } + } +} + +/// An iterator over the channel data for a sample, yielded by [Samples]. +pub struct Channels<'outer, 'inner> { + /// The raw output buffers. + pub(self) buffers: &'outer mut [&'inner mut [f32]], + pub(self) current_sample: usize, + pub(self) current_channel: usize, +} + +impl<'outer, 'inner> Iterator for Channels<'outer, 'inner> { + type Item = &'inner mut f32; + + fn next(&mut self) -> Option { + if self.current_channel < self.buffers.len() { + // SAFETY: These bounds have already been checked + let sample = unsafe { + self.buffers + .get_unchecked_mut(self.current_channel) + .get_unchecked_mut(self.current_sample) + }; + // SAFETY: It is not possible to have multiple mutable references to the same sample at + // the same time + let sample: &'inner mut f32 = unsafe { &mut *(sample as *mut f32) }; + + self.current_channel += 1; + + Some(sample) + } else { + None + } + } +} diff --git a/src/wrapper/vst3.rs b/src/wrapper/vst3.rs index 7b445ead..36bf93bd 100644 --- a/src/wrapper/vst3.rs +++ b/src/wrapper/vst3.rs @@ -43,7 +43,7 @@ use widestring::U16CStr; use crate::context::{EventLoop, MainThreadExecutor, OsEventLoop, ProcessContext}; use crate::param::internals::ParamPtr; use crate::param::Param; -use crate::plugin::{BufferConfig, BusConfig, Plugin, ProcessStatus, Vst3Plugin}; +use crate::plugin::{Buffer, BufferConfig, BusConfig, Plugin, ProcessStatus, Vst3Plugin}; use crate::wrapper::state::{ParamValue, State}; use crate::wrapper::util::{hash_param_id, strlcpy, u16strlcpy}; @@ -1001,11 +1001,15 @@ impl IAudioProcessor for Wrapper<'_, P> { } } + // SAFETY: This borrow never outlives this process function, but this is still super unsafe + // TODO: Try to rewrite this so the buffer owns the slices and is stored in the RwLock + let mut buffer = Buffer::unchecked_new(&mut *(output_slices.as_mut() as *mut _)); match self .inner .plugin .write() - .process(&mut output_slices, self.inner.as_ref()) + // SAFETY: Same here + .process(&mut *(&mut buffer as *mut _), self.inner.as_ref()) { ProcessStatus::Error(err) => { nih_debug_assert_failure!("Process error: {}", err);