diff --git a/Cargo.lock b/Cargo.lock index 256b1267..43cf20bd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9,10 +9,17 @@ dependencies = [ "nih_plug", ] +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + [[package]] name = "nih_plug" version = "0.1.0" dependencies = [ + "lazy_static", "nih_plug_derive", "vst3-sys", "widestring", diff --git a/Cargo.toml b/Cargo.toml index a2460ecb..d58c4428 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ members = ["nih_plug_derive", "plugins/gain"] [dependencies] nih_plug_derive = { path = "nih_plug_derive" } +lazy_static = "1.4" # Upstream currently does not support structs with generics and comments vst3-sys = { git = "https://github.com/robbert-vdh/vst3-sys.git", branch = "fix/vst3-macro-generics" } widestring = "1.0.0-beta.1" diff --git a/src/plugin.rs b/src/plugin.rs index ca41ba41..dfd2565f 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -32,6 +32,7 @@ use crate::params::Params; /// they do we'll ignore that it's a thing) /// - Parameter update callbacks /// - Parameter hierarchies/groups +/// - Bypass parameters, right now the VST3 wrapper generates one for you /// - Outputting parameter changes from the plugin /// - GUIs /// - Suspension and tail processing. This will be handled soon in a similar way to how CLAP diff --git a/src/wrapper/vst3.rs b/src/wrapper/vst3.rs index b39377aa..cf42077a 100644 --- a/src/wrapper/vst3.rs +++ b/src/wrapper/vst3.rs @@ -14,35 +14,73 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +use lazy_static::lazy_static; +use std::collections::HashMap; use std::ffi::c_void; use std::marker::PhantomData; use std::mem; -// Alias needed for the VST3 attribute macro -use vst3_sys as vst3_com; use vst3_sys::base::{kInvalidArgument, kNoInterface, kResultFalse, kResultOk, tresult, TBool}; use vst3_sys::base::{IPluginBase, IPluginFactory, IPluginFactory2, IPluginFactory3}; -use vst3_sys::vst::IComponent; +use vst3_sys::vst::TChar; +use vst3_sys::vst::{IComponent, IEditController}; use vst3_sys::VST3; +use crate::params::{ParamPtr, Params}; use crate::plugin::{BusConfig, Plugin}; -use crate::wrapper::util::{strlcpy, u16strlcpy}; +use crate::wrapper::util::{hash_param_id, strlcpy, u16strlcpy}; + +// Alias needed for the VST3 attribute macro +use vst3_sys as vst3_com; /// Re-export for the wrapper. pub use vst3_sys::sys::GUID; /// The VST3 SDK version this is roughtly based on. -const VST3_SDK_VERSION: &'static str = "VST 3.6.14"; +const VST3_SDK_VERSION: &str = "VST 3.6.14"; +/// Right now the wrapper adds its own bypass parameter. +/// +/// TODO: Actually use this parameter. +const BYPASS_PARAM_ID: &str = "bypass"; +lazy_static! { + static ref BYPASS_PARAM_HASH: u32 = hash_param_id(BYPASS_PARAM_ID); +} -#[VST3(implements(IComponent))] +#[VST3(implements(IComponent, IEditController))] pub struct Wrapper { + /// The wrapped plugin instance. plugin: P, + /// Whether the plugin is currently bypassed. This is not yet integrated with the `Plugin` + /// trait. + bypass_state: bool, + + /// A mapping from parameter IDs to pointers to parameters belonging to the plugin. As long as + /// `plugin` does not get recreated, these addresses will remain stable, as they are obtained + /// from a pinned object. + param_map: HashMap<&'static str, ParamPtr>, + /// The keys from `param_map` in a stable order. + param_ids: Vec<&'static str>, + /// Mappings from parameter hashes back to string parameter indentifiers. + /// + /// TODO: To avoid needing two pointer dereferences each time, maybe restructure `param_map` in + /// this wrapper to be indexed by the numerical hash. + param_id_hashes: HashMap, + /// The default normalized parameter value for every parameter in `param_ids`. We need to store + /// this in case the host requeries the parmaeter later. + param_defaults_normalized: Vec, + + /// The current bus configuration, modified through `IAudioProcessor::setBusArrangements()`. current_bus_config: BusConfig, } impl Wrapper

{ pub fn new() -> Box { - Self::allocate( - P::default(), + let mut wrapper = Self::allocate( + P::default(), // plugin + false, // bypass_state + HashMap::new(), // param_map + Vec::new(), // param_ids + HashMap::new(), // param_id_hashes + Vec::new(), // param_defaults_normalized // 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 @@ -51,7 +89,30 @@ impl Wrapper

{ num_input_channels: P::DEFAULT_NUM_INPUTS, num_output_channels: P::DEFAULT_NUM_OUTPUTS, }, - ) + ); + + // 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 + wrapper.param_map = wrapper.plugin.params().param_map(); + wrapper.param_ids = wrapper.param_map.keys().copied().collect(); + wrapper.param_defaults_normalized = wrapper + .param_ids + .iter() + .map(|id| unsafe { wrapper.param_map[id].normalized_value() }) + .collect(); + wrapper.param_id_hashes = wrapper + .param_ids + .iter() + .map(|id| (hash_param_id(id), *id)) + .collect(); + + nih_debug_assert!( + !wrapper.param_map.contains_key(BYPASS_PARAM_ID), + "The wrapper alread yadds its own bypass parameter" + ); + + wrapper } } @@ -177,6 +238,138 @@ impl IComponent for Wrapper

{ } } +impl IEditController for Wrapper

{ + unsafe fn set_component_state(&self, _state: *mut c_void) -> tresult { + // We have a single file component, so we don't need to do anything here + kResultOk + } + + unsafe fn set_state(&self, state: *mut c_void) -> tresult { + // We have a single file component, so there's only one `set_state()` function. Unlike C++, + // Rust allows you to have multiple methods with the same name when they're provided by + // different treats, but because of the Rust implementation the host may call either of + // these functions depending on how they're implemented + IComponent::set_state(self, state) + } + + unsafe fn get_state(&self, state: *mut c_void) -> tresult { + // Same for this function + IComponent::get_state(self, state) + } + + unsafe fn get_parameter_count(&self) -> i32 { + // NOTE: We add a bypass parameter ourselves on index `self.param_ids.len()`, so these + // indices are all off by one + self.param_ids.len() as i32 + 1 + } + + unsafe fn get_parameter_info( + &self, + param_index: i32, + info: *mut vst3_sys::vst::ParameterInfo, + ) -> tresult { + // Parameter index `self.param_ids.len()` is our own bypass parameter + if param_index < 0 || param_index > self.param_ids.len() as i32 { + return kInvalidArgument; + } + + *info = std::mem::zeroed(); + + let info = &mut *info; + if param_index == self.param_ids.len() as i32 { + info.id = hash_param_id(BYPASS_PARAM_ID); + u16strlcpy(&mut info.title, "Bypass"); + u16strlcpy(&mut info.short_title, "Bypass"); + u16strlcpy(&mut info.units, ""); + info.step_count = 0; + info.default_normalized_value = 0.0; + info.unit_id = vst3_sys::vst::kRootUnitId; + info.flags = vst3_sys::vst::ParameterFlags::kCanAutomate as i32 + | vst3_sys::vst::ParameterFlags::kIsBypass as i32; + } else { + let param_id = &self.param_ids[param_index as usize]; + let default_value = &self.param_defaults_normalized[param_index as usize]; + let param_ptr = &self.param_map[param_id]; + + info.id = hash_param_id(param_id); + u16strlcpy(&mut info.title, param_ptr.name()); + u16strlcpy(&mut info.short_title, param_ptr.name()); + u16strlcpy(&mut info.units, param_ptr.unit()); + // TODO: Don't forget this when we add enum parameters + info.step_count = 0; + info.default_normalized_value = *default_value as f64; + info.unit_id = vst3_sys::vst::kRootUnitId; + info.flags = vst3_sys::vst::ParameterFlags::kCanAutomate as i32; + } + + kResultOk + } + + unsafe fn get_param_string_by_value( + &self, + id: u32, + value_normalized: f64, + string: *mut TChar, + ) -> tresult { + // Somehow there's no length there, so we'll assume our own maximum + let dest = &mut *(string as *mut [TChar; 128]); + + if id == *BYPASS_PARAM_HASH { + if value_normalized > 0.5 { + u16strlcpy(dest, "Bypassed") + } else { + u16strlcpy(dest, "Enabled") + } + + kResultOk + } else if self.param_id_hashes.contains_key(&id) { + let param_id = &self.param_id_hashes[&id]; + let param_ptr = &self.param_map[param_id]; + u16strlcpy( + dest, + ¶m_ptr.normalized_value_to_string(value_normalized as f32), + ); + + kResultOk + } else { + kInvalidArgument + } + } + + unsafe fn get_param_value_by_string( + &self, + id: u32, + string: *const TChar, + value_normalized: *mut f64, + ) -> tresult { + todo!() + } + + unsafe fn normalized_param_to_plain(&self, id: u32, value_normalized: f64) -> f64 { + todo!() + } + + unsafe fn plain_param_to_normalized(&self, id: u32, plain_value: f64) -> f64 { + todo!() + } + + unsafe fn get_param_normalized(&self, id: u32) -> f64 { + todo!() + } + + unsafe fn set_param_normalized(&self, id: u32, value: f64) -> tresult { + todo!() + } + + unsafe fn set_component_handler(&self, handler: *mut c_void) -> tresult { + todo!() + } + + unsafe fn create_view(&self, name: vst3_sys::base::FIDString) -> *mut c_void { + todo!() + } +} + #[VST3(implements(IPluginFactory, IPluginFactory2, IPluginFactory3))] pub struct Factory { /// The exposed plugin's GUID. Instead of generating this, we'll just let the programmer decide