Also implement auxiliary IO for VST3
This commit is contained in:
parent
ee900f74c2
commit
0242cc5235
3 changed files with 175 additions and 22 deletions
|
@ -1821,13 +1821,13 @@ impl<P: ClapPlugin> Wrapper<P> {
|
||||||
// Would only happen if the user configured zero channels for the
|
// Would only happen if the user configured zero channels for the
|
||||||
// auxiliary buffers
|
// auxiliary buffers
|
||||||
|| storage.is_empty()
|
|| 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!(host_input_idx < process.audio_inputs_count as isize);
|
||||||
nih_debug_assert!(!process.audio_inputs.is_null());
|
nih_debug_assert!(!process.audio_inputs.is_null());
|
||||||
nih_debug_assert!(!(*host_input).data32.is_null());
|
nih_debug_assert!(!(*host_input).data32.is_null());
|
||||||
nih_debug_assert!(!storage.is_empty());
|
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
|
// If the host passes weird data then we need to be very sure that there are
|
||||||
// no dangling references to previous data
|
// no dangling references to previous data
|
||||||
|
|
|
@ -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
|
/// 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.
|
/// a mutable reference to the data contained in this mutex.
|
||||||
pub output_buffer: AtomicRefCell<Buffer<'static>>,
|
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
|
/// 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
|
/// `P::SAMPLE_ACCURATE_AUTOMATION`, this is also read in lockstep with the parameter change
|
||||||
/// block splitting.
|
/// block splitting.
|
||||||
|
@ -285,6 +297,9 @@ impl<P: Vst3Plugin> WrapperInner<P> {
|
||||||
last_process_status: AtomicCell::new(ProcessStatus::Normal),
|
last_process_status: AtomicCell::new(ProcessStatus::Normal),
|
||||||
current_latency: AtomicU32::new(0),
|
current_latency: AtomicU32::new(0),
|
||||||
output_buffer: AtomicRefCell::new(Buffer::default()),
|
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)),
|
input_events: AtomicRefCell::new(VecDeque::with_capacity(1024)),
|
||||||
output_events: AtomicRefCell::new(VecDeque::with_capacity(1024)),
|
output_events: AtomicRefCell::new(VecDeque::with_capacity(1024)),
|
||||||
note_expression_controller: AtomicRefCell::new(NoteExpressionController::default()),
|
note_expression_controller: AtomicRefCell::new(NoteExpressionController::default()),
|
||||||
|
|
|
@ -23,6 +23,7 @@ use super::util::{
|
||||||
u16strlcpy, VstPtr, VST3_MIDI_CCS, VST3_MIDI_NUM_PARAMS, VST3_MIDI_PARAMS_START,
|
u16strlcpy, VstPtr, VST3_MIDI_CCS, VST3_MIDI_NUM_PARAMS, VST3_MIDI_PARAMS_START,
|
||||||
};
|
};
|
||||||
use super::view::WrapperView;
|
use super::view::WrapperView;
|
||||||
|
use crate::buffer::Buffer;
|
||||||
use crate::context::Transport;
|
use crate::context::Transport;
|
||||||
use crate::midi::{MidiConfig, NoteEvent};
|
use crate::midi::{MidiConfig, NoteEvent};
|
||||||
use crate::param::ParamFlags;
|
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 [])
|
.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
|
kResultOk
|
||||||
} else {
|
} else {
|
||||||
|
@ -942,8 +984,6 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
|
||||||
unsafe fn process(&self, data: *mut vst3_sys::vst::ProcessData) -> tresult {
|
unsafe fn process(&self, data: *mut vst3_sys::vst::ProcessData) -> tresult {
|
||||||
check_null_ptr!(data);
|
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
|
// Panic on allocations if the `assert_process_allocs` feature has been enabled, and make
|
||||||
// sure that FTZ is set up correctly
|
// sure that FTZ is set up correctly
|
||||||
process_wrapper(|| {
|
process_wrapper(|| {
|
||||||
|
@ -956,20 +996,7 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
|
||||||
.expect("Process call without prior setup call")
|
.expect("Process call without prior setup call")
|
||||||
.sample_rate;
|
.sample_rate;
|
||||||
|
|
||||||
// The setups we suppport are:
|
nih_debug_assert!(data.num_inputs >= 0 && data.num_outputs >= 0);
|
||||||
// - 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_eq!(
|
nih_debug_assert_eq!(
|
||||||
data.symbolic_sample_size,
|
data.symbolic_sample_size,
|
||||||
vst3_sys::vst::SymbolicSampleSizes::kSample32 as i32
|
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
|
// 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
|
// 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.
|
// 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 result = if buffer_is_valid {
|
||||||
let mut plugin = self.inner.plugin.write();
|
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 {
|
let mut aux = AuxiliaryBuffers {
|
||||||
inputs: &mut [],
|
inputs: &mut *(aux_input_buffers.as_mut_slice() as *mut [Buffer]),
|
||||||
outputs: &mut [],
|
outputs: &mut *(aux_output_buffers.as_mut_slice() as *mut [Buffer]),
|
||||||
};
|
};
|
||||||
let mut context = self.inner.make_process_context(transport);
|
let mut context = self.inner.make_process_context(transport);
|
||||||
let result = plugin.process(&mut output_buffer, &mut aux, &mut context);
|
let result = plugin.process(&mut output_buffer, &mut aux, &mut context);
|
||||||
|
|
Loading…
Add table
Reference in a new issue