Skip to main content

Module extensions

Module extensions 

Source
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 the PluginSharedHandle::get_extension method 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 HostShared associated 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 Host subtype (HostMainThread, HostAudioProcessor, or HostShared), depending on the thread specification of the ABI’s method. For example, the Log extension’s ABI has to be fully thread-safe, therefore the HostLogImpl trait has to be implemented on the HostShared type.

    See the host module 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 Params ABI exposes one thread-safe method and two that are main-thread only. Therefore, the HostParamsImplShared and HostParamsImplMainThread traits have to be implemented on the HostShared and HostMainThread types, respectively.

    Once this is all done, the host implementation can declare this extension by using the HostExtensions::register method in the Host::declare_extensions method implementation.

    The fact that the right traits are implemented on the right Host associated types is automatically checked at compile time, upon calling the HostExtensions::register method.

§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§

HostExtensionSide
A marker struct that represents extensions to be implemented by the host side.
PluginExtensionSide
A marker struct that represents extensions to be implemented by the plugin side.
RawExtension
A raw extension pointer.
RawExtensionImplementation
A pointer to an extension’s implementation struct.

Traits§

Extension
A type representing a CLAP extension pointer.
ExtensionImplementation
Provides an implementation of this extension for a given type I (typically either a host or plugin structure).
ExtensionSide
An extension side marker: either PluginExtensionSide or HostExtensionSide.