Add a standalone buffer management abstraction
The idea is that all backends are refactored to use this. This greatly reduces the need for backend-specific code when it comes to buffer management, and thus also bugs. It also overwrites main output channels that don't have a corresponding input channel with zeroes, which the current backends don't do.
This commit is contained in:
parent
a33569bff6
commit
83dd585c40
|
@ -5,6 +5,7 @@ use std::os::raw::c_char;
|
|||
|
||||
use crate::util::permit_alloc;
|
||||
|
||||
pub(crate) mod buffer_management;
|
||||
#[cfg(debug_assertions)]
|
||||
pub(crate) mod context_checks;
|
||||
|
||||
|
|
366
src/wrapper/util/buffer_management.rs
Normal file
366
src/wrapper/util/buffer_management.rs
Normal file
|
@ -0,0 +1,366 @@
|
|||
//! Helpers for safely constructing [`Buffer`]s from a plugin host's audio buffers.
|
||||
|
||||
use std::num::NonZeroU32;
|
||||
use std::ptr::NonNull;
|
||||
|
||||
use crate::audio_setup::AudioIOLayout;
|
||||
use crate::buffer::Buffer;
|
||||
|
||||
/// Buffers created using [`create_buffers`]. At some point the main `Plugin::process()` should
|
||||
/// probably also take an argument like this instead of main+aux buffers if we also want to provide
|
||||
/// access to overflowing input channels for e.g. stereo to mono plugins.
|
||||
pub struct Buffers<'a, 'buffer: 'a> {
|
||||
pub main_buffer: &'a mut Buffer<'buffer>,
|
||||
|
||||
// We can't use `AuxiliaryBuffers` here directly because we need different lifetimes for `'a`
|
||||
// and `'buffer` while `AuxiliaryBuffers` uses the same lifetime for both.
|
||||
pub aux_inputs: &'a mut [Buffer<'buffer>],
|
||||
pub aux_outputs: &'a mut [Buffer<'buffer>],
|
||||
}
|
||||
|
||||
/// A helper for safely creating and initializing [`Buffer`]s based on the host's input and output
|
||||
/// buffers.
|
||||
pub struct BufferManager {
|
||||
// These are the storage backing the fields in `BufferSource`. The wrapper needs to set these
|
||||
// values to match the channel pointers provided by the host. If audio buffers are not provided
|
||||
// for a bus, then they should be set to `None`. This helper will then copy data to the buffers
|
||||
// or fill them with zeroes if there is no data, while also accounting for in-place main IO
|
||||
// buffers.
|
||||
main_input_channel_pointers: Option<ChannelPointers>,
|
||||
main_output_channel_pointers: Option<ChannelPointers>,
|
||||
aux_input_channel_pointers: Vec<Option<ChannelPointers>>,
|
||||
aux_output_channel_pointers: Vec<Option<ChannelPointers>>,
|
||||
|
||||
/// The backing buffers that will be filled during `create_buffers`. This `'static` lifetime
|
||||
/// will be shortened when returning a reference to these buffers in `create_buffers` to match
|
||||
/// the function's lifetime.
|
||||
main_buffer: Buffer<'static>,
|
||||
|
||||
aux_input_buffers: Vec<Buffer<'static>>,
|
||||
/// Stores the data to back `aux_input_buffers`. We need to copy the host's auxiliary input
|
||||
/// buffers to our own first because the `Buffer` API is designed around mutable buffers, and
|
||||
/// the host may reuse its input buffers between plugins.
|
||||
aux_input_storage: Vec<Vec<Vec<f32>>>,
|
||||
|
||||
aux_output_buffers: Vec<Buffer<'static>>,
|
||||
}
|
||||
|
||||
// SAFETY: The raw pointers in the `ChannelPointers` fields/vectors are only used as scratch storage
|
||||
// inside of the `create_buffers()` function.
|
||||
unsafe impl Send for BufferManager {}
|
||||
unsafe impl Sync for BufferManager {}
|
||||
|
||||
/// Host data that the plugin's [`Buffer`]s should be created from. Leave these fields as `None`
|
||||
/// values
|
||||
pub struct BufferSource<'a> {
|
||||
pub main_input_channel_pointers: &'a mut Option<ChannelPointers>,
|
||||
pub main_output_channel_pointers: &'a mut Option<ChannelPointers>,
|
||||
pub aux_input_channel_pointers: &'a mut [Option<ChannelPointers>],
|
||||
pub aux_output_channel_pointers: &'a mut [Option<ChannelPointers>],
|
||||
}
|
||||
|
||||
/// Pointers to raw multichannel audio data for this port.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct ChannelPointers {
|
||||
/// A raw pointer to an array of f32 arrays, containing one array for each channel. `ptrs` must
|
||||
/// contain (at least) `num_channel` `*const f32`s, and each of those inner arrays must contain
|
||||
/// (at least) `num_samples` `f32` values.
|
||||
pub ptrs: NonNull<*mut f32>,
|
||||
/// The number of audio channels used for this port.
|
||||
pub num_channels: usize,
|
||||
}
|
||||
|
||||
impl BufferManager {
|
||||
/// Initialize managed buffers for a specific audio IO layout. The actual buffers can be set up
|
||||
/// using channel pointer data using [`create_buffers()`][Self::create_buffers()].
|
||||
pub fn for_audio_io_layout(max_buffer_size: usize, audio_io_layout: AudioIOLayout) -> Self {
|
||||
nih_debug_assert!(
|
||||
audio_io_layout
|
||||
.main_input_channels
|
||||
.map(NonZeroU32::get)
|
||||
.unwrap_or(0)
|
||||
<= audio_io_layout
|
||||
.main_output_channels
|
||||
.map(NonZeroU32::get)
|
||||
.unwrap_or(0),
|
||||
"Stereo-to-mono and other many-to-few audio channel configurations are currently not \
|
||||
supported"
|
||||
);
|
||||
|
||||
// The buffers are preallocated so that `create_buffers()` can be called without having to
|
||||
// allocate
|
||||
let mut main_buffer = Buffer::default();
|
||||
unsafe {
|
||||
main_buffer.set_slices(0, |output_slices| {
|
||||
output_slices.resize_with(
|
||||
audio_io_layout
|
||||
.main_output_channels
|
||||
.map(NonZeroU32::get)
|
||||
.unwrap_or(0) as usize,
|
||||
|| &mut [],
|
||||
);
|
||||
})
|
||||
};
|
||||
|
||||
let mut aux_input_buffers = Vec::with_capacity(audio_io_layout.aux_input_ports.len());
|
||||
let mut aux_input_storage = Vec::with_capacity(audio_io_layout.aux_input_ports.len());
|
||||
for num_channels in audio_io_layout.aux_input_ports {
|
||||
let mut buffer = Buffer::default();
|
||||
unsafe {
|
||||
buffer.set_slices(0, |slices| {
|
||||
slices.resize_with(num_channels.get() as usize, || &mut []);
|
||||
})
|
||||
};
|
||||
|
||||
aux_input_buffers.push(buffer);
|
||||
aux_input_storage.push(vec![
|
||||
vec![0.0; max_buffer_size];
|
||||
num_channels.get() as usize
|
||||
]);
|
||||
}
|
||||
|
||||
let mut aux_output_buffers = Vec::with_capacity(audio_io_layout.aux_output_ports.len());
|
||||
for num_channels in audio_io_layout.aux_output_ports {
|
||||
let mut buffer = Buffer::default();
|
||||
unsafe {
|
||||
buffer.set_slices(0, |slices| {
|
||||
slices.resize_with(num_channels.get() as usize, || &mut []);
|
||||
})
|
||||
};
|
||||
|
||||
aux_output_buffers.push(buffer);
|
||||
}
|
||||
|
||||
Self {
|
||||
main_input_channel_pointers: None,
|
||||
main_output_channel_pointers: None,
|
||||
aux_input_channel_pointers: vec![None; audio_io_layout.aux_input_ports.len()],
|
||||
aux_output_channel_pointers: vec![None; audio_io_layout.aux_output_ports.len()],
|
||||
|
||||
main_buffer,
|
||||
|
||||
aux_input_buffers,
|
||||
aux_input_storage,
|
||||
|
||||
aux_output_buffers,
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize the buffers using the host provided buffer pointers and return a reference to the
|
||||
/// created buffers that can be passed to `Plugin::process()`. This accounts for in-place main
|
||||
/// IO, missing channel pointers, null pointers, and mismatching channel counts. All
|
||||
/// uninitialized buffer data (aux outputs, and main output channels with no matching input
|
||||
/// channel) are filled with zeroes.
|
||||
///
|
||||
/// If any of the output
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// May panic if one of the inner channel pointers is a null pointer.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Any provided `ChannelPointers` must point to memory regions that remain valid to read from
|
||||
/// or write to for the lifetime of the returned [`Buffers`].
|
||||
pub unsafe fn create_buffers<'a, 'buffer: 'a>(
|
||||
&'a mut self,
|
||||
num_samples: usize,
|
||||
set_buffer_sources: impl FnOnce(&mut BufferSource),
|
||||
) -> Buffers<'a, 'buffer> {
|
||||
// Make sure the caller can't forget to unset previously set values
|
||||
self.main_input_channel_pointers = None;
|
||||
self.main_output_channel_pointers = None;
|
||||
self.aux_input_channel_pointers.fill(None);
|
||||
self.aux_output_channel_pointers.fill(None);
|
||||
set_buffer_sources(&mut BufferSource {
|
||||
main_input_channel_pointers: &mut self.main_input_channel_pointers,
|
||||
main_output_channel_pointers: &mut self.main_output_channel_pointers,
|
||||
aux_input_channel_pointers: &mut self.aux_input_channel_pointers,
|
||||
aux_output_channel_pointers: &mut self.aux_output_channel_pointers,
|
||||
});
|
||||
|
||||
// The main buffer points directly to the main output pointers
|
||||
self.main_buffer.set_slices(num_samples, |output_slices| {
|
||||
match self.main_output_channel_pointers {
|
||||
Some(output_channel_pointers) => {
|
||||
nih_debug_assert_eq!(output_slices.len(), output_channel_pointers.num_channels);
|
||||
for (channel_idx, output_slice) in output_slices
|
||||
.iter_mut()
|
||||
.enumerate()
|
||||
.take(output_channel_pointers.num_channels)
|
||||
{
|
||||
let output_channel_pointer =
|
||||
output_channel_pointers.ptrs.as_ptr().add(channel_idx);
|
||||
assert!(!output_channel_pointer.is_null());
|
||||
|
||||
*output_slice =
|
||||
std::slice::from_raw_parts_mut(*output_channel_pointer, num_samples);
|
||||
}
|
||||
|
||||
// If the caller/host should have provided buffer pointers but didn't then we
|
||||
// must get rid of any dangling slices
|
||||
output_slices[output_channel_pointers.num_channels..].fill_with(|| &mut [])
|
||||
}
|
||||
None => {
|
||||
nih_debug_assert_eq!(output_slices.len(), 0);
|
||||
|
||||
// Same as above
|
||||
output_slices.fill_with(|| &mut [])
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Since NIH-plug processes audio in-place, main input data needs to be copied to the main
|
||||
// output buffers
|
||||
if let (Some(input_channel_pointers), Some(output_channel_pointers)) = (
|
||||
self.main_input_channel_pointers,
|
||||
self.main_output_channel_pointers,
|
||||
) {
|
||||
// If the host processes the main IO out of place then the inputs need to be copied to
|
||||
// the output buffers. Otherwise the input should already be there.
|
||||
if input_channel_pointers.ptrs != output_channel_pointers.ptrs {
|
||||
self.main_buffer.set_slices(num_samples, |output_slices| {
|
||||
for (channel_idx, output_slice) in output_slices
|
||||
.iter_mut()
|
||||
.enumerate()
|
||||
.take(input_channel_pointers.num_channels)
|
||||
{
|
||||
let input_channel_pointer =
|
||||
input_channel_pointers.ptrs.as_ptr().add(channel_idx);
|
||||
assert!(!input_channel_pointer.is_null());
|
||||
|
||||
output_slice.copy_from_slice(std::slice::from_raw_parts_mut(
|
||||
*input_channel_pointer,
|
||||
num_samples,
|
||||
))
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Any excess channels will need to be filled with zeroes since they'd otherwise point
|
||||
// to whatever was left in the buffer
|
||||
if input_channel_pointers.num_channels < output_channel_pointers.num_channels {
|
||||
self.main_buffer.set_slices(num_samples, |output_slices| {
|
||||
for slice in &mut output_slices[input_channel_pointers.num_channels..] {
|
||||
slice.fill(0.0);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Because NIH-plug's `Buffer` type is geared around in-place processing, auxiliary inputs
|
||||
// need to be copied to our own buffers first (backed by the 'storage' vectors on this
|
||||
// object). That way the plugin can modify those buffers like any other buffers.
|
||||
for (input_channel_pointers, (input_storage, input_buffer)) in
|
||||
self.aux_input_channel_pointers.iter().zip(
|
||||
self.aux_input_storage
|
||||
.iter_mut()
|
||||
.zip(self.aux_input_buffers.iter_mut()),
|
||||
)
|
||||
{
|
||||
// Since these buffers are backed by our own storage, we can fill them with zeroes if
|
||||
// the pointers are missing for whatever reason that might be
|
||||
nih_debug_assert!(input_channel_pointers.is_some());
|
||||
match input_channel_pointers {
|
||||
Some(input_channel_pointers) => {
|
||||
nih_debug_assert_eq!(input_channel_pointers.num_channels, input_storage.len());
|
||||
for (channel_idx, channel) in input_storage
|
||||
.iter_mut()
|
||||
.enumerate()
|
||||
.take(input_channel_pointers.num_channels)
|
||||
{
|
||||
let input_channel_pointer =
|
||||
input_channel_pointers.ptrs.as_ptr().add(channel_idx);
|
||||
assert!(!input_channel_pointer.is_null());
|
||||
|
||||
nih_debug_assert!(num_samples <= channel.capacity());
|
||||
channel.resize(num_samples, 0.0);
|
||||
channel.copy_from_slice(std::slice::from_raw_parts_mut(
|
||||
*input_channel_pointer,
|
||||
num_samples,
|
||||
))
|
||||
}
|
||||
|
||||
// In case we were provided too few channels we'll fill the rest with zeroes to
|
||||
// avoid unexpected situations
|
||||
for channel in input_storage
|
||||
.iter_mut()
|
||||
.skip(input_channel_pointers.num_channels)
|
||||
{
|
||||
channel.fill(0.0);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
for channel in input_storage.iter_mut() {
|
||||
channel.fill(0.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
input_buffer.set_slices(num_samples, |input_slices| {
|
||||
// Since we initialized both `input_buffer` and `input_storage` this invariant
|
||||
// should never fail unless we made an error ourselves
|
||||
debug_assert_eq!(input_slices.len(), input_storage.len());
|
||||
|
||||
for (channel_slice, channel_storage) in
|
||||
input_slices.iter_mut().zip(input_storage.iter_mut())
|
||||
{
|
||||
// SAFETY: `channel_storage` is no longer used accessed directly after this
|
||||
*channel_slice = &mut *(channel_storage.as_mut_slice() as *mut [f32]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// The auxiliary output buffers can point directly to the host's buffers. This logic is the
|
||||
// same as the main outputs, minus the copying of input cdata
|
||||
for (output_channel_pointers, output_buffer) in self
|
||||
.aux_output_channel_pointers
|
||||
.iter()
|
||||
.zip(self.aux_output_buffers.iter_mut())
|
||||
{
|
||||
output_buffer.set_slices(num_samples, |output_slices| {
|
||||
match output_channel_pointers {
|
||||
Some(output_channel_pointers) => {
|
||||
nih_debug_assert_eq!(
|
||||
output_slices.len(),
|
||||
output_channel_pointers.num_channels
|
||||
);
|
||||
for (channel_idx, output_slice) in output_slices
|
||||
.iter_mut()
|
||||
.enumerate()
|
||||
.take(output_channel_pointers.num_channels)
|
||||
{
|
||||
let output_channel_pointer =
|
||||
output_channel_pointers.ptrs.as_ptr().add(channel_idx);
|
||||
assert!(!output_channel_pointer.is_null());
|
||||
|
||||
*output_slice = std::slice::from_raw_parts_mut(
|
||||
*output_channel_pointer,
|
||||
num_samples,
|
||||
);
|
||||
}
|
||||
|
||||
// If the caller/host should have provided buffer pointers but didn't then
|
||||
// we must get rid of any dangling slices
|
||||
output_slices[output_channel_pointers.num_channels..].fill_with(|| &mut [])
|
||||
}
|
||||
None => {
|
||||
nih_debug_assert_eq!(output_slices.len(), 0);
|
||||
|
||||
// Same as above
|
||||
output_slices.fill_with(|| &mut [])
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// SAFETY: The 'static lifetimes on the objects are needed so we can store the buffers.
|
||||
// Their actual lifetimes are `'a`, so we need to shrink them here. The contents are
|
||||
// valid for as long as the returned object is borrowed.
|
||||
std::mem::transmute::<Buffers<'a, 'static>, Buffers<'a, 'buffer>>(Buffers {
|
||||
main_buffer: &mut self.main_buffer,
|
||||
aux_inputs: &mut self.aux_input_buffers,
|
||||
aux_outputs: &mut self.aux_output_buffers,
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue