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:
parent
63e4a54d7c
commit
58736f5cc8
|
@ -21,7 +21,7 @@ use nih_plug::{
|
||||||
context::ProcessContext,
|
context::ProcessContext,
|
||||||
formatters,
|
formatters,
|
||||||
param::{BoolParam, FloatParam, Param, Params, Range},
|
param::{BoolParam, FloatParam, Param, Params, Range},
|
||||||
plugin::{BufferConfig, BusConfig, Plugin, ProcessStatus, Vst3Plugin},
|
plugin::{Buffer, BufferConfig, BusConfig, Plugin, ProcessStatus, Vst3Plugin},
|
||||||
util,
|
util,
|
||||||
};
|
};
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
|
@ -118,34 +118,14 @@ impl Plugin for Gain {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process(
|
fn process<'samples>(
|
||||||
&mut self,
|
&mut self,
|
||||||
samples: &mut [&mut [f32]],
|
buffer: &'samples mut Buffer<'_, 'samples>,
|
||||||
_context: &dyn ProcessContext,
|
_context: &dyn ProcessContext,
|
||||||
) -> ProcessStatus {
|
) -> 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: The wrapper should set FTZ if not yet enabled, mention ths in the process fuctnion
|
||||||
// TODO: Move this iterator to an adapter
|
for samples in buffer.iter_mut() {
|
||||||
let num_channels = samples.len();
|
for sample in samples {
|
||||||
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)
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: Smoothing
|
// TODO: Smoothing
|
||||||
*sample *= util::db_to_gain(self.params.gain.value);
|
*sample *= util::db_to_gain(self.params.gain.value);
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ use nih_plug::{
|
||||||
context::ProcessContext,
|
context::ProcessContext,
|
||||||
formatters,
|
formatters,
|
||||||
param::{FloatParam, Param, Params, Range},
|
param::{FloatParam, Param, Params, Range},
|
||||||
plugin::{BufferConfig, BusConfig, Plugin, ProcessStatus, Vst3Plugin},
|
plugin::{Buffer, BufferConfig, BusConfig, Plugin, ProcessStatus, Vst3Plugin},
|
||||||
util,
|
util,
|
||||||
};
|
};
|
||||||
use std::f32::consts;
|
use std::f32::consts;
|
||||||
|
@ -118,27 +118,13 @@ impl Plugin for Sine {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process(
|
fn process<'samples>(
|
||||||
&mut self,
|
&mut self,
|
||||||
samples: &mut [&mut [f32]],
|
buffer: &'samples mut Buffer<'_, 'samples>,
|
||||||
_context: &dyn ProcessContext,
|
_context: &dyn ProcessContext,
|
||||||
) -> ProcessStatus {
|
) -> 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;
|
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();
|
let sine = (self.phase * consts::TAU).sin();
|
||||||
|
|
||||||
self.phase = self.phase + phase_delta;
|
self.phase = self.phase + phase_delta;
|
||||||
|
@ -146,13 +132,7 @@ impl Plugin for Sine {
|
||||||
self.phase -= 1.0;
|
self.phase -= 1.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
for channel_idx in 0..num_channels {
|
for sample in samples {
|
||||||
let sample = unsafe {
|
|
||||||
samples
|
|
||||||
.get_unchecked_mut(channel_idx)
|
|
||||||
.get_unchecked_mut(sample_idx)
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: Parameter smoothing
|
// TODO: Parameter smoothing
|
||||||
*sample = sine * util::db_to_gain(self.params.gain.value);
|
*sample = sine * util::db_to_gain(self.params.gain.value);
|
||||||
}
|
}
|
||||||
|
|
110
src/plugin.rs
110
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
|
/// 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.
|
/// 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
|
/// TODO: Provide a way to access auxiliary input channels if the IO configuration is
|
||||||
/// assymetric
|
/// assymetric
|
||||||
/// TODO: Handle FTZ stuff on the wrapper side and mention that this has been handled
|
/// 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
|
/// TODO: Pass transport and other context information to the plugin
|
||||||
fn process(
|
fn process<'samples>(
|
||||||
&mut self,
|
&mut self,
|
||||||
samples: &mut [&mut [f32]],
|
buffer: &'samples mut Buffer<'_, 'samples>,
|
||||||
context: &dyn ProcessContext,
|
context: &dyn ProcessContext,
|
||||||
) -> ProcessStatus;
|
) -> ProcessStatus;
|
||||||
}
|
}
|
||||||
|
@ -145,3 +144,108 @@ pub enum ProcessStatus {
|
||||||
/// infite tail.
|
/// infite tail.
|
||||||
KeepAlive,
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -43,7 +43,7 @@ use widestring::U16CStr;
|
||||||
use crate::context::{EventLoop, MainThreadExecutor, OsEventLoop, ProcessContext};
|
use crate::context::{EventLoop, MainThreadExecutor, OsEventLoop, ProcessContext};
|
||||||
use crate::param::internals::ParamPtr;
|
use crate::param::internals::ParamPtr;
|
||||||
use crate::param::Param;
|
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::state::{ParamValue, State};
|
||||||
use crate::wrapper::util::{hash_param_id, strlcpy, u16strlcpy};
|
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
|
match self
|
||||||
.inner
|
.inner
|
||||||
.plugin
|
.plugin
|
||||||
.write()
|
.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) => {
|
ProcessStatus::Error(err) => {
|
||||||
nih_debug_assert_failure!("Process error: {}", err);
|
nih_debug_assert_failure!("Process error: {}", err);
|
||||||
|
|
Loading…
Reference in a new issue