diff --git a/CHANGELOG.md b/CHANGELOG.md index a01b8d67..b389ee87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,11 @@ state is to list breaking changes. ## [2023-09-03] +### Added + +- `nih_export_vst3!()` now also supports more than one plugin type argument, + just like `nih_export_clap!()`. + ### Fixed - The `nih_export_*!()` macros now use `$crate` to refer to NIH-plug itself, diff --git a/src/wrapper/clap.rs b/src/wrapper/clap.rs index cfa310fa..b969154b 100644 --- a/src/wrapper/clap.rs +++ b/src/wrapper/clap.rs @@ -47,9 +47,7 @@ macro_rules! nih_export_clap { // Sneaky way to get the number of expanded elements const PLUGIN_COUNT: usize = [$(stringify!($plugin_ty)),+].len(); - // We'll put these plugin descriptors in a tuple since we can't easily associate them - // with indices without involving even more macros. We can't initialize this tuple - // completely statically + // This is a type erased version of the information stored on the plugin types static PLUGIN_DESCRIPTORS: OnceLock<[PluginDescriptor; PLUGIN_COUNT]> = OnceLock::new(); fn plugin_descriptors() -> &'static [PluginDescriptor; PLUGIN_COUNT] { diff --git a/src/wrapper/vst3.rs b/src/wrapper/vst3.rs index 6121b60d..e2938dae 100644 --- a/src/wrapper/vst3.rs +++ b/src/wrapper/vst3.rs @@ -11,16 +11,180 @@ mod view; mod wrapper; /// Re-export for the wrapper. -pub use factory::Factory; +pub use factory::PluginInfo; +pub use vst3_sys; +pub use wrapper::Wrapper; -/// Export a VST3 plugin from this library using the provided plugin type. +/// Export one or more VST3 plugins from this library using the provided plugin types. The first +/// plugin's vendor information is used for the factory's information. #[macro_export] macro_rules! nih_export_vst3 { - ($plugin_ty:ty) => { + ($($plugin_ty:ty),+) => { + // Earlier versions used a simple generic struct for this, but because we don't have + // variadic generics (yet) we can't generate the struct for multiple plugin types without + // macros. So instead we'll generate the implementation ad-hoc inside of this macro. + #[doc(hidden)] + mod vst3 { + use ::std::collections::HashSet; + + // `vst3_sys` is imported from the VST3 wrapper module + use $crate::wrapper::vst3::{vst3_sys, PluginInfo, Wrapper}; + use vst3_sys::base::{kInvalidArgument, kResultOk, tresult}; + use vst3_sys::base::{ + FactoryFlags, IPluginFactory, IPluginFactory2, IPluginFactory3, IUnknown, + PClassInfo, PClassInfo2, PClassInfoW, PFactoryInfo, + }; + use vst3_sys::VST3; + + // This alias is needed for the VST3 attribute macro + use vst3_sys as vst3_com; + + // Because the `$plugin_ty`s are likely defined in the enclosing scope. This works even + // if the types are not public because this is a child module. + use super::*; + + // Sneaky way to get the number of expanded elements + const PLUGIN_COUNT: usize = [$(stringify!($plugin_ty)),+].len(); + + #[doc(hidden)] + #[VST3(implements(IPluginFactory, IPluginFactory2, IPluginFactory3))] + pub struct Factory { + // This is a type erased version of the information stored on the plugin types + plugin_infos: [PluginInfo; PLUGIN_COUNT], + } + + impl Factory { + pub fn new() -> Box { + let plugin_infos = [$(PluginInfo::for_plugin::<$plugin_ty>()),+]; + + if cfg!(debug_assertions) { + let unique_cids: HashSet<[u8; 16]> = plugin_infos.iter().map(|d| *d.cid).collect(); + nih_debug_assert_eq!( + unique_cids.len(), + plugin_infos.len(), + "Duplicate VST3 class IDs found in `nih_export_vst3!()` call" + ); + } + + Self::allocate(plugin_infos) + } + } + + impl IPluginFactory for Factory { + unsafe fn get_factory_info(&self, info: *mut PFactoryInfo) -> tresult { + if info.is_null() { + return kInvalidArgument; + } + + // We'll use the first plugin's info for this + *info = self.plugin_infos[0].create_factory_info(); + + kResultOk + } + + unsafe fn count_classes(&self) -> i32 { + self.plugin_infos.len() as i32 + } + + unsafe fn get_class_info(&self, index: i32, info: *mut PClassInfo) -> tresult { + if index < 0 || index >= self.plugin_infos.len() as i32 { + return kInvalidArgument; + } + + *info = self.plugin_infos[index as usize].create_class_info(); + + kResultOk + } + + unsafe fn create_instance( + &self, + cid: *const vst3_sys::IID, + iid: *const vst3_sys::IID, + obj: *mut *mut vst3_sys::c_void, + ) -> tresult { + // Can't use `check_null_ptr!()` here without polluting NIH-plug's general + // exports + if cid.is_null() || obj.is_null() { + return kInvalidArgument; + } + + // This is a poor man's way of treating `$plugin_ty` like an indexable array. + // Assuming `self.plugin_infos` is in the same order, we can simply check all of + // the registered plugin CIDs for matches using an unrolled loop. + let mut plugin_idx = 0; + $({ + let plugin_info = &self.plugin_infos[plugin_idx]; + if (*cid).data == *plugin_info.cid { + let wrapper = Wrapper::<$plugin_ty>::new(); + + // 99.999% of the times `iid` will be that of `IComponent`, but the + // caller is technically allowed to create an object for any support + // interface. We don't have a way to check whether our plugin supports + // the interface without creating it, but since the odds that a caller + // will create an object with an interface we don't support are + // basically zero this is not a problem. + let result = wrapper.query_interface(iid, obj); + if result == kResultOk { + // This is a bit awkward now but if the cast succeeds we need to get + // rid of the reference from the `wrapper` binding. The VST3 query + // interface always increments the reference count and returns an + // owned reference, so we need to explicitly release the reference + // from `wrapper` and leak the `Box` so the wrapper doesn't + // automatically get deallocated when this function returns (`Box` + // is an incorrect choice on vst3-sys' part, it should have used a + // `VstPtr` instead). + wrapper.release(); + Box::leak(wrapper); + + return kResultOk; + } + } + + plugin_idx += 1; + })+ + + kInvalidArgument + } + } + + impl IPluginFactory2 for Factory { + unsafe fn get_class_info2(&self, index: i32, info: *mut PClassInfo2) -> tresult { + if index < 0 || index >= self.plugin_infos.len() as i32 { + return kInvalidArgument; + } + + *info = self.plugin_infos[index as usize].create_class_info_2(); + + kResultOk + } + } + + impl IPluginFactory3 for Factory { + unsafe fn get_class_info_unicode( + &self, + index: i32, + info: *mut PClassInfoW, + ) -> tresult { + if index < 0 || index >= self.plugin_infos.len() as i32 { + return kInvalidArgument; + } + + *info = self.plugin_infos[index as usize].create_class_info_unicode(); + + kResultOk + } + + unsafe fn set_host_context(&self, _context: *mut vst3_sys::c_void) -> tresult { + // We don't need to do anything with this + kResultOk + } + } + } + /// The VST3 plugin factory entry point. #[no_mangle] pub extern "system" fn GetPluginFactory() -> *mut ::std::ffi::c_void { - let factory = $crate::wrapper::vst3::Factory::<$plugin_ty>::new(); + let factory = self::vst3::Factory::new(); Box::into_raw(factory) as *mut ::std::ffi::c_void } diff --git a/src/wrapper/vst3/factory.rs b/src/wrapper/vst3/factory.rs index 57eac767..4dad87a3 100644 --- a/src/wrapper/vst3/factory.rs +++ b/src/wrapper/vst3/factory.rs @@ -1,166 +1,111 @@ -use std::ffi::c_void; -use std::marker::PhantomData; -use std::mem; -use vst3_sys::base::{kInvalidArgument, kResultOk, tresult}; -use vst3_sys::base::{IPluginFactory, IPluginFactory2, IPluginFactory3, IUnknown}; -use vst3_sys::VST3; +//! Utilities for building a VST3 factory. +//! +//! In order to support exporting multiple VST3 plugins from a single library a lot of functionality +//! had to be moved to the `nih_export_vst3!()` macro. Because working in macro-land can be both +//! frustrating and error prone, most code that does not specifically depend on all of the exposed +//! plugin types was moved back to this module so it can be compiled and type checked as normal. -// Alias needed for the VST3 attribute macro -use vst3_sys as vst3_com; +use vst3_sys::base::{ + ClassCardinality, FactoryFlags, PClassInfo, PClassInfo2, PClassInfoW, PFactoryInfo, +}; use super::subcategories::Vst3SubCategory; -use super::util::u16strlcpy; -use super::wrapper::Wrapper; use crate::prelude::Vst3Plugin; use crate::wrapper::util::strlcpy; +use crate::wrapper::vst3::util::u16strlcpy; /// The VST3 SDK version this is roughly based on. The bindings include some VST 3.7 things but not /// everything, so we'll play it safe. -const VST3_SDK_VERSION: &str = "VST 3.6.14"; +pub const VST3_SDK_VERSION: &str = "VST 3.6.14"; -#[doc(hidden)] -#[VST3(implements(IPluginFactory, IPluginFactory2, IPluginFactory3))] -pub struct Factory { - /// The type will be used for constructing plugin instances later. - _phantom: PhantomData

