Search guides and the APICtrl K
  1. API reference
  2. Formats and wrappers

clap_wrapper.h

The CLAP format wrapper.

#include <sonore/clap_wrapper.h>
On this page

Design notes

One header that turns a SonoreDsp + a PluginDescriptor into a complete CLAP plugin: factory, entry point, params, audio/note ports, state, latency, and sample-accurate event handling. Include it exactly once in a translation unit that has already defined the DSP and the descriptor -- through formats.h, which adds every other format the build asks for:

#define SONORE_NUM_PARAMS 4
#include <sonore/dsp.h>
struct SonoreDsp { ... };                       // the generated DSP
#include <sonore/plugin.h>
static const sonore::ParamInfo kParams[SONORE_NUM_PARAMS] = { ... };
static const sonore::PluginDescriptor kDesc = { ... };  // named kDesc
#include <sonore/formats.h>                      // defines clap_entry

The descriptor's name is fixed: the wrappers read kDesc. SONORE_NUM_PARAMS must match the table it points at. Why CLAP is the native format rather than a shim over something else: it is MIT, it is a C ABI (no framework runtime to link, no licence to negotiate), and its process() model (planar float buffers plus a sorted event list) is already the shape our ABI has used since the wasm preview. The mapping is nearly one-to-one, which is what keeps preview and shipped binary identical.

Real-time discipline: process() allocates nothing, takes no locks and does no IO. Parameter values cross threads as relaxed atomics (SharedParam), written on the audio thread from events and read by the main thread for get_value, and snapshotted once per block so the DSP sees one consistent set -- which is what lets the ThreadSanitizer leg of verify:sanitize be a gate. (This once described a plain float array and accepted torn reads; it stopped being true when the parameters became atomics.)

Types 24

template <typename T> struct TakesMidi<T, std::void_t<decltype(std::declval<T&>().process(std::declval<AudioBlock<float>&>(), static_cast<const float*>(nullptr), std::declval<MidiBuffer&>()))>> : std::true_type

The match for TakesMidi: T declares the MIDI-taking process().

template <typename T> struct TakesSidechain<T, std::void_t<decltype(std::declval<T&>().process(std::declval<AudioBlock<float>&>(), std::declval<AudioBlock<float>&>(), static_cast<const float*>(nullptr)))>> : std::true_type

The match for TakesSidechain: T declares the two-block process().

template <typename T> struct TakesContext<T, std::void_t<decltype(std::declval<T&>().process(std::declval<ProcessContext&>(), static_cast<const float*>(nullptr)))>> : std::true_type

The match for TakesContext: T declares the context-taking process().

template <typename T> struct TakesDouble<T, std::void_t<decltype(std::declval<T&>().process(std::declval<AudioBlock<double>&>(), static_cast<const float*>(nullptr)))>> : std::true_type

The match for TakesDouble: T declares the double-block process().

template <typename T> struct TakesDoubleCtx<T, std::void_t<decltype(std::declval<T&>().process(std::declval<ProcessContextT<double>&>(), static_cast<const float*>(nullptr)))>> : std::true_type

The match for TakesDoubleCtx: T declares the double-context process().

template <typename T> struct WantsTransport<T, std::void_t<decltype(std::declval<T&>().setTransport(std::declval<const TransportInfo&>()))>> : std::true_type

The match for WantsTransport: T declares setTransport().

template <typename T> struct WantsHostInfo< T, std::void_t<decltype(std::declval<T&>().setHostInfo(std::declval<const HostInfo&>()))>> : std::true_type

The match for WantsHostInfo: T declares setHostInfo().

template <typename T> struct WantsTrackInfo< T, std::void_t<decltype(std::declval<T&>().setTrackInfo(std::declval<const TrackInfo&>()))>> : std::true_type

The match for WantsTrackInfo: T declares setTrackInfo().

template <typename T> struct WantsFile<T, std::void_t<decltype(std::declval<T&>().loadFile(std::declval<const char*>(), std::declval<const char*>()))>> : std::true_type

The match for WantsFile: T declares loadFile(purpose, path).

The persisted state blob. Versioned from day one: a plugin that ships and then gains a parameter must still load its old sessions.

template <typename T> struct HasVoiceCapacity<T, std::void_t<decltype(std::declval<const T&>().voiceCapacity())>> : std::true_type

The match for HasVoiceCapacity: T declares voiceCapacity() const.

