1
0
Fork 0

Support multiple plugins in nih_export_vst3!()

Just like in `nih_export_clap!()`.
This commit is contained in:
Robbert van der Helm 2023-09-03 15:43:23 +02:00
parent b9b30feb86
commit 19988db139
5 changed files with 252 additions and 140 deletions

View file

@ -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,

View file

@ -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] {

View file

@ -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<Self> {
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
}

View file

@ -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<P: Vst3Plugin> {
/// The type will be used for constructing plugin instances later.
_phantom: PhantomData<P>,
/// 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<P: Vst3Plugin> Factory<P> {
pub fn new() -> Box<Self> {
Self::allocate(PhantomData)
}
}
impl<P: Vst3Plugin> IPluginFactory for Factory<P> {
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<P: Vst3Plugin>() -> PluginInfo {
PluginInfo {
cid: &P::PLATFORM_VST3_CLASS_ID,
name: P::NAME,
subcategories: make_subcategories_string::<P>(),
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::<P>::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<P: Vst3Plugin> IPluginFactory2 for Factory<P> {
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::<P>());
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<P: Vst3Plugin> IPluginFactory3 for Factory<P> {
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::<P>());
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<P: Vst3Plugin>() -> 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

View file

@ -47,7 +47,7 @@ use vst3_sys as vst3_com;
IProcessContextRequirements,
IUnitInfo
))]
pub(crate) struct Wrapper<P: Vst3Plugin> {
pub struct Wrapper<P: Vst3Plugin> {
inner: Arc<WrapperInner<P>>,
}