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
this change NIH-plug's buffer management has been refactored to reuse the same
logic in all of its wrappers.
- Any outstanding VST3 output events are now sent to the host during a parameter
flush.
## [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::view::WrapperView;
use crate::audio_setup::{AudioIOLayout, BufferConfig, ProcessMode};
use crate::buffer::Buffer;
use crate::context::gui::AsyncExecutor;
use crate::context::process::Transport;
use crate::editor::Editor;
@ -26,6 +25,7 @@ use crate::params::{ParamFlags, Params};
use crate::plugin::{Plugin, ProcessStatus, TaskExecutor, Vst3Plugin};
use crate::util::permit_alloc;
use crate::wrapper::state::{self, PluginState};
use crate::wrapper::util::buffer_management::BufferManager;
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.
@ -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
/// [`ProcessContext`].
pub current_latency: AtomicU32,
/// Contains slices for the plugin's outputs. You can't directly create a nested slice from
/// a pointer to pointers, so this needs to be preallocated in the setup call and kept around
/// 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>>>,
/// A data structure that helps manage and create buffers for all of the plugin's inputs and
/// outputs based on channel pointers provided by the host.
pub buffer_manager: AtomicRefCell<BufferManager>,
/// 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.
@ -317,10 +303,12 @@ impl<P: Vst3Plugin> WrapperInner<P> {
current_process_mode: AtomicCell::new(ProcessMode::Realtime),
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()),
// This is initialized just before calling `Plugin::initialize()` so that during the
// process call buffers can be initialized without any allocations
buffer_manager: AtomicRefCell::new(BufferManager::for_audio_io_layout(
0,
AudioIOLayout::default(),
)),
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

@ -1,9 +1,8 @@
use std::borrow::Borrow;
use std::cmp;
use std::ffi::c_void;
use std::mem::{self, MaybeUninit};
use std::num::NonZeroU32;
use std::ptr;
use std::ptr::NonNull;
use std::sync::atomic::Ordering;
use std::sync::Arc;
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::view::WrapperView;
use crate::audio_setup::{AuxiliaryBuffers, BufferConfig, ProcessMode};
use crate::buffer::Buffer;
use crate::context::process::Transport;
use crate::midi::sysex::SysExMessage;
use crate::midi::{MidiConfig, NoteEvent};
@ -36,6 +34,7 @@ use crate::params::ParamFlags;
use crate::plugin::{ProcessStatus, Vst3Plugin};
use crate::util::permit_alloc;
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};
// 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
// 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
// f32` to a `&mut [&mut f32]` in the process call
self.inner
.output_buffer
.borrow_mut()
.set_slices(0, |output_slices| {
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 []);
});
}
// This preallocates enough space so we can transform all of the host's raw
// channel pointers into a set of `Buffer` objects for the plugin's main and
// auxiliary IO
*self.inner.buffer_manager.borrow_mut() = BufferManager::for_audio_io_layout(
buffer_config.max_buffer_size as usize,
audio_io_layout,
);
kResultOk
} else {
@ -473,7 +423,11 @@ impl<P: Vst3Plugin> IComponent for Wrapper<P> {
let mut eof_pos = 0;
if state.tell(&mut current_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");
return kResultFalse;
@ -744,7 +698,7 @@ impl<P: Vst3Plugin> IEditController for Wrapper<P> {
match self.inner.editor.borrow().as_ref() {
Some(editor) => Box::into_raw(WrapperView::new(self.inner.clone(), editor.clone()))
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;
// 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 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 aux_input_start_idx = if has_main_input { 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 {
let host_output = data.outputs.add(output_idx);
if !(*host_output).buffers.is_null() {
for channel_idx in 0..(*host_output).num_channels as isize {
ptr::write_bytes(
*((*host_output).buffers.offset(channel_idx)) as *mut f32,
0,
total_buffer_len,
);
}
}
}
// NOTE: VST3 hosts may trigger a 'parameter flush' by calling the process function for
// 0 input samples. If this is the case then we'll only handle events and skip all
// audio processing. Some hosts, like Ableton Live, implement this in a broken way
// and instead only set the number of channels to 0. In that case the
// 'buffer_is_valid' check from below should still prevent audio processing.
let mut is_param_flush = total_buffer_len == 0;
if (data.num_outputs == 0 || data.outputs.is_null())
&& (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
@ -1271,270 +1221,205 @@ impl<P: Vst3Plugin> IAudioProcessor for Wrapper<P> {
}
}
// This vector has been preallocated to contain enough slices as there are output
// channels. In case the does does not provide an output or if they don't provide
// 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
let result = if is_param_flush {
kResultOk
} 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
if let Some(events) = data.output_events.upgrade() {
let mut output_events = self.inner.output_events.borrow_mut();
while let Some(event) = output_events.pop_front() {
// We'll set the correct variant on this struct, or skip to the next
// loop iteration if we don't handle the event type
// We'll set the correct variant on this struct, or skip to the next loop
// iteration if we don't handle the event type
let mut vst3_event: Event = mem::zeroed();
vst3_event.bus_index = 0;
// 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,
velocity,
length: 0, // What?
// We'll use this for our note IDs, that way we don't have
// to do anything complicated here
// We'll use this for our note IDs, that way we don't have to do
// anything complicated here
note_id: voice_id
.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
// unprocessed (parameter) events. If there are more events, we'll just keep going
// through this process until we've processed the entire buffer.