1
0
Fork 0

Update VST3 wrapper to use new buffer manager

This also fixes output events not being sent during a parameter flush.
This commit is contained in:
Robbert van der Helm 2023-03-31 16:56:38 +02:00
parent 30a26e0d9a
commit 8196641d65
3 changed files with 232 additions and 366 deletions

View file

@ -27,6 +27,8 @@ state is to list breaking changes.
containing whatever data was left in the host's output buffers. As part of containing whatever data was left in the host's output buffers. As part of
this change NIH-plug's buffer management has been refactored to reuse the same this change NIH-plug's buffer management has been refactored to reuse the same
logic in all of its wrappers. logic in all of its wrappers.
- Any outstanding VST3 output events are now sent to the host during a parameter
flush.
## [2023-03-21] ## [2023-03-21]

View file

@ -15,7 +15,6 @@ use super::param_units::ParamUnits;
use super::util::{ObjectPtr, VstPtr, VST3_MIDI_PARAMS_END, VST3_MIDI_PARAMS_START}; use super::util::{ObjectPtr, VstPtr, VST3_MIDI_PARAMS_END, VST3_MIDI_PARAMS_START};
use super::view::WrapperView; use super::view::WrapperView;
use crate::audio_setup::{AudioIOLayout, BufferConfig, ProcessMode}; use crate::audio_setup::{AudioIOLayout, BufferConfig, ProcessMode};
use crate::buffer::Buffer;
use crate::context::gui::AsyncExecutor; use crate::context::gui::AsyncExecutor;
use crate::context::process::Transport; use crate::context::process::Transport;
use crate::editor::Editor; use crate::editor::Editor;
@ -26,6 +25,7 @@ use crate::params::{ParamFlags, Params};
use crate::plugin::{Plugin, ProcessStatus, TaskExecutor, Vst3Plugin}; use crate::plugin::{Plugin, ProcessStatus, TaskExecutor, Vst3Plugin};
use crate::util::permit_alloc; use crate::util::permit_alloc;
use crate::wrapper::state::{self, PluginState}; use crate::wrapper::state::{self, PluginState};
use crate::wrapper::util::buffer_management::BufferManager;
use crate::wrapper::util::{hash_param_id, process_wrapper}; use crate::wrapper::util::{hash_param_id, process_wrapper};
/// The actual wrapper bits. We need this as an `Arc<T>` so we can safely use our event loop API. /// The actual wrapper bits. We need this as an `Arc<T>` so we can safely use our event loop API.
@ -82,23 +82,9 @@ pub(crate) struct WrapperInner<P: Vst3Plugin> {
/// The current latency in samples, as set by the plugin through the [`InitContext`] and the /// The current latency in samples, as set by the plugin through the [`InitContext`] and the
/// [`ProcessContext`]. /// [`ProcessContext`].
pub current_latency: AtomicU32, pub current_latency: AtomicU32,
/// Contains slices for the plugin's outputs. You can't directly create a nested slice from /// A data structure that helps manage and create buffers for all of the plugin's inputs and
/// a pointer to pointers, so this needs to be preallocated in the setup call and kept around /// outputs based on channel pointers provided by the host.
/// between process calls. This buffer owns the vector, because otherwise it would need to store pub buffer_manager: AtomicRefCell<BufferManager>,
/// 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 /// 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.
@ -317,10 +303,12 @@ impl<P: Vst3Plugin> WrapperInner<P> {
current_process_mode: AtomicCell::new(ProcessMode::Realtime), current_process_mode: AtomicCell::new(ProcessMode::Realtime),
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()), // This is initialized just before calling `Plugin::initialize()` so that during the
aux_input_storage: AtomicRefCell::new(Vec::new()), // process call buffers can be initialized without any allocations
aux_input_buffers: AtomicRefCell::new(Vec::new()), buffer_manager: AtomicRefCell::new(BufferManager::for_audio_io_layout(
aux_output_buffers: AtomicRefCell::new(Vec::new()), 0,
AudioIOLayout::default(),
)),
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()),