, +/// The information queried about a plugin through the `IPluginFactory*` methods. Stored in a +/// separate struct so it can be type erased and stored in an array. +#[derive(Debug)] +pub struct PluginInfo { + pub cid: &'static [u8; 16], + pub name: &'static str, + pub subcategories: String, + pub vendor: &'static str, + pub version: &'static str, + + // These are used for the factory's own info struct + pub url: &'static str, + pub email: &'static str, } -impl Factory

{ - pub fn new() -> Box { - Self::allocate(PhantomData) - } -} - -impl IPluginFactory for Factory

{ - unsafe fn get_factory_info(&self, info: *mut vst3_sys::base::PFactoryInfo) -> tresult { - *info = mem::zeroed(); - - let info = &mut *info; - strlcpy(&mut info.vendor, P::VENDOR); - strlcpy(&mut info.url, P::URL); - strlcpy(&mut info.email, P::EMAIL); - info.flags = vst3_sys::base::FactoryFlags::kUnicode as i32; - - kResultOk - } - - unsafe fn count_classes(&self) -> i32 { - // We don't do shell plugins, and good of an idea having separated components and edit - // controllers in theory is, few software can use it, and doing that would make our simple - // microframework a lot less simple - 1 - } - - unsafe fn get_class_info(&self, index: i32, info: *mut vst3_sys::base::PClassInfo) -> tresult { - if index != 0 { - return kInvalidArgument; +impl PluginInfo { + pub fn for_plugin() -> PluginInfo { + PluginInfo { + cid: &P::PLATFORM_VST3_CLASS_ID, + name: P::NAME, + subcategories: make_subcategories_string::

(), + vendor: P::VENDOR, + version: P::VERSION, + url: P::URL, + email: P::EMAIL, } + } - *info = mem::zeroed(); + /// Fill a [`PFactoryInfo`] struct with the information from this library. Used in + /// `IPluginFactory`. + pub fn create_factory_info(&self) -> PFactoryInfo { + let mut info: PFactoryInfo = unsafe { std::mem::zeroed() }; + strlcpy(&mut info.vendor, self.vendor); + strlcpy(&mut info.url, self.url); + strlcpy(&mut info.email, self.email); + info.flags = FactoryFlags::kUnicode as i32; - let info = &mut *info; - info.cid.data = P::PLATFORM_VST3_CLASS_ID; - info.cardinality = vst3_sys::base::ClassCardinality::kManyInstances as i32; + info + } + + /// Fill a [`PClassInfo`] struct with the information from this library. Used in + /// `IPluginFactory`. + pub fn create_class_info(&self) -> PClassInfo { + let mut info: PClassInfo = unsafe { std::mem::zeroed() }; + info.cid.data = *self.cid; + info.cardinality = ClassCardinality::kManyInstances as i32; strlcpy(&mut info.category, "Audio Module Class"); - strlcpy(&mut info.name, P::NAME); + strlcpy(&mut info.name, self.name); - kResultOk + info } - unsafe fn create_instance( - &self, - cid: *const vst3_sys::IID, - iid: *const vst3_sys::IID, - obj: *mut *mut vst3_sys::c_void, - ) -> tresult { - check_null_ptr!(cid, obj); - - if (*cid).data != P::PLATFORM_VST3_CLASS_ID { - return kInvalidArgument; - } - - let wrapper = Wrapper::

