From 0242cc5235e8509b4d936a555166e508c7c80b0d Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Sat, 28 May 2022 00:10:16 +0200 Subject: [PATCH] Also implement auxiliary IO for VST3 --- src/wrapper/clap/wrapper.rs | 4 +- src/wrapper/vst3/inner.rs | 15 +++ src/wrapper/vst3/wrapper.rs | 178 ++++++++++++++++++++++++++++++++---- 3 files changed, 175 insertions(+), 22 deletions(-) diff --git a/src/wrapper/clap/wrapper.rs b/src/wrapper/clap/wrapper.rs index ec2da59a..b42c53e2 100644 --- a/src/wrapper/clap/wrapper.rs +++ b/src/wrapper/clap/wrapper.rs @@ -1821,13 +1821,13 @@ impl Wrapper

{ // 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 diff --git a/src/wrapper/vst3/inner.rs b/src/wrapper/vst3/inner.rs index f18584d7..6063e7fa 100644 --- a/src/wrapper/vst3/inner.rs +++ b/src/wrapper/vst3/inner.rs @@ -78,6 +78,18 @@ pub(crate) struct WrapperInner { /// 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>, + /// 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>>>, + /// 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>>, + /// Buffers for auxiliary plugin outputs, if the plugin has any. These reference the host's + /// memory directly. + pub aux_output_buffers: AtomicRefCell>>, /// 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 WrapperInner

{ 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()), diff --git a/src/wrapper/vst3/wrapper.rs b/src/wrapper/vst3/wrapper.rs index bd351339..b6855c9f 100644 --- a/src/wrapper/vst3/wrapper.rs +++ b/src/wrapper/vst3/wrapper.rs @@ -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 IComponent for Wrapper

{ .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 IAudioProcessor for Wrapper

{ 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 IAudioProcessor for Wrapper

{ .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 IAudioProcessor for Wrapper

{ } }); + 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 IAudioProcessor for Wrapper

{ 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);