Support exporting multiple CLAP plugins
This required the factory to be rewritten as a macro. since variadic generics are not yet a thing. Not 100% satisfied with this design yet, but it's much less ugly than my earlier attempts at this.
This commit is contained in:
parent
ff89e25d2f
commit
3ccaced613
12
CHANGELOG.md
12
CHANGELOG.md
|
@ -10,6 +10,18 @@ Since there is no stable release yet, the changes are organized per day in
|
||||||
reverse chronological order. The main purpose of this document in its current
|
reverse chronological order. The main purpose of this document in its current
|
||||||
state is to list breaking changes.
|
state is to list breaking changes.
|
||||||
|
|
||||||
|
## [2023-08-05]
|
||||||
|
|
||||||
|
### Breaking changes
|
||||||
|
|
||||||
|
- The minimum supported Rust version has been bumped to 1.70 so we can start
|
||||||
|
using `OnceCell` and `OnceLock` to phase out uses of `lazy_static`.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- `nih_export_clap!()` can now take more than one plugin type argument to allow
|
||||||
|
exporting more than one plugin from a single plugin library.
|
||||||
|
|
||||||
## [2023-05-13]
|
## [2023-05-13]
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
name = "nih_plug"
|
name = "nih_plug"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.63"
|
rust-version = "1.70"
|
||||||
authors = ["Robbert van der Helm <mail@robbertvanderhelm.nl>"]
|
authors = ["Robbert van der Helm <mail@robbertvanderhelm.nl>"]
|
||||||
license = "ISC"
|
license = "ISC"
|
||||||
|
|
||||||
|
|
|
@ -3,31 +3,114 @@ mod util;
|
||||||
|
|
||||||
mod context;
|
mod context;
|
||||||
mod descriptor;
|
mod descriptor;
|
||||||
mod factory;
|
|
||||||
pub mod features;
|
pub mod features;
|
||||||
mod wrapper;
|
mod wrapper;
|
||||||
|
|
||||||
/// Re-export for the wrapper.
|
/// Re-export for the macro
|
||||||
pub use self::factory::Factory;
|
pub use self::descriptor::PluginDescriptor;
|
||||||
|
pub use self::wrapper::Wrapper;
|
||||||
pub use clap_sys::entry::clap_plugin_entry;
|
pub use clap_sys::entry::clap_plugin_entry;
|
||||||
pub use clap_sys::factory::plugin_factory::CLAP_PLUGIN_FACTORY_ID;
|
pub use clap_sys::factory::plugin_factory::{clap_plugin_factory, CLAP_PLUGIN_FACTORY_ID};
|
||||||
|
pub use clap_sys::host::clap_host;
|
||||||
|
pub use clap_sys::plugin::{clap_plugin, clap_plugin_descriptor};
|
||||||
pub use clap_sys::version::CLAP_VERSION;
|
pub use clap_sys::version::CLAP_VERSION;
|
||||||
pub use lazy_static::lazy_static;
|
pub use lazy_static::lazy_static;
|
||||||
|
|
||||||
/// Export a CLAP plugin from this library using the provided plugin type.
|
/// Export a CLAP plugin from this library using the provided plugin type.
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! nih_export_clap {
|
macro_rules! nih_export_clap {
|
||||||
($plugin_ty:ty) => {
|
($($plugin_ty:ty),+) => {
|
||||||
// We need a function pointer to a [wrapper::get_factory()] that creates a factory for `$plugin_ty`, so we need to generate the function inside of this macro
|
// 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)]
|
#[doc(hidden)]
|
||||||
mod clap {
|
mod clap {
|
||||||
// Because `$plugin_ty` is likely defined in the enclosing scope
|
// 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::*;
|
use super::*;
|
||||||
|
|
||||||
// We don't use generics inside of statics, so this lazy_static is used as kind of an
|
const CLAP_PLUGIN_FACTORY: ::nih_plug::wrapper::clap::clap_plugin_factory =
|
||||||
// escape hatch
|
::nih_plug::wrapper::clap::clap_plugin_factory {
|
||||||
::nih_plug::wrapper::clap::lazy_static! {
|
get_plugin_count: Some(get_plugin_count),
|
||||||
static ref FACTORY: ::nih_plug::wrapper::clap::Factory<$plugin_ty> = ::nih_plug::wrapper::clap::Factory::default();
|
get_plugin_descriptor: Some(get_plugin_descriptor),
|
||||||
|
create_plugin: Some(create_plugin),
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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
|
||||||
|
static PLUGIN_DESCRIPTORS: ::std::sync::OnceLock<
|
||||||
|
[::nih_plug::wrapper::clap::PluginDescriptor; PLUGIN_COUNT]
|
||||||
|
> = ::std::sync::OnceLock::new();
|
||||||
|
|
||||||
|
fn plugin_descriptors() -> &'static [::nih_plug::wrapper::clap::PluginDescriptor; PLUGIN_COUNT] {
|
||||||
|
PLUGIN_DESCRIPTORS.get_or_init(|| {
|
||||||
|
let descriptors = [$(::nih_plug::wrapper::clap::PluginDescriptor::for_plugin::<$plugin_ty>()),+];
|
||||||
|
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
let unique_plugin_ids: std::collections::HashSet<_>
|
||||||
|
= descriptors.iter().map(|d| d.clap_id()).collect();
|
||||||
|
::nih_plug::debug::nih_debug_assert_eq!(
|
||||||
|
unique_plugin_ids.len(),
|
||||||
|
descriptors.len(),
|
||||||
|
"Duplicate plugin IDs found in `nih_export_clap!()` call"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
descriptors
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe extern "C" fn get_plugin_count(
|
||||||
|
_factory: *const ::nih_plug::wrapper::clap::clap_plugin_factory,
|
||||||
|
) -> u32 {
|
||||||
|
plugin_descriptors().len() as u32
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe extern "C" fn get_plugin_descriptor (
|
||||||
|
_factory: *const ::nih_plug::wrapper::clap::clap_plugin_factory,
|
||||||
|
index: u32,
|
||||||
|
) -> *const ::nih_plug::wrapper::clap::clap_plugin_descriptor {
|
||||||
|
match plugin_descriptors().get(index as usize) {
|
||||||
|
Some(descriptor) => descriptor.clap_plugin_descriptor(),
|
||||||
|
None => std::ptr::null()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe extern "C" fn create_plugin (
|
||||||
|
factory: *const ::nih_plug::wrapper::clap::clap_plugin_factory,
|
||||||
|
host: *const ::nih_plug::wrapper::clap::clap_host,
|
||||||
|
plugin_id: *const ::std::os::raw::c_char,
|
||||||
|
) -> *const ::nih_plug::wrapper::clap::clap_plugin {
|
||||||
|
if plugin_id.is_null() {
|
||||||
|
return ::std::ptr::null();
|
||||||
|
}
|
||||||
|
let plugin_id_cstr = ::std::ffi::CStr::from_ptr(plugin_id);
|
||||||
|
|
||||||
|
// This isn't great, but we'll just assume that `$plugin_ids` and the descriptors
|
||||||
|
// are in the same order. We also can't directly enumerate over them with an index,
|
||||||
|
// which is why we do things the way we do. Otherwise we could have used a tuple
|
||||||
|
// instead.
|
||||||
|
let descriptors = plugin_descriptors();
|
||||||
|
let mut descriptor_idx = 0;
|
||||||
|
$({
|
||||||
|
let descriptor = &descriptors[descriptor_idx];
|
||||||
|
if plugin_id_cstr == descriptor.clap_id() {
|
||||||
|
// Arc does not have a convenient leak function like Box, so this gets a bit awkward
|
||||||
|
// This pointer gets turned into an Arc and its reference count decremented in
|
||||||
|
// [Wrapper::destroy()]
|
||||||
|
return (*Arc::into_raw(::nih_plug::wrapper::clap::Wrapper::<$plugin_ty>::new(host)))
|
||||||
|
.clap_plugin
|
||||||
|
.as_ptr();
|
||||||
|
}
|
||||||
|
|
||||||
|
descriptor_idx += 1;
|
||||||
|
})+
|
||||||
|
|
||||||
|
std::ptr::null()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub extern "C" fn init(_plugin_path: *const ::std::os::raw::c_char) -> bool {
|
pub extern "C" fn init(_plugin_path: *const ::std::os::raw::c_char) -> bool {
|
||||||
|
@ -44,7 +127,7 @@ macro_rules! nih_export_clap {
|
||||||
&& unsafe { ::std::ffi::CStr::from_ptr(factory_id) }
|
&& unsafe { ::std::ffi::CStr::from_ptr(factory_id) }
|
||||||
== ::nih_plug::wrapper::clap::CLAP_PLUGIN_FACTORY_ID
|
== ::nih_plug::wrapper::clap::CLAP_PLUGIN_FACTORY_ID
|
||||||
{
|
{
|
||||||
&*FACTORY as *const _ as *const ::std::ffi::c_void
|
&CLAP_PLUGIN_FACTORY as *const _ as *const ::std::ffi::c_void
|
||||||
} else {
|
} else {
|
||||||
std::ptr::null()
|
std::ptr::null()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
use clap_sys::plugin::clap_plugin_descriptor;
|
use clap_sys::plugin::clap_plugin_descriptor;
|
||||||
use clap_sys::version::CLAP_VERSION;
|
use clap_sys::version::CLAP_VERSION;
|
||||||
use std::ffi::{CStr, CString};
|
use std::ffi::{CStr, CString};
|
||||||
use std::marker::PhantomData;
|
|
||||||
use std::os::raw::c_char;
|
use std::os::raw::c_char;
|
||||||
|
|
||||||
use crate::prelude::ClapPlugin;
|
use crate::prelude::ClapPlugin;
|
||||||
|
@ -11,7 +10,7 @@ use crate::prelude::ClapPlugin;
|
||||||
///
|
///
|
||||||
/// This cannot be cloned as [`Self::clap_features_ptrs`] contains pointers to
|
/// This cannot be cloned as [`Self::clap_features_ptrs`] contains pointers to
|
||||||
/// [Self::clap_features].
|
/// [Self::clap_features].
|
||||||
pub struct PluginDescriptor<P: ClapPlugin> {
|
pub struct PluginDescriptor {
|
||||||
// We need [CString]s for all of `ClapPlugin`'s `&str` fields
|
// We need [CString]s for all of `ClapPlugin`'s `&str` fields
|
||||||
clap_id: CString,
|
clap_id: CString,
|
||||||
name: CString,
|
name: CString,
|
||||||
|
@ -29,13 +28,14 @@ pub struct PluginDescriptor<P: ClapPlugin> {
|
||||||
/// descriptor upfront. We also need to initialize the `CString` fields above first before we
|
/// descriptor upfront. We also need to initialize the `CString` fields above first before we
|
||||||
/// can initialize this plugin descriptor.
|
/// can initialize this plugin descriptor.
|
||||||
plugin_descriptor: Option<clap_plugin_descriptor>,
|
plugin_descriptor: Option<clap_plugin_descriptor>,
|
||||||
|
|
||||||
/// The plugin's type.
|
|
||||||
_phantom: PhantomData<P>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P: ClapPlugin> Default for PluginDescriptor<P> {
|
unsafe impl Send for PluginDescriptor {}
|
||||||
fn default() -> Self {
|
unsafe impl Sync for PluginDescriptor {}
|
||||||
|
|
||||||
|
impl PluginDescriptor {
|
||||||
|
/// Construct the plugin descriptor for a specific CLAP plugin.
|
||||||
|
pub fn for_plugin<P: ClapPlugin>() -> Self {
|
||||||
let mut descriptor = Self {
|
let mut descriptor = Self {
|
||||||
clap_id: CString::new(P::CLAP_ID).expect("`CLAP_ID` contained null bytes"),
|
clap_id: CString::new(P::CLAP_ID).expect("`CLAP_ID` contained null bytes"),
|
||||||
name: CString::new(P::NAME).expect("`NAME` contained null bytes"),
|
name: CString::new(P::NAME).expect("`NAME` contained null bytes"),
|
||||||
|
@ -59,8 +59,6 @@ impl<P: ClapPlugin> Default for PluginDescriptor<P> {
|
||||||
// descriptor
|
// descriptor
|
||||||
clap_features_ptrs: Vec::new(),
|
clap_features_ptrs: Vec::new(),
|
||||||
plugin_descriptor: None,
|
plugin_descriptor: None,
|
||||||
|
|
||||||
_phantom: PhantomData,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// The keyword list is an environ-like list of char pointers terminated by a null pointer.
|
// The keyword list is an environ-like list of char pointers terminated by a null pointer.
|
||||||
|
@ -101,12 +99,7 @@ impl<P: ClapPlugin> Default for PluginDescriptor<P> {
|
||||||
|
|
||||||
descriptor
|
descriptor
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl<P: ClapPlugin> Send for PluginDescriptor<P> {}
|
|
||||||
unsafe impl<P: ClapPlugin> Sync for PluginDescriptor<P> {}
|
|
||||||
|
|
||||||
impl<P: ClapPlugin> PluginDescriptor<P> {
|
|
||||||
pub fn clap_plugin_descriptor(&self) -> &clap_plugin_descriptor {
|
pub fn clap_plugin_descriptor(&self) -> &clap_plugin_descriptor {
|
||||||
self.plugin_descriptor.as_ref().unwrap()
|
self.plugin_descriptor.as_ref().unwrap()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,75 +0,0 @@
|
||||||
use clap_sys::factory::plugin_factory::clap_plugin_factory;
|
|
||||||
use clap_sys::host::clap_host;
|
|
||||||
use clap_sys::plugin::{clap_plugin, clap_plugin_descriptor};
|
|
||||||
use std::ffi::CStr;
|
|
||||||
use std::os::raw::c_char;
|
|
||||||
use std::ptr;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use super::descriptor::PluginDescriptor;
|
|
||||||
use super::wrapper::Wrapper;
|
|
||||||
use crate::prelude::ClapPlugin;
|
|
||||||
|
|
||||||
/// The plugin's factory. Initialized using a lazy_static from the entry point's `get_factory()`
|
|
||||||
/// function. From this point onwards we don't need to generate code with macros anymore.
|
|
||||||
#[doc(hidden)]
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct Factory<P: ClapPlugin> {
|
|
||||||
// Keep the vtable as the first field so we can do a simple pointer cast. There's no data
|
|
||||||
// pointer as the API expects this thing to be entirely static, which in our case it isn't.
|
|
||||||
pub clap_plugin_factory: clap_plugin_factory,
|
|
||||||
|
|
||||||
plugin_descriptor: PluginDescriptor<P>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P: ClapPlugin> Default for Factory<P> {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
clap_plugin_factory: clap_plugin_factory {
|
|
||||||
get_plugin_count: Some(Self::get_plugin_count),
|
|
||||||
get_plugin_descriptor: Some(Self::get_plugin_descriptor),
|
|
||||||
create_plugin: Some(Self::create_plugin),
|
|
||||||
},
|
|
||||||
plugin_descriptor: PluginDescriptor::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P: ClapPlugin> Factory<P> {
|
|
||||||
unsafe extern "C" fn get_plugin_count(_factory: *const clap_plugin_factory) -> u32 {
|
|
||||||
1
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe extern "C" fn get_plugin_descriptor(
|
|
||||||
factory: *const clap_plugin_factory,
|
|
||||||
index: u32,
|
|
||||||
) -> *const clap_plugin_descriptor {
|
|
||||||
let factory = &*(factory as *const Self);
|
|
||||||
|
|
||||||
if index == 0 {
|
|
||||||
factory.plugin_descriptor.clap_plugin_descriptor()
|
|
||||||
} else {
|
|
||||||
ptr::null()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe extern "C" fn create_plugin(
|
|
||||||
factory: *const clap_plugin_factory,
|
|
||||||
host: *const clap_host,
|
|
||||||
plugin_id: *const c_char,
|
|
||||||
) -> *const clap_plugin {
|
|
||||||
let factory = &*(factory as *const Self);
|
|
||||||
|
|
||||||
if !plugin_id.is_null() && CStr::from_ptr(plugin_id) == factory.plugin_descriptor.clap_id()
|
|
||||||
{
|
|
||||||
// Arc does not have a convenient leak function like Box, so this gets a bit awkward
|
|
||||||
// This pointer gets turned into an Arc and its reference count decremented in
|
|
||||||
// [Wrapper::destroy()]
|
|
||||||
(*Arc::into_raw(Wrapper::<P>::new(host)))
|
|
||||||
.clap_plugin
|
|
||||||
.as_ptr()
|
|
||||||
} else {
|
|
||||||
ptr::null()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -174,7 +174,7 @@ pub struct Wrapper<P: ClapPlugin> {
|
||||||
pub clap_plugin: AtomicRefCell<clap_plugin>,
|
pub clap_plugin: AtomicRefCell<clap_plugin>,
|
||||||
/// Needs to be boxed because the plugin object is supposed to contain a static reference to
|
/// Needs to be boxed because the plugin object is supposed to contain a static reference to
|
||||||
/// this.
|
/// this.
|
||||||
_plugin_descriptor: Box<PluginDescriptor<P>>,
|
_plugin_descriptor: Box<PluginDescriptor>,
|
||||||
|
|
||||||
clap_plugin_audio_ports: clap_plugin_audio_ports,
|
clap_plugin_audio_ports: clap_plugin_audio_ports,
|
||||||
|
|
||||||
|
@ -433,7 +433,8 @@ impl<P: ClapPlugin> Wrapper<P> {
|
||||||
// on `Self::updated_state_sender`
|
// on `Self::updated_state_sender`
|
||||||
let (updated_state_sender, updated_state_receiver) = channel::bounded(0);
|
let (updated_state_sender, updated_state_receiver) = channel::bounded(0);
|
||||||
|
|
||||||
let plugin_descriptor: Box<PluginDescriptor<P>> = Box::default();
|
let plugin_descriptor: Box<PluginDescriptor> =
|
||||||
|
Box::new(PluginDescriptor::for_plugin::<P>());
|
||||||
|
|
||||||
// We're not allowed to query any extensions until the init function has been called, so we
|
// We're not allowed to query any extensions until the init function has been called, so we
|
||||||
// need a bunch of AtomicRefCells instead
|
// need a bunch of AtomicRefCells instead
|
||||||
|
|
Loading…
Reference in a new issue