::new(); - - // 99.999% of the times `iid` will be that of `IComponent`, but the caller is technically - // allowed to create an object for any support interface. We don't have a way to check - // whether our plugin supports the interface without creating it, but since the odds that a - // caller will create an object with an interface we don't support are basically zero this - // is not a problem. - let result = wrapper.query_interface(iid, obj); - if result == kResultOk { - // This is a bit awkward now but if the cast succeeds we need to get rid of the - // reference from the `wrapper` binding. The VST3 query interface always increments the - // reference count and returns an owned reference, so we need to explicitly release the - // reference from `wrapper` and leak the `Box` so the wrapper doesn't automatically get - // deallocated when this function returns (`Box` is an incorrect choice on vst3-sys' - // part, it should have used a `VstPtr` instead). - wrapper.release(); - Box::leak(wrapper); - } - - result - } -} - -impl IPluginFactory2 for Factory

{ - unsafe fn get_class_info2( - &self, - index: i32, - info: *mut vst3_sys::base::PClassInfo2, - ) -> tresult { - if index != 0 { - return kInvalidArgument; - } - - *info = mem::zeroed(); - - let info = &mut *info; - info.cid.data = P::PLATFORM_VST3_CLASS_ID; - info.cardinality = vst3_sys::base::ClassCardinality::kManyInstances as i32; + /// Fill a [`PClassInfo2`] struct with the information from this library. Used in + /// `IPluginFactory2`. + pub fn create_class_info_2(&self) -> PClassInfo2 { + let mut info: PClassInfo2 = unsafe { std::mem::zeroed() }; + info.cid.data = *self.cid; + info.cardinality = ClassCardinality::kManyInstances as i32; strlcpy(&mut info.category, "Audio Module Class"); - strlcpy(&mut info.name, P::NAME); + strlcpy(&mut info.name, self.name); info.class_flags = 1 << 1; // kSimpleModeSupported - strlcpy(&mut info.subcategories, &make_subcategories_string::

()); - strlcpy(&mut info.vendor, P::VENDOR); - strlcpy(&mut info.version, P::VERSION); + strlcpy(&mut info.subcategories, &self.subcategories); + strlcpy(&mut info.vendor, self.vendor); + strlcpy(&mut info.version, self.version); strlcpy(&mut info.sdk_version, VST3_SDK_VERSION); - kResultOk + info } -} -impl IPluginFactory3 for Factory