template <typename T> struct HasActiveVoices<T, std::void_t<decltype(std::declval<const T&>().activeVoices())>> : std::true_type

The match for HasActiveVoices: T declares activeVoices() const.

template <typename T> struct HasStateBag<T, std::void_t<decltype(std::declval<const T&>().saveState(std::declval<StateBag&>()))>> : std::true_type

The match for HasStateBag: T declares saveState(StateBag&) const.

A value one thread writes and another reads.

Parameters are set by the host on the main thread (a knob, a preset, a session load) and read by the audio thread every block. Two threads on one plain float is a data race by the language, however benign the hardware makes it, and it is the first thing ThreadSanitizer names. These wrap a relaxed atomic behind the conversions a plain float had, so the forty sites that assign or read one did not change -- a relaxed 32-bit load or store is an ordinary move on every CPU this ships on -- and the audio thread takes ONE snapshot per block (snapshotParams), which also gives the DSP a parameter set that cannot change between two samples of one block.

SharedParam for a flag: a relaxed atomic bool that reads and assigns like a plain one, and copies its value the way SharedParam does. For a fact one thread sets and another polls (bypass engaged, offline render, state dirty); relaxed means it orders nothing else, so it must not be used to publish other data.

Host-driven bypass with a click-free crossfade and a latency-aligned dry path. VST3 (kIsBypass parameter), AU (BypassEffect property) and LV2 (lv2:enabled port) all drive this; CLAP hosts bypass plugins themselves, so the CLAP path never engages it. The dry signal is delayed by the DSP's reported latency -- mixing an undelayed dry against latency-delayed wet during the ramp would comb-filter every bypass toggle.

engaged may be set from any thread; everything else is the audio thread's. prepare() allocates the dry ring and runs at activation, on the main thread; bypassCapture() and bypassApply() then run on every block.

One plugin instance as every format holds it: the DSP, its parameters, the MIDI and expression buffers, bypass, latency and tail bookkeeping, the editor (web or native) and the user presets.

The CLAP wrapper allocates one per create_plugin and frees it in destroy; VST3, AU, LV2 and the standalone each embed one in their own object and drive it through the same functions, which is why a fix made here reaches every format. A wrapper's internal type, not something a plugin constructs.

Fields are grouped by who may touch them, and the member comments say so: params is shared between threads through SharedParam, paramsBlock and the MIDI buffers belong to the audio thread, the GUI and preset state to the main thread.

template <typename T> struct HasLatency<T, std::void_t<decltype(std::declval<const T&>().latencySamples())>> : std::true_type

The match for HasLatency: T declares latencySamples() const.

template <typename T> struct HasGainReduction<T, std::void_t<decltype(std::declval<const T&>().gainReduction())>> : std::true_type

The match for HasGainReduction: T declares gainReduction() const.

template <typename T> struct HasStateDirty<T, std::void_t<decltype(std::declval<T&>().consumeStateDirty())>> : std::true_type

The match for HasStateDirty: T declares consumeStateDirty().

template <typename T> struct HasTail<T, std::void_t<decltype(std::declval<const T&>().tailSamples())>> : std::true_type

The match for HasTail: T declares tailSamples() const.

The host's events for one piece of a block, at times within the piece. Each get() copies the event over the last one, which is all process() needs: it applies each event before asking for the next.

And what process() sends back, moved from the piece's time to the host's.

One preset-discovery provider the host created, as its provider_data: the clap object, the indexer it reports to, and the strings it handed the indexer, which must outlive the declare calls. Made by the preset discovery factory and freed by the provider's destroy, and never sees a plugin instance: a host indexes presets without loading the plugin.

Functions 179

Aliases 1

Enums 1

  • enumenum class StateScope
    What a state restore is FOR, which decides the fields that are the session's rather than the sound's.
    More
    Session
    a host restoring a project -- everything in the blob applies.
    Preset
    a preset the user picked -- the blob's sound applies, and the session keeps its bypass, its editor size and its selection (the preset manager sets that afterwards). A preset saved while bypassed must not bypass the track it is loaded on, and one saved from a small window must not resize this one.
    Restore
    an earlier state of THIS session coming back -- an undo. As Preset, but the selection comes back with the sound.
    clapwrap::StateScope · clap_wrapper.h:491

Constants 5

Macros 2

↑↓ to move↵ to openEvery public name in the headers, and every guide