View file

@ -1,9 +1,8 @@
use std::borrow::Borrow; use std::borrow::Borrow;
use std::cmp;
use std::ffi::c_void; use std::ffi::c_void;
use std::mem::{self, MaybeUninit}; use std::mem::{self, MaybeUninit};
use std::num::NonZeroU32; use std::num::NonZeroU32;
use std::ptr; use std::ptr::NonNull;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use std::sync::Arc; use std::sync::Arc;
use vst3_com::vst::{DataEvent, IProcessContextRequirementsFlags, ProcessModes}; use vst3_com::vst::{DataEvent, IProcessContextRequirementsFlags, ProcessModes};
@ -28,7 +27,6 @@ use super::util::{
use super::util::{VST3_MIDI_CHANNELS, VST3_MIDI_PARAMS_END}; use super::util::{VST3_MIDI_CHANNELS, VST3_MIDI_PARAMS_END};
use super::view::WrapperView; use super::view::WrapperView;
use crate::audio_setup::{AuxiliaryBuffers, BufferConfig, ProcessMode}; use crate::audio_setup::{AuxiliaryBuffers, BufferConfig, ProcessMode};
use crate::buffer::Buffer;
use crate::context::process::Transport; use crate::context::process::Transport;
use crate::midi::sysex::SysExMessage; use crate::midi::sysex::SysExMessage;
use crate::midi::{MidiConfig, NoteEvent}; use crate::midi::{MidiConfig, NoteEvent};
@ -36,6 +34,7 @@ use crate::params::ParamFlags;
use crate::plugin::{ProcessStatus, Vst3Plugin}; use crate::plugin::{ProcessStatus, Vst3Plugin};
use crate::util::permit_alloc; use crate::util::permit_alloc;
use crate::wrapper::state; use crate::wrapper::state;
use crate::wrapper::util::buffer_management::{BufferManager, ChannelPointers};
use crate::wrapper::util::{clamp_input_event_timing, clamp_output_event_timing, process_wrapper}; use crate::wrapper::util::{clamp_input_event_timing, clamp_output_event_timing, process_wrapper};
// Alias needed for the VST3 attribute macro // Alias needed for the VST3 attribute macro
@ -390,62 +389,13 @@ impl<P: Vst3Plugin> IComponent for Wrapper<P> {
// instead. Otherwise we would call the function twice, and `set_process()` needs // instead. Otherwise we would call the function twice, and `set_process()` needs
// to be called after this function before the plugin may process audio again. // to be called after this function before the plugin may process audio again.
// Preallocate enough room in the output slices vector so we can convert a `*mut *mut // This preallocates enough space so we can transform all of the host's raw
// f32` to a `&mut [&mut f32]` in the process call // channel pointers into a set of `Buffer` objects for the plugin's main and
self.inner // auxiliary IO
.output_buffer *self.inner.buffer_manager.borrow_mut() = BufferManager::for_audio_io_layout(
.borrow_mut() buffer_config.max_buffer_size as usize,
.set_slices(0, |output_slices| { audio_io_layout,
output_slices.resize_with( );
audio_io_layout
.main_output_channels
.map(NonZeroU32::get)
.unwrap_or_default() as usize,
|| &mut [],
);
// All slices must have the same length, so if the number of output
// channels has changed since the last call then we should make sure to
// clear any old (dangling) slices to be consistent
output_slices.fill_with(|| &mut []);
});
// 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();
let mut aux_input_buffers = self.inner.aux_input_buffers.borrow_mut();
aux_input_storage.resize_with(audio_io_layout.aux_input_ports.len(), Vec::new);
aux_input_buffers
.resize_with(audio_io_layout.aux_input_ports.len(), Buffer::default);
for ((buffer_storage, buffer), num_channels) in aux_input_storage
.iter_mut()
.zip(aux_input_buffers.iter_mut())
.zip(audio_io_layout.aux_input_ports.iter())
{
buffer_storage.resize_with(num_channels.get() as usize, Vec::new);
for channel_storage in buffer_storage {
channel_storage.resize(buffer_config.max_buffer_size as usize, 0.0);
}
buffer.set_slices(0, |channel_slices| {
channel_slices.resize_with(num_channels.get() as usize, || &mut []);
channel_slices.fill_with(|| &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(audio_io_layout.aux_output_ports.len(), Buffer::default);
for (buffer, num_channels) in aux_output_buffers
.iter_mut()
.zip(audio_io_layout.aux_output_ports.iter())
{
buffer.set_slices(0, |channel_slices| {
channel_slices.resize_with(num_channels.get() as usize, || &mut []);
channel_slices.fill_with(|| &mut []);
});
}
kResultOk kResultOk
} else { } else {
@ -473,7 +423,11 @@ impl<P: Vst3Plugin> IComponent for Wrapper<P> {
let mut eof_pos = 0; let mut eof_pos = 0;
if state.tell(&mut current_pos) != kResultOk if state.tell(&mut current_pos) != kResultOk
|| state.seek(0, vst3_sys::base::kIBSeekEnd, &mut eof_pos) != kResultOk || state.seek(0, vst3_sys::base::kIBSeekEnd, &mut eof_pos) != kResultOk
|| state.seek(current_pos, vst3_sys::base::kIBSeekSet, ptr::null_mut()) != kResultOk || state.seek(
current_pos,
vst3_sys::base::kIBSeekSet,
std::ptr::null_mut(),
) != kResultOk
{ {
nih_debug_assert_failure!("Could not get the stream length"); nih_debug_assert_failure!("Could not get the stream length");
return kResultFalse; return kResultFalse;
@ -744,7 +698,7 @@ impl<P: Vst3Plugin> IEditController for Wrapper<P> {
match self.inner.editor.borrow().as_ref() { match self.inner.editor.borrow().as_ref() {
Some(editor) => Box::into_raw(WrapperView::new(self.inner.clone(), editor.clone())) Some(editor) => Box::into_raw(WrapperView::new(self.inner.clone(), editor.clone()))
as *mut vst3_sys::c_void, as *mut vst3_sys::c_void,
None => ptr::null_mut(), None => std::ptr::null_mut(),
} }
} }
} }
@ -1002,26 +956,22 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
let total_buffer_len = data.num_samples as usize; let total_buffer_len = data.num_samples as usize;
// Before doing anything, clear out any auxiliary outputs since they may contain
// uninitialized data when the host assumes that we'll always write something there
let current_audio_io_layout = self.inner.current_audio_io_layout.load(); let current_audio_io_layout = self.inner.current_audio_io_layout.load();
let has_main_input = current_audio_io_layout.main_input_channels.is_some(); let has_main_input = current_audio_io_layout.main_input_channels.is_some();
let has_main_output = current_audio_io_layout.main_output_channels.is_some(); let has_main_output = current_audio_io_layout.main_output_channels.is_some();
let aux_input_start_idx = if has_main_input { 1 } else { 0 }; let aux_input_start_idx = if has_main_input { 1 } else { 0 };
let aux_output_start_idx = if has_main_output { 1 } else { 0 }; let aux_output_start_idx = if has_main_output { 1 } else { 0 };
if !data.outputs.is_null() {
for output_idx in aux_output_start_idx..data.num_outputs as usize { // NOTE: VST3 hosts may trigger a 'parameter flush' by calling the process function for
let host_output = data.outputs.add(output_idx); // 0 input samples. If this is the case then we'll only handle events and skip all
if !(*host_output).buffers.is_null() { // audio processing. Some hosts, like Ableton Live, implement this in a broken way
for channel_idx in 0..(*host_output).num_channels as isize { // and instead only set the number of channels to 0. In that case the
ptr::write_bytes( // 'buffer_is_valid' check from below should still prevent audio processing.
*((*host_output).buffers.offset(channel_idx)) as *mut f32, let mut is_param_flush = total_buffer_len == 0;
0, if (data.num_outputs == 0 || data.outputs.is_null())
total_buffer_len, && (has_main_output || !current_audio_io_layout.aux_output_ports.is_empty())
); {
} is_param_flush = true;
}
}
} }
// If `P::SAMPLE_ACCURATE_AUTOMATION` is set, then we'll split up the audio buffer into // If `P::SAMPLE_ACCURATE_AUTOMATION` is set, then we'll split up the audio buffer into
@ -1271,270 +1221,205 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
} }
} }
// This vector has been preallocated to contain enough slices as there are output let result = if is_param_flush {
// channels. In case the does does not provide an output or if they don't provide kResultOk
// all of the channels (this should not happen, but Ableton Live might do it) then
// we'll skip the process function.
let block_len = block_end - block_start;
let mut output_buffer = self.inner.output_buffer.borrow_mut();
let mut buffer_is_valid = false;
output_buffer.set_slices(block_len, |output_slices| {
// Buffers for zero-channel plugins like note effects should always be allowed
buffer_is_valid = output_slices.is_empty();
if !data.outputs.is_null() && has_main_output {
let num_output_channels = (*data.outputs).num_channels as usize;
// This ensures that we never feed dangling slices to the wrapped plugin
buffer_is_valid = num_output_channels == output_slices.len();
nih_debug_assert_eq!(num_output_channels, output_slices.len());
// In case the host does provide fewer output channels than we expect, we
// should still try to handle that gracefully. This happens when the plugin
// is bypassed in Ableton Live and a parameter is modified. In that case the
// above assertion will still trigger.
for (output_channel_idx, output_channel_slice) in output_slices
.iter_mut()
.take(num_output_channels)
.enumerate()
{
// If `P::SAMPLE_ACCURATE_AUTOMATION` is set, then we may be iterating
// over the buffer in smaller sections.
// SAFETY: These pointers may not be valid outside of this function even
// though their lifetime is equal to this structs. This is still safe
// because they are only dereferenced here later as part of this process
// function.
let channel_ptr =
*((*data.outputs).buffers as *mut *mut f32).add(output_channel_idx);
*output_channel_slice = std::slice::from_raw_parts_mut(
channel_ptr.add(block_start),
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.
if !data.outputs.is_null()
&& !data.inputs.is_null()
&& has_main_input
&& has_main_output
{
let num_output_channels = (*data.outputs).num_channels as usize;
let num_input_channels = (*data.inputs).num_channels as usize;
nih_debug_assert!(
num_input_channels <= num_output_channels,
"Stereo to mono and similar configurations are not supported"
);
for input_channel_idx in 0..cmp::min(num_input_channels, num_output_channels) {
let output_channel_ptr =
*((*data.outputs).buffers as *mut *mut f32).add(input_channel_idx);
let input_channel_ptr =
*((*data.inputs).buffers as *const *const f32).add(input_channel_idx);
if input_channel_ptr != output_channel_ptr {
ptr::copy_nonoverlapping(
input_channel_ptr.add(block_start),
output_channel_ptr.add(block_start),
block_len,
);
}
}
}
// 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 (aux_input_idx, (storage, buffer)) in aux_input_storage
.iter_mut()
.zip(aux_input_buffers.iter_mut())
.enumerate()
{
let host_input_idx = aux_input_start_idx + aux_input_idx;
let host_input = data.inputs.add(host_input_idx);
if host_input_idx >= data.num_inputs as usize
|| 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
{
// During a parameter flush the number of inputs/outputs may be 0 and the
// number of channels may be 0, so these assertions need to be a bit more
// relaxed
nih_debug_assert!(
data.num_inputs == 0 || host_input_idx < data.num_inputs as usize
);
nih_debug_assert!(!storage.is_empty());
if !data.inputs.is_null() && host_input_idx < data.num_inputs as usize {
nih_debug_assert!(!(*host_input).buffers.is_null());
nih_debug_assert!(
(*host_input).num_channels == 0
|| (*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.set_slices(0, |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
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.set_slices(block_len, |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 (aux_output_idx, buffer) in aux_output_buffers.iter_mut().enumerate() {
let host_output_idx = aux_output_start_idx + aux_output_idx;
let host_output = data.outputs.add(host_output_idx);
if host_output_idx >= data.num_outputs as usize
|| data.outputs.is_null()
|| (*host_output).buffers.is_null()
|| buffer.channels() == 0
|| (*host_output).num_channels != buffer.channels() as i32
{
nih_debug_assert!(host_output_idx < data.num_outputs as usize);
nih_debug_assert!(!data.outputs.is_null());
if !data.outputs.is_null() && host_output_idx < data.num_outputs as usize {
nih_debug_assert!(!(*host_output).buffers.is_null());
nih_debug_assert!(
!(*host_output).num_channels == 0
|| !(*host_output).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.set_slices(0, |slices| slices.fill_with(|| &mut []));
continue;
}
buffer.set_slices(block_len, |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 of the fields are left empty because VST3 does not provide this
// information, but the methods on [`Transport`] can reconstruct these values
// from the other fields
let mut transport = Transport::new(sample_rate);
if !data.context.is_null() {
let context = &*data.context;
// These constants are missing from vst3-sys, see:
// https://steinbergmedia.github.io/vst3_doc/vstinterfaces/structSteinberg_1_1Vst_1_1ProcessContext.html
transport.playing = context.state & (1 << 1) != 0; // kPlaying
transport.recording = context.state & (1 << 3) != 0; // kRecording
if context.state & (1 << 10) != 0 {
// kTempoValid
transport.tempo = Some(context.tempo);
}
if context.state & (1 << 13) != 0 {
// kTimeSigValid
transport.time_sig_numerator = Some(context.time_sig_num);
transport.time_sig_denominator = Some(context.time_sig_den);
}
// We need to compensate for the block splitting here
transport.pos_samples = Some(context.project_time_samples + block_start as i64);
if context.state & (1 << 9) != 0 {
// kProjectTimeMusicValid
if P::SAMPLE_ACCURATE_AUTOMATION
&& block_start > 0
&& (context.state & (1 << 10) != 0)
{
// kTempoValid
transport.pos_beats = Some(
context.project_time_music
+ (block_start as f64 / sample_rate as f64 / 60.0
* context.tempo),
);
} else {
transport.pos_beats = Some(context.project_time_music);
}
}
if context.state & (1 << 11) != 0 {
// kBarPositionValid
if P::SAMPLE_ACCURATE_AUTOMATION && block_start > 0 {
// The transport object knows how to recompute this from the other information
transport.bar_start_pos_beats = match transport.bar_start_pos_beats() {
Some(updated) => Some(updated),
None => Some(context.bar_position_music),
};
} else {
transport.bar_start_pos_beats = Some(context.bar_position_music);
}
}
if context.state & (1 << 2) != 0 && context.state & (1 << 12) != 0 {
// kCycleActive && kCycleValid
transport.loop_range_beats =
Some((context.cycle_start_music, context.cycle_end_music));
}
}
let result = if buffer_is_valid {
// NOTE: `parking_lot`'s mutexes sometimes allocate because of their use of
// thread locals
let mut plugin = permit_alloc(|| self.inner.plugin.lock());
// 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 *(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);
self.inner.last_process_status.store(result);
result
} else { } else {
ProcessStatus::Normal // After processing the events we now know where/if the block should be split,
// and we can start preparing audio processing
let block_len = block_end - block_start;
// The buffer manager preallocated buffer slices for all the IO and storage for
// any axuiliary inputs.
let mut buffer_manager = self.inner.buffer_manager.borrow_mut();
let buffers = buffer_manager.create_buffers(block_len, |buffer_source| {
if data.num_outputs > 0
&& !data.outputs.is_null()
&& !(*data.outputs).buffers.is_null()
&& has_main_output
{
let audio_output = &*data.outputs;
let ptrs = NonNull::new(audio_output.buffers as *mut *mut f32).unwrap();
let num_channels = audio_output.num_channels as usize;
*buffer_source.main_output_channel_pointers =
Some(ChannelPointers { ptrs, num_channels });
}
if data.num_inputs > 0
&& !data.inputs.is_null()
&& !(*data.inputs).buffers.is_null()
&& has_main_input
{
let audio_input = &*data.inputs;
let ptrs = NonNull::new(audio_input.buffers as *mut *mut f32).unwrap();
let num_channels = audio_input.num_channels as usize;
*buffer_source.main_input_channel_pointers =
Some(ChannelPointers { ptrs, num_channels });
}
if !data.inputs.is_null() {
for (aux_input_no, aux_input_channel_pointers) in buffer_source
.aux_input_channel_pointers
.iter_mut()
.enumerate()
{
let aux_input_idx = aux_input_no + aux_input_start_idx;
if aux_input_idx > data.num_outputs as usize {
break;
}
let audio_input = &*data.inputs.add(aux_input_idx);
match NonNull::new(audio_input.buffers as *mut *mut f32) {
Some(ptrs) => {
let num_channels = audio_input.num_channels as usize;
*aux_input_channel_pointers =
Some(ChannelPointers { ptrs, num_channels });
}
None => continue,
}
}
}
if !data.outputs.is_null() {
for (aux_output_no, aux_output_channel_pointers) in buffer_source
.aux_output_channel_pointers
.iter_mut()
.enumerate()
{
let aux_output_idx = aux_output_no + aux_output_start_idx;
if aux_output_idx > data.num_outputs as usize {
break;
}
let audio_output = &*data.outputs.add(aux_output_idx);
match NonNull::new(audio_output.buffers as *mut *mut f32) {
Some(ptrs) => {
let num_channels = audio_output.num_channels as usize;
*aux_output_channel_pointers =
Some(ChannelPointers { ptrs, num_channels });
}
None => continue,
}
}
}
});
// We already checked whether the host has initiated a parameter flush, but in
// case it still did something unexpected that we did not catch we'll still try
// to prevent processing audio when the slices don't contain the values we
// expect.
let mut buffer_is_valid = true;
for output_buffer_slice in
buffers.main_buffer.as_slice_immutable().iter().chain(
buffers
.aux_outputs
.iter()
.flat_map(|buffer| buffer.as_slice_immutable().iter()),
)
{
if output_buffer_slice.is_empty() {
buffer_is_valid = false;
break;
}
}
nih_debug_assert!(buffer_is_valid);
// Some of the fields are left empty because VST3 does not provide this
// information, but the methods on [`Transport`] can reconstruct these values
// from the other fields
let mut transport = Transport::new(sample_rate);
if !data.context.is_null() {
let context = &*data.context;
// These constants are missing from vst3-sys, see:
// https://steinbergmedia.github.io/vst3_doc/vstinterfaces/structSteinberg_1_1Vst_1_1ProcessContext.html
transport.playing = context.state & (1 << 1) != 0; // kPlaying
transport.recording = context.state & (1 << 3) != 0; // kRecording
if context.state & (1 << 10) != 0 {
// kTempoValid
transport.tempo = Some(context.tempo);
}
if context.state & (1 << 13) != 0 {
// kTimeSigValid
transport.time_sig_numerator = Some(context.time_sig_num);
transport.time_sig_denominator = Some(context.time_sig_den);
}
// We need to compensate for the block splitting here
transport.pos_samples =
Some(context.project_time_samples + block_start as i64);
if context.state & (1 << 9) != 0 {
// kProjectTimeMusicValid
if P::SAMPLE_ACCURATE_AUTOMATION
&& block_start > 0
&& (context.state & (1 << 10) != 0)
{
// kTempoValid
transport.pos_beats = Some(
context.project_time_music
+ (block_start as f64 / sample_rate as f64 / 60.0
* context.tempo),
);
} else {
transport.pos_beats = Some(context.project_time_music);
}
}
if context.state & (1 << 11) != 0 {
// kBarPositionValid
if P::SAMPLE_ACCURATE_AUTOMATION && block_start > 0 {
// The transport object knows how to recompute this from the other information
transport.bar_start_pos_beats =
match transport.bar_start_pos_beats() {
Some(updated) => Some(updated),
None => Some(context.bar_position_music),
};
} else {
transport.bar_start_pos_beats = Some(context.bar_position_music);
}
}
if context.state & (1 << 2) != 0 && context.state & (1 << 12) != 0 {
// kCycleActive && kCycleValid
transport.loop_range_beats =
Some((context.cycle_start_music, context.cycle_end_music));
}
}
let result = if buffer_is_valid {
// NOTE: `parking_lot`'s mutexes sometimes allocate because of their use of
// thread locals
let mut plugin = permit_alloc(|| self.inner.plugin.lock());
let mut aux = AuxiliaryBuffers {
inputs: buffers.aux_inputs,
outputs: buffers.aux_outputs,
};
let mut context = self.inner.make_process_context(transport);
let result = plugin.process(buffers.main_buffer, &mut aux, &mut context);
self.inner.last_process_status.store(result);
result
} else {
ProcessStatus::Normal
};
match result {
ProcessStatus::Error(err) => {
nih_debug_assert_failure!("Process error: {}", err);
return kResultFalse;
}
_ => kResultOk,
}
}; };
// Send any events output by the plugin during the process cycle // Send any events output by the plugin during the process cycle
if let Some(events) = data.output_events.upgrade() { if let Some(events) = data.output_events.upgrade() {
let mut output_events = self.inner.output_events.borrow_mut(); let mut output_events = self.inner.output_events.borrow_mut();
while let Some(event) = output_events.pop_front() { while let Some(event) = output_events.pop_front() {
// We'll set the correct variant on this struct, or skip to the next // We'll set the correct variant on this struct, or skip to the next loop
// loop iteration if we don't handle the event type // iteration if we don't handle the event type
let mut vst3_event: Event = mem::zeroed(); let mut vst3_event: Event = mem::zeroed();
vst3_event.bus_index = 0; vst3_event.bus_index = 0;
// There's also a ppqPos field, but uh how about no // There's also a ppqPos field, but uh how about no
@ -1561,8 +1446,8 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
tuning: 0.0, tuning: 0.0,
velocity, velocity,
length: 0, // What? length: 0, // What?
// We'll use this for our note IDs, that way we don't have // We'll use this for our note IDs, that way we don't have to do
// to do anything complicated here // anything complicated here
note_id: voice_id note_id: voice_id
.unwrap_or_else(|| ((channel as i32) << 8) | note as i32), .unwrap_or_else(|| ((channel as i32) << 8) | note as i32),
}; };
@ -1750,15 +1635,6 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
} }
} }
let result = match result {
ProcessStatus::Error(err) => {
nih_debug_assert_failure!("Process error: {}", err);
return kResultFalse;
}
_ => kResultOk,
};
// If our block ends at the end of the buffer then that means there are no more // If our block ends at the end of the buffer then that means there are no more
// unprocessed (parameter) events. If there are more events, we'll just keep going // unprocessed (parameter) events. If there are more events, we'll just keep going
// through this process until we've processed the entire buffer. // through this process until we've processed the entire buffer.