{ - unsafe fn get_class_info_unicode( - &self, - index: i32, - info: *mut vst3_sys::base::PClassInfoW, - ) -> tresult { - if index != 0 { - return kInvalidArgument; - } - - *info = mem::zeroed(); - - let info = &mut *info; - info.cid.data = P::PLATFORM_VST3_CLASS_ID; - info.cardinality = vst3_sys::base::ClassCardinality::kManyInstances as i32; + /// Fill a [`PClassInfoW`] struct with the information from this library. Used in + /// `IPluginFactory3`. + pub fn create_class_info_unicode(&self) -> PClassInfoW { + let mut info: PClassInfoW = unsafe { std::mem::zeroed() }; + info.cid.data = *self.cid; + info.cardinality = ClassCardinality::kManyInstances as i32; strlcpy(&mut info.category, "Audio Module Class"); - u16strlcpy(&mut info.name, P::NAME); + u16strlcpy(&mut info.name, self.name); info.class_flags = 1 << 1; // kSimpleModeSupported - strlcpy(&mut info.subcategories, &make_subcategories_string::

()); - u16strlcpy(&mut info.vendor, P::VENDOR); - u16strlcpy(&mut info.version, P::VERSION); + strlcpy(&mut info.subcategories, &self.subcategories); + u16strlcpy(&mut info.vendor, self.vendor); + u16strlcpy(&mut info.version, self.version); u16strlcpy(&mut info.sdk_version, VST3_SDK_VERSION); - kResultOk - } - - unsafe fn set_host_context(&self, _context: *mut c_void) -> tresult { - // We don't need to do anything with this - kResultOk + info } } +/// Build a pipe separated subcategories string for a VST3 plugin. fn make_subcategories_string() -> String { // No idea if any hosts do something with OnlyRT, but it's part of VST3's example categories // list. Plugins should not be adding this feature manually diff --git a/src/wrapper/vst3/wrapper.rs b/src/wrapper/vst3/wrapper.rs index 090e3df8..f284af54 100644 --- a/src/wrapper/vst3/wrapper.rs +++ b/src/wrapper/vst3/wrapper.rs @@ -47,7 +47,7 @@ use vst3_sys as vst3_com; IProcessContextRequirements, IUnitInfo ))] -pub(crate) struct Wrapper { +pub struct Wrapper { inner: Arc>, }