Skip to main content

Module host

Module host 

Source
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 the MainThreadHandler trait, which is neither Send nor Sync, and lives encapsulated in the PluginInstance struct.

    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 Send this type to any other threads.

  • The audio thread ([audio-thread] in the CLAP specification): this is represented by a type implementing the AudioProcessorHandler trait, which is Send but !Sync, and lives encapsulated in the PluginAudioProcessor struct. It is only instantiated when the activate method is called, and is dropped on deactivate. 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 Send the AudioProcessorHandler type 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 AudioProcessorHandler implementation to receive temporary exclusive references to the MainThreadHandler type 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 the SharedHandler trait, which is both Send and Sync, 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§

HostExtensions
A collection of all extensions supported for a given HostHandlers type.
HostInfo
Human-readable information and description about the host.

Enums§

HostError
A generic, type-erased error type for host-originating errors.

Traits§

AudioProcessorHandler
Host data and callbacks that are tied to [audio-thread] operations.
HostHandlers
A set of host callback handlers.
MainThreadHandler
Host data and callbacks that are tied to [main-thread] operations.
SharedHandler
Host data and callbacks that are tied to [thread-safe] operations.