From b9d38f5c39b3332b6c3d561ddabc5584fb2c3e2b Mon Sep 17 00:00:00 2001 From: Robbert van der Helm Date: Fri, 28 Jan 2022 13:40:47 +0100 Subject: [PATCH] Implement most of IAudioProcessor Except for the process function itself. --- src/plugin.rs | 8 ++- src/wrapper/vst3.rs | 169 +++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 163 insertions(+), 14 deletions(-) diff --git a/src/plugin.rs b/src/plugin.rs index d2b50c7d..a0b56144 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -27,6 +27,8 @@ use crate::params::Params; /// - MIDI /// - Sidechain inputs /// - Multiple output busses +/// - Latency compensation +/// - Special handling for offline processing /// - Storing custom state, only the parameters are saved right now /// - Sample accurate automation (this would be great, but sadly few hosts even support it so until /// they do we'll ignore that it's a thing) @@ -100,7 +102,7 @@ pub trait Vst3Plugin: Plugin { } /// We only support a single main input and output bus at the moment. -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct BusConfig { /// The number of input channels for the plugin. pub num_input_channels: u32, @@ -109,7 +111,7 @@ pub struct BusConfig { } /// Configuration for (the host's) audio buffers. -#[derive(Debug, PartialEq)] +#[derive(Debug, Clone, PartialEq)] pub struct BufferConfig { /// The current sample rate. pub sample_rate: f32, @@ -119,7 +121,7 @@ pub struct BufferConfig { } /// Indicates the current situation after the plugin has processed audio. -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ProcessStatus { /// Something went wrong while processing audio. Error(&'static str), diff --git a/src/wrapper/vst3.rs b/src/wrapper/vst3.rs index 8f94796c..950dbbea 100644 --- a/src/wrapper/vst3.rs +++ b/src/wrapper/vst3.rs @@ -24,15 +24,16 @@ use std::collections::HashMap; use std::ffi::c_void; use std::marker::PhantomData; use std::mem; +use vst3_com::base::kResultTrue; use vst3_sys::base::{kInvalidArgument, kNoInterface, kResultFalse, kResultOk, tresult, TBool}; use vst3_sys::base::{IPluginBase, IPluginFactory, IPluginFactory2, IPluginFactory3}; use vst3_sys::vst::TChar; -use vst3_sys::vst::{IComponent, IEditController}; +use vst3_sys::vst::{IAudioProcessor, IComponent, IEditController}; use vst3_sys::VST3; use widestring::U16CStr; use crate::params::ParamPtr; -use crate::plugin::{BusConfig, Plugin, Vst3Plugin}; +use crate::plugin::{BufferConfig, BusConfig, Plugin, ProcessStatus, Vst3Plugin}; use crate::wrapper::util::{hash_param_id, strlcpy, u16strlcpy}; // Alias needed for the VST3 attribute macro @@ -51,13 +52,15 @@ lazy_static! { static ref BYPASS_PARAM_HASH: u32 = hash_param_id(BYPASS_PARAM_ID); } -#[VST3(implements(IComponent, IEditController))] +#[VST3(implements(IComponent, IEditController, IAudioProcessor))] pub struct Wrapper { /// The wrapped plugin instance. - plugin: P, + plugin: RefCell

, /// Whether the plugin is currently bypassed. This is not yet integrated with the `Plugin` /// trait. bypass_state: Cell, + /// The last process status returned by the plugin. This is used for tail handling. + last_process_status: Cell, /// A mapping from parameter ID hashes (obtained from the string parameter IDs) to pointers to /// parameters belonging to the plugin. As long as `plugin` does not get recreated, these @@ -79,12 +82,13 @@ pub struct Wrapper { impl Wrapper

