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
|
||||
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]
|
||||
|
||||
### Fixed
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
name = "nih_plug"
|
||||
version = "0.0.0"
|
||||
edition = "2021"
|
||||
rust-version = "1.63"
|
||||
rust-version = "1.70"
|
||||
authors = ["Robbert van der Helm <mail@robbertvanderhelm.nl>"]
|
||||
license = "ISC"
|
||||
|
||||
|
|
|
@ -3,31 +3,114 @@ mod util;
|
|||
|
||||
mod context;
|
||||
mod descriptor;
|
||||
mod factory;
|
||||
pub mod features;
|
||||
mod wrapper;
|
||||
|
||||
/// Re-export for the wrapper.
|
||||
pub use self::factory::Factory;
|
||||
/// Re-export for the macro
|
||||
pub use self::descriptor::PluginDescriptor;
|
||||
pub use self::wrapper::Wrapper;
|
||||
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 lazy_static::lazy_static;
|
||||
|
||||
/// Export a CLAP plugin from this library using the provided plugin type.
|
||||
#[macro_export]
|
||||
macro_rules! nih_export_clap {
|
||||
($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
|
||||
($($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 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::*;
|
||||
|
||||
// We don't use generics inside of statics, so this lazy_static is used as kind of an
|
||||
// escape hatch
|
||||
::nih_plug::wrapper::clap::lazy_static! {
|
||||
static ref FACTORY: ::nih_plug::wrapper::clap::Factory<$plugin_ty> = ::nih_plug::wrapper::clap::Factory::default();
|
||||
const CLAP_PLUGIN_FACTORY: ::nih_plug::wrapper::clap::clap_plugin_factory =
|
||||
::nih_plug::wrapper::clap::clap_plugin_factory {
|
||||
get_plugin_count: Some(get_plugin_count),
|
||||
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 {
|
||||
|
@ -44,7 +127,7 @@ macro_rules! nih_export_clap {
|
|||
&& unsafe { ::std::ffi::CStr::from_ptr(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 {
|
||||
std::ptr::null()
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
use clap_sys::plugin::clap_plugin_descriptor;
|
||||
use clap_sys::version::CLAP_VERSION;
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::marker::PhantomData;
|
||||
use std::os::raw::c_char;
|
||||
|
||||
use crate::prelude::ClapPlugin;
|
||||
|
@ -11,7 +10,7 @@ use crate::prelude::ClapPlugin;
|
|||
///
|
||||
/// This cannot be cloned as [`Self::clap_features_ptrs`] contains pointers to
|
||||
/// [Self::clap_features].
|
||||
pub struct PluginDescriptor<P: ClapPlugin> {
|
||||
pub struct PluginDescriptor {
|
||||
// We need [CString]s for all of `ClapPlugin`'s `&str` fields
|
||||
clap_id: 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
|
||||
/// can initialize this plugin descriptor.
|
||||
plugin_descriptor: Option<clap_plugin_descriptor>,
|
||||
|
||||
/// The plugin's type.
|
||||
_phantom: PhantomData<P>,
|
||||
}
|
||||
|
||||
impl<P: ClapPlugin> Default for PluginDescriptor<P> {
|
||||
fn default() -> Self {
|
||||
unsafe impl Send for PluginDescriptor {}
|
||||
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 {
|
||||
clap_id: CString::new(P::CLAP_ID).expect("`CLAP_ID` 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
|
||||
clap_features_ptrs: Vec::new(),
|
||||
plugin_descriptor: None,
|
||||
|
||||
_phantom: PhantomData,
|
||||
};
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
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>,
|
||||
/// Needs to be boxed because the plugin object is supposed to contain a static reference to
|
||||
/// this.
|
||||
_plugin_descriptor: Box<PluginDescriptor<P>>,
|
||||
_plugin_descriptor: Box<PluginDescriptor>,
|
||||
|
||||
clap_plugin_audio_ports: clap_plugin_audio_ports,
|
||||
|
||||
|
@ -433,7 +433,8 @@ impl<P: ClapPlugin> Wrapper<P> {
|
|||
// on `Self::updated_state_sender`
|
||||
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
|
||||
// need a bunch of AtomicRefCells instead
|
||||
|
|
Loading…
Reference in a new issue