Expand description
Core types and traits to implement CLAP host callback handlers.
The HostHandlers trait is the main one required to be implemented for a Clack host. It provides
the host’s supported extensions and is associated to a main type implementing SharedHandler ,
as well as two more optional types implementing MainThreadHandler and AudioProcessorHandler (the
unit type () can be used as default implementations that do nothing).
These three types must implement host interfaces to allow them to respond to various requests from the plugin, following the CLAP thread model described below.
§Thread specifications
CLAP’s thread model for plugins is split into three specifications of operations: those happening in the main thread, those happening in an audio processing thread, and thread-safe operations:
-
The main thread (
[main-thread]in the CLAP specification): this is represented by a type implementing theMainThreadHandlertrait, which is neitherSendnorSync, and lives encapsulated in thePluginInstancestruct.This type can handle all non-realtime-safe operations, such as those related to buffer allocations or GUI handling, and extensions can extend its use to more kinds of operations.
This type is intended to stay on the thread it was created in (i.e. the “main” thread): implementations cannot
Sendthis type to any other threads. -
The audio thread (
[audio-thread]in the CLAP specification): this is represented by a type implementing theAudioProcessorHandlertrait, which isSendbut!Sync, and lives encapsulated in thePluginAudioProcessorstruct. It is only instantiated when theactivatemethod is called, and is dropped ondeactivate. If it isn’t needed ,()can be used instead.This type is designed to handle all DSP-related requests from the plugin and lives in one of the host’s audio threads, of which there may be multiple, if the host uses a thread pool for example.
The host is free to
SendtheAudioProcessorHandlertype between any of its audio threads, but any operation of this class is guaranteed to be called by only a single thread simultaneously (!Sync).One exception is for CLAP plugins’ activation and deactivation (represented in Clack by the plugin type’s construction and destruction), which is guaranteed to happen in the Main Thread instead. This allows an
AudioProcessorHandlerimplementation to receive temporary exclusive references to theMainThreadHandlertype during its construction and destruction, to take and release ownership of extra buffers for instance. -
Thread-safe operations (
[thread-safe]in the CLAP specification) are represented by a type implementing theSharedHandlertrait, which is bothSendandSync, and will be shared between the main thread and the audio thread.It can be used to hold read-only data (such as all the detected plugin extensions), or to hold any other kind of state that is to be synchronized between multiple threads.
However, it should be noted that this type can be used by the plugin simultaneously from threads that are neither the main thread nor the audio thread.
§Example
This example implements a basic host which is able to process callback requests from the plugin,
along with two extensions: latency and log.
This is done by implementing the HostHandlers trait and specifying its associated types:
SharedHandler, MainThreadHandler, and AudioProcessorHandler.
Because our host supports some extensions, we also implement the HostHandlers::declare_extensions
method to declare them to the plugin, which requires us to implement the associated traits on
the appropriate HostHandlers associated types to handle the host-side.
For more information about which extension traits needs to be implemented on which time, refer to that extension’s documentation.
For more information about how to work with extensions, see the
extensions module documentation.
use clack_host::events::event_types::*;
use clack_host::prelude::*;
use clack_extensions::latency::*;
use clack_extensions::log::*;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::OnceLock;
use std::cell::{Cell, OnceCell};
use std::ffi::CStr;
#[derive(Default)]
struct MyHostShared {
// A real-world implementation may use a fancier notification system.
// For this example, we are simply checking a handful of atomics from time to time.
restart_requested: AtomicBool,
process_requested: AtomicBool,
callback_requested: AtomicBool,
// Queried extensions
// This may be none if the extension isn't supported by the plugin.
latency_extension: OnceLock<Option<PluginLatency>>
}
impl<'a> SharedHandler<'a> for MyHostShared {
// Once the plugin is initializing, we can query its extensions
fn initializing(&self, instance: InitializingPluginHandle<'a>) {
let _ = self.latency_extension.set(instance.get_extension());
}
fn request_restart(&self) { self.restart_requested.store(true, Ordering::SeqCst) }
fn request_process(&self) { self.process_requested.store(true, Ordering::SeqCst) }
fn request_callback(&self) { self.callback_requested.store(true, Ordering::SeqCst) }
}
impl HostLogImpl for MyHostShared {
fn log(&self, severity: LogSeverity, message: &str) {
// A real-world implementation would make sure this is wait-free.
// But for this example, println! is good enough.
println!("[{severity}] [Plugin] {message}")
}
}
struct MyHostMainThread<'a> {
shared: &'a MyHostShared,
instance: OnceCell<InitializedPluginHandle<'a>>,
latency_changed: Cell<bool>
}
impl<'a> MainThreadHandler<'a> for MyHostMainThread<'a> {
fn initialized(&self, instance: InitializedPluginHandle<'a>) {
self.instance.set(instance);
}
}
impl<'a> HostLatencyImpl for MyHostMainThread<'a> {
fn changed(&self) {
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::<HostLog>()
.register::<HostLatency>();
}
}
// Information about our totally legit host.
let host_info = HostInfo::new("Legit Studio", "Legit Ltd.", "https://example.com", "4.3.2")?;
let entry = PluginEntry::load("/home/user/.clap/u-he/libdiva.so")?;
let mut plugin_instance = PluginInstance::<MyHost>::new(
|_| MyHostShared::default(),
|shared| MyHostMainThread { shared, instance: OnceCell::new(), latency_changed: false.into() },
&entry,
// We're hard-coding a specific plugin to load for this example
c"com.u-he.diva",
&host_info
)?;
// Assume we've activated the plugin and just done some processing.
/* ... */
// Let's check if the plugin requested a callback, by accessing our shared host data.
let callback_requested: &AtomicBool = plugin_instance.access_shared_handler(|h| &h.callback_requested);
// This fetches the previous value and sets it to false in a single atomic operation.
if callback_requested.fetch_and(false, Ordering::SeqCst) {
plugin_instance.call_on_main_thread_callback();
}
// Do the same for the restart and process requests
/* ... */
Structs§
- Host
Extensions - A collection of all extensions supported for a given
HostHandlerstype. - Host
Info - Human-readable information and description about the host.
Enums§
- Host
Error - A generic, type-erased error type for host-originating errors.
Traits§
- Audio
Processor Handler - Host data and callbacks that are tied to
[audio-thread]operations. - Host
Handlers - A set of host callback handlers.
- Main
Thread Handler - Host data and callbacks that are tied to
[main-thread]operations. - Shared
Handler - Host data and callbacks that are tied to
[thread-safe]operations.