1
0
Fork 0

Add a super unsafe channel buffer adapter

To avoid having to perform lifetime magic here, this buffer needs to own
the slices and be stored as part of the RwLock.
This commit is contained in:
Robbert van der Helm 2022-02-02 12:29:33 +01:00
parent 63e4a54d7c
commit 58736f5cc8
4 changed files with 123 additions and 55 deletions

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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<Self::Item> {
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<Self::Item> {
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
}
}
}

View file

@ -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<P: Plugin> 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);