1
0
Fork 0

Also implement auxiliary IO for VST3

This commit is contained in:
Robbert van der Helm 2022-05-28 00:10:16 +02:00
parent ee900f74c2
commit 0242cc5235
3 changed files with 175 additions and 22 deletions

View file

@ -1821,13 +1821,13 @@ impl<P: ClapPlugin> Wrapper<P> {
// Would only happen if the user configured zero channels for the
// auxiliary buffers
|| storage.is_empty()
|| (*host_input).channel_count != storage.len() as u32
|| (*host_input).channel_count != buffer.channels() as u32
{
nih_debug_assert!(host_input_idx < process.audio_inputs_count as isize);
nih_debug_assert!(!process.audio_inputs.is_null());
nih_debug_assert!(!(*host_input).data32.is_null());
nih_debug_assert!(!storage.is_empty());
nih_debug_assert_eq!((*host_input).channel_count, storage.len() as u32);
nih_debug_assert_eq!((*host_input).channel_count, buffer.channels() as u32);
// If the host passes weird data then we need to be very sure that there are
// no dangling references to previous data

View file

@ -78,6 +78,18 @@ pub(crate) struct WrapperInner<P: Vst3Plugin> {
/// between process calls. This buffer owns the vector, because otherwise it would need to store
/// a mutable reference to the data contained in this mutex.
pub output_buffer: AtomicRefCell<Buffer<'static>>,
/// Stores sample data for every sidechain input the plugin has. Indexed by
/// `[sidechain_input][channel][sample]` We'll copy the data to these buffers since modifying
/// the host's sidechain input buffers may not be safe, and the plugin may want to be able to
/// modify the buffers.
pub aux_input_storage: AtomicRefCell<Vec<Vec<Vec<f32>>>>,
/// Accompanying buffers for `aux_input_storage`. There is no way to do this in safe Rust, so
/// the process function needs to make sure all channel pointers stored in these buffers are
/// still correct before passing it to the plugin, hence the static lifetime.
pub aux_input_buffers: AtomicRefCell<Vec<Buffer<'static>>>,
/// Buffers for auxiliary plugin outputs, if the plugin has any. These reference the host's
/// memory directly.
pub aux_output_buffers: AtomicRefCell<Vec<Buffer<'static>>>,
/// The incoming events for the plugin, if `P::ACCEPTS_MIDI` is set. If
/// `P::SAMPLE_ACCURATE_AUTOMATION`, this is also read in lockstep with the parameter change
/// block splitting.
@ -285,6 +297,9 @@ impl<P: Vst3Plugin> WrapperInner<P> {
last_process_status: AtomicCell::new(ProcessStatus::Normal),
current_latency: AtomicU32::new(0),
output_buffer: AtomicRefCell::new(Buffer::default()),
aux_input_storage: AtomicRefCell::new(Vec::new()),
aux_input_buffers: AtomicRefCell::new(Vec::new()),
aux_output_buffers: AtomicRefCell::new(Vec::new()),
input_events: AtomicRefCell::new(VecDeque::with_capacity(1024)),
output_events: AtomicRefCell::new(VecDeque::with_capacity(1024)),
note_expression_controller: AtomicRefCell::new(NoteExpressionController::default()),

View file

@ -23,6 +23,7 @@ use super::util::{
u16strlcpy, VstPtr, VST3_MIDI_CCS, VST3_MIDI_NUM_PARAMS, VST3_MIDI_PARAMS_START,
};
use super::view::WrapperView;
use crate::buffer::Buffer;
use crate::context::Transport;
use crate::midi::{MidiConfig, NoteEvent};
use crate::param::ParamFlags;
@ -387,7 +388,48 @@ impl<P: Vst3Plugin> IComponent for Wrapper<P> {
.resize_with(bus_config.num_output_channels as usize, || &mut [])
});
// TODO: Initialize auxiliary IO
// Also allocate both the buffers and the slices pointing to those buffers for
// sidechain inputs. The slices will be assigned in the process function as this
// object may have been moved before then.
let mut aux_input_storage = self.inner.aux_input_storage.borrow_mut();
aux_input_storage.resize_with(
bus_config.aux_input_busses.num_busses as usize,
|| {
vec![
vec![0.0; buffer_config.max_buffer_size as usize];
bus_config.aux_input_busses.num_channels as usize
]
},
);
let mut aux_input_buffers = self.inner.aux_input_buffers.borrow_mut();
aux_input_buffers.resize_with(
bus_config.aux_input_busses.num_busses as usize,
Buffer::default,
);
for buffer in aux_input_buffers.iter_mut() {
buffer.with_raw_vec(|channel_slices| {
channel_slices.resize_with(
bus_config.aux_input_busses.num_channels as usize,
|| &mut [],
)
});
}
// And the same thing for the output buffers
let mut aux_output_buffers = self.inner.aux_output_buffers.borrow_mut();
aux_output_buffers.resize_with(
bus_config.aux_output_busses.num_busses as usize,
Buffer::default,
);
for buffer in aux_output_buffers.iter_mut() {
buffer.with_raw_vec(|channel_slices| {
channel_slices.resize_with(
bus_config.aux_output_busses.num_channels as usize,
|| &mut [],
)
});
}
kResultOk
} else {
@ -942,8 +984,6 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
unsafe fn process(&self, data: *mut vst3_sys::vst::ProcessData) -> tresult {
check_null_ptr!(data);
// TODO: Handle auxiliary IO, this still assumes main IO is always present and there's no auxiliary IO
// Panic on allocations if the `assert_process_allocs` feature has been enabled, and make
// sure that FTZ is set up correctly
process_wrapper(|| {
@ -956,20 +996,7 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
.expect("Process call without prior setup call")
.sample_rate;
// The setups we suppport are:
// - 1 input bus
// - 1 output bus
// - 1 input bus and 1 output bus
//
// Depending on the host either of these may also be missing if the number of channels
// is set to 0.
nih_debug_assert!(
data.num_inputs >= 0
&& data.num_inputs <= 1
&& data.num_outputs >= 0
&& data.num_outputs <= 1,
"The host provides more than one input or output bus"
);
nih_debug_assert!(data.num_inputs >= 0 && data.num_outputs >= 0);
nih_debug_assert_eq!(
data.symbolic_sample_size,
vst3_sys::vst::SymbolicSampleSizes::kSample32 as i32
@ -1246,6 +1273,115 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
}
});
let current_bus_config = self.inner.current_bus_config.load();
let has_main_input = current_bus_config.num_input_channels > 0;
// HACK: Bitwig requires VST3 plugins to always have a main output. We'll however
// still use this variable here to maintain consistency between the backends.
let has_main_output = true;
// We'll need to do the same thing for auxiliary input sidechain buffers. Since we
// don't know whether overwriting the host's buffers is safe here or not, we'll copy
// the data to our own buffers instead. These buffers are only accessible through
// the `aux` parameter on the `process()` function.
let mut aux_input_storage = self.inner.aux_input_storage.borrow_mut();
let mut aux_input_buffers = self.inner.aux_input_buffers.borrow_mut();
for (auxiliary_input_idx, (storage, buffer)) in aux_input_storage
.iter_mut()
.zip(aux_input_buffers.iter_mut())
.enumerate()
{
let host_input_idx = if has_main_input {
auxiliary_input_idx as isize + 1
} else {
auxiliary_input_idx as isize
};
let host_input = data.inputs.offset(host_input_idx);
if host_input_idx >= data.num_inputs as isize
|| data.inputs.is_null()
|| (*host_input).buffers.is_null()
// Would only happen if the user configured zero channels for the
// auxiliary buffers
|| storage.is_empty()
|| (*host_input).num_channels != buffer.channels() as i32
{
nih_debug_assert!(host_input_idx < data.num_inputs as isize);
nih_debug_assert!(!data.inputs.is_null());
nih_debug_assert!(!(*host_input).buffers.is_null());
nih_debug_assert!(!storage.is_empty());
nih_debug_assert_eq!((*host_input).num_channels, buffer.channels() as i32);
// If the host passes weird data then we need to be very sure that there are
// no dangling references to previous data
buffer.with_raw_vec(|slices| slices.fill_with(|| &mut []));
continue;
}
// We'll always reuse the start of the buffer even of the current block is
// shorter for cache locality reasons
let block_len = block_end - block_start;
for (channel_idx, channel_storage) in storage.iter_mut().enumerate() {
// The `set_len()` avoids having to unnecessarily fill the buffer with
// zeroes when sizing up
assert!(block_len <= channel_storage.capacity());
channel_storage.set_len(block_len);
channel_storage.copy_from_slice(std::slice::from_raw_parts(
(*(*host_input).buffers.add(channel_idx)).add(block_start)
as *const f32,
block_len,
));
}
buffer.with_raw_vec(|slices| {
for (channel_slice, channel_storage) in
slices.iter_mut().zip(storage.iter_mut())
{
// SAFETY: The 'static cast is required because Rust does not allow you
// to store references to a field in another field. Because
// these slices are set here before the process function is
// called, we ensure that there are no dangling slices. These
// buffers/slices are only ever read from in the second part of
// this block process loop.
*channel_slice = &mut *(channel_storage.as_mut_slice() as *mut [f32]);
}
});
}
// And the same thing for auxiliary output buffers
let mut aux_output_buffers = self.inner.aux_output_buffers.borrow_mut();
for (auxiliary_output_idx, buffer) in aux_output_buffers.iter_mut().enumerate() {
let host_output_idx = if has_main_output {
auxiliary_output_idx as isize + 1
} else {
auxiliary_output_idx as isize
};
let host_output = data.outputs.offset(host_output_idx);
if host_output_idx >= data.num_outputs as isize
|| data.outputs.is_null()
|| (*host_output).buffers.is_null()
|| buffer.channels() == 0
{
nih_debug_assert!(host_output_idx < data.num_outputs as isize);
nih_debug_assert!(!data.outputs.is_null());
nih_debug_assert!(!(*host_output).buffers.is_null());
// If the host passes weird data then we need to be very sure that there are
// no dangling references to previous data
buffer.with_raw_vec(|slices| slices.fill_with(|| &mut []));
continue;
}
let block_len = block_end - block_start;
buffer.with_raw_vec(|slices| {
for (channel_idx, channel_slice) in slices.iter_mut().enumerate() {
*channel_slice = std::slice::from_raw_parts_mut(
(*(*host_output).buffers.add(channel_idx)).add(block_start)
as *mut f32,
block_len,
);
}
});
}
// Some hosts process data in place, in which case we don't need to do any copying
// ourselves. If the pointers do not alias, then we'll do the copy here and then the
// plugin can just do normal in place processing.
@ -1332,10 +1468,12 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
let result = if buffer_is_valid {
let mut plugin = self.inner.plugin.write();
// TODO: Provide this for the VST3 version
// SAFETY: Shortening these borrows is safe as even if the plugin overwrites the
// slices (which it cannot do without using unsafe code), then they
// would still be reset on the next iteration
let mut aux = AuxiliaryBuffers {
inputs: &mut [],
outputs: &mut [],
inputs: &mut *(aux_input_buffers.as_mut_slice() as *mut [Buffer]),
outputs: &mut *(aux_output_buffers.as_mut_slice() as *mut [Buffer]),
};
let mut context = self.inner.make_process_context(transport);
let result = plugin.process(&mut output_buffer, &mut aux, &mut context);