{ pub fn new() -> Box { let mut wrapper = Self::allocate( - P::default(), // plugin - Cell::new(false), // bypass_state - HashMap::new(), // param_by_hash - Vec::new(), // param_hashes - Vec::new(), // param_defaults_normalized - HashMap::new(), // param_id_hashes + RefCell::new(P::default()), // plugin + Cell::new(false), // bypass_state + Cell::new(ProcessStatus::Normal), // last_process_status + HashMap::new(), // param_by_hash + Vec::new(), // param_hashes + Vec::new(), // param_defaults_normalized + HashMap::new(), // param_id_hashes // Some hosts, like the current version of Bitwig and Ardour at the time of writing, // will try using the plugin's default not yet initialized bus arrangement. Because of // that, we'll always initialize this configuration even before the host requests a @@ -98,7 +102,7 @@ impl Wrapper

{ // This is a mapping from the parameter IDs specified by the plugin to pointers to thsoe // parameters. Since the object returned by `params()` is pinned, these pointers are safe to // dereference as long as `wrapper.plugin` is alive - let param_map = wrapper.plugin.params().param_map(); + let param_map = wrapper.plugin.borrow().params().param_map(); nih_debug_assert!( !param_map.contains_key(BYPASS_PARAM_ID), "The wrapper alread yadds its own bypass parameter" @@ -460,6 +464,149 @@ impl IEditController for Wrapper

{ } } +impl IAudioProcessor for Wrapper

{ + unsafe fn set_bus_arrangements( + &self, + inputs: *mut vst3_sys::vst::SpeakerArrangement, + num_ins: i32, + outputs: *mut vst3_sys::vst::SpeakerArrangement, + num_outs: i32, + ) -> tresult { + if inputs.is_null() || outputs.is_null() { + nih_debug_assert_failure!("Null pointer passed to function"); + return kInvalidArgument; + } + + // We currently only do single audio bus IO configurations + if num_ins != 1 || num_outs != 1 { + return kInvalidArgument; + } + + let input_channel_map = &*inputs; + let output_channel_map = &*outputs; + let proposed_config = BusConfig { + num_input_channels: input_channel_map.count_ones(), + num_output_channels: output_channel_map.count_ones(), + }; + if self.plugin.borrow().accepts_bus_config(&proposed_config) { + self.current_bus_config.replace(proposed_config); + + kResultOk + } else { + kResultFalse + } + } + + unsafe fn get_bus_arrangement( + &self, + dir: vst3_sys::vst::BusDirection, + index: i32, + arr: *mut vst3_sys::vst::SpeakerArrangement, + ) -> tresult { + if arr.is_null() { + nih_debug_assert_failure!("Null pointer passed to function"); + return kInvalidArgument; + } + + let config = self.current_bus_config.borrow(); + match (dir, index) { + (d, 0) if d == vst3_sys::vst::BusDirections::kInput as i32 => { + let channel_map = match config.num_input_channels { + 0 => vst3_sys::vst::kEmpty, + 1 => vst3_sys::vst::kMono, + 2 => vst3_sys::vst::kStereo, + 5 => vst3_sys::vst::k50, + 6 => vst3_sys::vst::k51, + 7 => vst3_sys::vst::k70Cine, + 8 => vst3_sys::vst::k71Cine, + n => { + nih_debug_assert_failure!( + "No defined layout for {} channels, making something up on the spot...", + n + ); + (1 << n) - 1 + } + }; + + nih_debug_assert_eq!(config.num_input_channels, channel_map.count_ones()); + *arr = channel_map; + + kResultOk + } + _ => kInvalidArgument, + } + } + + unsafe fn can_process_sample_size(&self, symbolic_sample_size: i32) -> tresult { + if symbolic_sample_size == vst3_sys::vst::SymbolicSampleSizes::kSample32 as i32 { + kResultOk + } else { + kResultFalse + } + } + + unsafe fn get_latency_samples(&self) -> u32 { + // TODO: Latency compensation + 0 + } + + unsafe fn setup_processing(&self, setup: *const vst3_sys::vst::ProcessSetup) -> tresult { + if setup.is_null() { + nih_debug_assert_failure!("Null pointer passed to function"); + return kInvalidArgument; + } + + // There's no special handling for offline processing at the moment + let setup = &*setup; + nih_debug_assert_eq!( + setup.symbolic_sample_size, + vst3_sys::vst::SymbolicSampleSizes::kSample32 as i32 + ); + + let bus_config = self.current_bus_config.borrow(); + let buffer_config = BufferConfig { + sample_rate: setup.sample_rate as f32, + max_buffer_size: setup.max_samples_per_block as u32, + }; + + if self + .plugin + .borrow_mut() + .initialize(&bus_config, &buffer_config) + { + kResultOk + } else { + kResultFalse + } + } + + unsafe fn set_processing(&self, _state: TBool) -> tresult { + // Always reset the processing status when the plugin gets activated or deactivated + self.last_process_status.set(ProcessStatus::Normal); + + // We don't have any special handling for suspending and resuming plugins, yet + kResultTrue + } + + unsafe fn process(&self, data: *mut vst3_sys::vst::ProcessData) -> tresult { + if data.is_null() { + nih_debug_assert_failure!("Null pointer passed to function"); + return kInvalidArgument; + } + + todo!() + } + + unsafe fn get_tail_samples(&self) -> u32 { + // https://github.com/steinbergmedia/vst3_pluginterfaces/blob/2ad397ade5b51007860bedb3b01b8afd2c5f6fba/vst/ivstaudioprocessor.h#L145-L159 + match self.last_process_status.get() { + ProcessStatus::Tail(samples) => samples, + ProcessStatus::KeepAlive => u32::MAX, // kInfiniteTail + _ => 0, // kNoTail + } + } +} + #[VST3(implements(IPluginFactory, IPluginFactory2, IPluginFactory3))] pub struct Factory { /// The exposed plugin's GUID. Instead of generating this, we'll just let the programmer decide