Expand description
The host side of the Clack extension system.
The majority of the features in CLAP come from extensions, including parameters management, state loading and saving, GUI handling, and more.
The goal of this system is to maximize flexibility and extensibility, while also preventing feature creep by not requiring all hosts to implement every single extension. This module provides thin wrappers that maintain those abilities while also enforcing both type safety and memory safety.
This crate does not include any extension itself. All official, first-party CLAP extensions are
implemented on top of it in the clack-extensions crate, but all extension implementations are
treated as first-class citizens by both CLAP and Clack, regardless of their provenance. See the
Creating custom extensions section below for more information on
how to implement third-party extensions.
At instantiation time, both the plugin and the host will query each other’s declared supported
extensions. When one side declares supporting an extension, it will also provide an Extension
object containing that extension’s ABI to the other side. See the Extension type
documentation for more information on how to use and store them.
Extension ABIs are split in two parts: one that is exposed by the host, and one that is
exposed by the plugin. For instance, for the Latency extension, the ABIs are named HostLatency
and PluginLatency respectively.
§Using extensions in a Clack host
Supporting a specific extension in a CLAP host has two requirements:
-
Querying a plugin for its side of the ABI, and consuming it.
This is the most straightforward part: once the plugin is instantiated and the host can access its
PluginSharedHandle, it can use thePluginSharedHandle::get_extensionmethod to query the plugin for any supported extension, and store its associated ABI.References to an Extension ABI can be shared, copied and used in any thread as long as they don’t outlive the plugin instance. They are therefore most commonly stored in the host’s
HostSharedassociated type, as shown in the example below. -
Implementing the host side of the ABI, and exposing it to the plugin to be queried.
All extensions in Clack have at least one trait to be implemented onto a specific
Hostsubtype (HostMainThread,HostAudioProcessor, orHostShared), depending on the thread specification of the ABI’s method. For example, theLogextension’s ABI has to be fully thread-safe, therefore theHostLogImpltrait has to be implemented on theHostSharedtype.See the
hostmodule documentation to know more about CLAP’s thread specification.Sometimes however, some ABIs expose different methods in different thread classes, leading to that many traits to be implemented on different types. For instance, the
ParamsABI exposes one thread-safe method and two that are main-thread only. Therefore, theHostParamsImplSharedandHostParamsImplMainThreadtraits have to be implemented on theHostSharedandHostMainThreadtypes, respectively.Once this is all done, the host implementation can declare this extension by using the
HostExtensions::registermethod in theHost::declare_extensionsmethod implementation.The fact that the right traits are implemented on the right
Hostassociated types is automatically checked at compile time, upon calling theHostExtensions::registermethod.
§Example
This example implements a host supporting the Latency extension.
use std::cell::{Cell, OnceCell};
use std::sync::OnceLock;
use clack_host::prelude::*;
use clack_extensions::latency::*;
#[derive(Default)]
struct MyHostShared {
// Queried extension
// Note this may be None even after instantiation,
// in case the extension isn't supported by the plugin.
latency_extension: OnceLock<Option<PluginLatency>>
}
impl<'a> SharedHandler<'a> for MyHostShared {
// We can query the plugin's extensions as soon as the plugin starts initializing
fn initializing(&self, instance: InitializingPluginHandle<'a>) {
let _ = self.latency_extension.set(instance.get_extension());
}
/* ... */
}
struct MyHostMainThread<'a> {
shared: &'a MyHostShared,
instance: OnceCell<InitializedPluginHandle<'a>>,
// The latency that is sent to us by the plugin's Latency extension.
latency_changed: Cell<bool>
}
impl<'a> MainThreadHandler<'a> for MyHostMainThread<'a> {
// The plugin's instance handle is required to call extension methods.
fn initialized(&self, instance: InitializedPluginHandle<'a>) {
self.instance.set(instance).unwrap();
}
}
impl<'a> HostLatencyImpl for MyHostMainThread<'a> {
// This method is called by the plugin whenever its latency changed.
fn changed(&self) {
// Ensure that the plugin is instantiated and supports the Latency extension.
if let Some(Some(_latency)) = self.shared.latency_extension.get() {
self.latency_changed.set(true)
}
}
}
struct MyHost;
impl HostHandlers for MyHost {
type Shared<'a> = MyHostShared;
type MainThread<'a> = MyHostMainThread<'a>;
type AudioProcessor<'a> = ();
fn declare_extensions(builder: &mut HostExtensions<Self>, shared: &Self::Shared<'_>) {
builder.register::<HostLatency>();
}
}§Example
use clack_host::extensions::prelude::*;
use clap_sys::ext::latency::{clap_host_latency, clap_plugin_latency, CLAP_EXT_LATENCY};
use std::ffi::CStr;
/// The type we will receive from a plugin implementing the Latency extension
#[derive(Copy, Clone)]
pub struct PluginLatency(RawExtension<PluginExtensionSide, clap_plugin_latency>);
// Mark this type as being the plugin side of an extension, and tie it to its ID
unsafe impl Extension for PluginLatency {
const IDENTIFIERS: &[&CStr] = &[CLAP_EXT_LATENCY];
type ExtensionSide = PluginExtensionSide;
#[inline]
unsafe fn from_raw(raw: RawExtension<Self::ExtensionSide>) -> Self {
Self(unsafe { raw.cast() })
}
}
/// The type we will expose to a plugin by implementing the Latency extension
#[derive(Copy, Clone)]
pub struct HostLatency(RawExtension<HostExtensionSide, clap_host_latency>);
// Mark this type as being the host side of an extension, and tie it to its ID
// SAFETY: CLAP_EXT_LATENCY is the ID of the clap_host_latency extension, defined by the CLAP spec
unsafe impl Extension for HostLatency {
const IDENTIFIERS: &[&CStr] = &[CLAP_EXT_LATENCY];
type ExtensionSide = HostExtensionSide;
#[inline]
unsafe fn from_raw(raw: RawExtension<Self::ExtensionSide>) -> Self {
// SAFETY: the guarantee that this pointer is of the correct type is upheld by the caller.
unsafe { Self(raw.cast()) }
}
}
// Implement calling to the plugin-side
impl PluginLatency {
// The `clap_plugin_latency.get` function requires to be called on the `[main-thread]`.
// Therefore, we will require the `PluginMainThreadHandle` to be passed.
#[inline]
pub fn get(&self, plugin: &PluginMainThreadHandle) -> u32 {
match plugin.use_extension(&self.0).get {
None => 0,
Some(get) => unsafe { get(plugin.as_raw()) },
}
}
}
/// Provides the implementation of the host-side to be called by the plugin.
pub trait HostLatencyImpl {
fn changed(&self);
}
// SAFETY: The given struct is the CLAP extension struct for the matching side of this extension.
unsafe impl<H: for<'a> HostHandlers<MainThread<'a>: HostLatencyImpl>> ExtensionImplementation<H> for HostLatency
{
const IMPLEMENTATION: RawExtensionImplementation =
RawExtensionImplementation::new(&clap_host_latency {
changed: Some(changed::<H>),
});
}
unsafe extern "C" fn changed<H: for<'a> HostHandlers<MainThread<'a>: HostLatencyImpl>>(host: *const clap_host)
{
HostWrapper::<H>::handle_main_thread(host, |host| {
host.changed();
Ok(())
});
}Modules§
- prelude
- A prelude which re-exports all the types and traits used for custom extension implementation.
- wrapper
- Helper utilities to help implementing the host side of custom CLAP extensions.
Structs§
- Host
Extension Side - A marker struct that represents extensions to be implemented by the host side.
- Plugin
Extension Side - A marker struct that represents extensions to be implemented by the plugin side.
- RawExtension
- A raw extension pointer.
- RawExtension
Implementation - A pointer to an extension’s implementation struct.
Traits§
- Extension
- A type representing a CLAP extension pointer.
- Extension
Implementation - Provides an implementation of this extension for a given type
I(typically either a host or plugin structure). - Extension
Side - An extension side marker: either
PluginExtensionSideorHostExtensionSide.