Support multiple plugins in nih_export_vst3!()
Just like in `nih_export_clap!()`.
This commit is contained in:
parent
b9b30feb86
commit
19988db139
|
@ -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,
|
||||
|
|
|
@ -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] {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>>,
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue