clap_wrapper.h
The CLAP format wrapper.
#include <sonore/clap_wrapper.h>- 24types
- 188other declarations
- Guides: Getting started, Platforms and formats
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.
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
- fn
template <typename T> inline void runDsp(T& dsp, AudioBlock<float>& block, const float* params, MidiBuffer& midi)clapwrap::runDsp · clap_wrapper.h:134 - fn
template <typename T> inline void runDspSc(T& dsp, AudioBlock<float>& block, AudioBlock<float>& sidechain, const float* params, MidiBuffer& midi)clapwrap::runDspSc · clap_wrapper.h:169 - fn
inline constexpr bool supportsDouble()clapwrap::supportsDouble · clap_wrapper.h:223 - fn
inline license::Gate& licenseGate()This binary's licence, read once and shared by every instance.clapwrap::licenseGate · clap_wrapper.h:235More
A function-local static rather than a namespace-scope object: a plugin is a shared library, and the first thing a host does is scan it. Construction reads a file and verifies a signature, and doing that during static init -- before the host has even decided to instantiate us -- is work done in the one place where failure has nowhere to go.
- fn
template <typename Sample> inline void applyLicenceGate(AudioBlock<Sample>& block)[audio-thread] Degrade the main bus while unlicensed.clapwrap::applyLicenceGate · clap_wrapper.h:246More
On the MAIN bus only, deliberately: it is what a listener hears, and a demo that also mangled a splitter's aux sends would be describing a broken plugin rather than an unactivated one.
- fn
template <typename T> inline void runDspCtx64(T& dsp, ProcessContextT<double>& ctx, const float* params)Run a double-precision block through whichever double signature exists.clapwrap::runDspCtx64 · clap_wrapper.h:259 - fn
inline bool wantsMidiIn()Does the plugin want a MIDI INPUT port?clapwrap::wantsMidiIn · clap_wrapper.h:285More
The classic three-argument signature says so by itself. The context form carries a MIDI buffer too, but a context-taking EFFECT (a splitter, say) should not grow a note port it never reads -- so for that form the descriptor decides: an instrument is note-driven by definition, and a plugin that emits MIDI consumes it.
- fn
inline uint32_t numAuxOutputs()How many aux output buses this plugin really has: what the descriptor declares, but only for a DSP that can actually be handed them.clapwrap::numAuxOutputs · clap_wrapper.h:292 - fn
inline uint32_t auxBusChannels(uint32_t index)clapwrap::auxBusChannels · clap_wrapper.h:305 - fn
template <typename T> inline void runDspCtx(T& dsp, ProcessContext& ctx, const float* params)The one entry point every wrapper calls.clapwrap::runDspCtx · clap_wrapper.h:313 - fn
template <typename T> inline void sendTransport(T& dsp, const TransportInfo& info)clapwrap::sendTransport · clap_wrapper.h:346 - fn
template <typename T> inline void sendHostInfo(T& dsp, const HostInfo& info)Told ONCE, when the plugin is created. A host does not change identity mid-session, and telling a DSP every block something that cannot change would be a per-block cost for a constant. An Audio Unit is told when the host names itself through kAudioUnitProperty_AUHostIdentifier, early and on the main thread, and not at all by a host that never sets it.clapwrap::sendHostInfo · clap_wrapper.h:368 - fn
template <typename T> inline void sendTrackInfo(T& dsp, const TrackInfo& info)clapwrap::sendTrackInfo · clap_wrapper.h:386 - fn
template <typename T> inline void sendFile(T& dsp, const char* purpose, const char* path)clapwrap::sendFile · clap_wrapper.h:418 - fn
inline TransportInfo readTransport(const clap_event_transport_t* t)Translate the host's transport event into ours.clapwrap::readTransport · clap_wrapper.h:424 - fn
inline void clampEditorSize(uint32_t* width, uint32_t* height)Bring a size inside what THIS plugin declared. The one place that decides.clapwrap::clampEditorSize · clap_wrapper.h:516More
The numbers were written three times before this and differently: 320x200 in CLAP's adjust_size, 320x200 again in VST3's checkSizeConstraint, and 120x120 in the state restore that reads a saved size back -- so a session could restore an editor smaller than the plugin's own minimum, and nothing on the resize path would ever have allowed it. Unifying them here fixed that.
What it did NOT fix is that the one surviving answer was still invented. It was 320x200 to 8192x8192, freely resizable, no aspect ratio, for EVERY plugin -- so a synth with a five-octave keyboard along the bottom could be dragged to 320 pixels wide by any host, and a fixed-proportion skin could be stretched to any shape. The plugin had no way to say otherwise.
It does now: PluginDescriptor::editorLimits, whose defaults are exactly the constants that used to be here. The rules themselves live in plugin.h as a pure function, so each one is a test rather than something checked by dragging a window in a host and looking at it.
- fn
template <typename T> inline void saveDspState(const T& dsp, StateBag& bag)clapwrap::saveDspState · clap_wrapper.h:566 - fn
template <typename T> inline void loadDspState(T& dsp, const StateBag& bag)clapwrap::loadDspState · clap_wrapper.h:576 - fn
inline uint32_t minMainChannels()clapwrap::minMainChannels · clap_wrapper.h:591 - fn
inline uint32_t maxMainChannels()clapwrap::maxMainChannels · clap_wrapper.h:595 - fn
inline bool channelCountAllowed(uint32_t n)clapwrap::channelCountAllowed · clap_wrapper.h:601 - fn
inline uint32_t defaultMainChannels()What a host gets before it negotiates anything: stereo when the range allows it, else the nearest edge (a mono-only DSP defaults to mono).clapwrap::defaultMainChannels · clap_wrapper.h:606 - fn
inline const char* channelLayoutName(uint32_t n)clapwrap::channelLayoutName · clap_wrapper.h:610 - fn
template <typename Sample> inline void bypassCapture(BypassState& b, Sample* const* chans, uint32_t numChannels, uint32_t frames)Stash the dry input BEFORE the DSP runs (the wrappers process in place). With zero latency this may be skipped while idle -- the block an engage arrives in still captures in time. With latency it runs every block, or the firstclapwrap::bypassCapture · clap_wrapper.h:755delaysamples after an engage would crossfade against stale history. - fn
inline float bypassStep(float mix, float target, float step)One sample of the bypass crossfade: a step towardclapwrap::bypassStep · clap_wrapper.h:789targetthat lands ON it. It stepped and then clamped to [0, 1], so at full bypasstarget > mixwas false, the next step went back down, and the mix ran 1, 1 - step, 1, ... for as long as the bypass stayed engaged: the processed signal leaked through on every other sample, some 60 dB down and modulated at Nyquist. - fn
template <typename Sample> inline void bypassApply(BypassState& b, Sample* const* chans, uint32_t numChannels, uint32_t frames)Crossfade the delayed dry path over the wet output and advance the ring. Instruments have no dry signal, so bypass fades them to silence. Fully bypassed is a copy (or silence), not a mix with a gain of 0: an Inf or a NaN out of the DSP times 0 is still NaN.clapwrap::bypassApply · clap_wrapper.h:799 - fn
inline void snapshotParams(Instance* inst)[audio-thread] Copy the shared parameters into the block's own array. One relaxed load per parameter, once per block; the DSP then reads plain floats that cannot move under it for the rest of the call.clapwrap::snapshotParams · clap_wrapper.h:1131More
A copy taken while writeParamsTogether() was writing -- a state or preset loaded on the main thread mid-playback, which every format allows -- was half one set and half the other: a block of a gain from one session with the pan from another, a set nobody saved. Such a copy is dropped, and the block runs on the last whole set; the next takes the new one. Never waits: a reader that retried could spin for as long as a load takes.
- fn
inline void primeParamsBlock(Instance* inst)[main-thread, before processing starts] The block's copy, filled with a whole set before the first block. snapshotParams() keeps the last whole set when a copy is torn -- except on the very first block, which had no set to keep and took the torn copy as it was. Under load that was one block in about 800,000 in the state-race test: the first one after activation, with a state loading beside it. Done where the first prepare is, on the main thread, where a state load cannot be half-way through.clapwrap::primeParamsBlock · clap_wrapper.h:1149 - fn
template <typename Write> inline void writeParamsTogether(Instance* inst, Write&& write)[main-thread] Write the whole parameter set as one change: a block sees all of it or none of it (see snapshotParams).writestores the values.clapwrap::writeParamsTogether · clap_wrapper.h:1165More
Every caller is a state or a preset replacing the whole set, so an edit the editor queued before it is older than every value written here, and its drain would otherwise put it back over them (see UiEventQueue::supersedeParamEdits). Marked first: a drain is then left only the instant between its own check and its own store, where the edit and the load are as simultaneous as two things on two threads can be.
- fn
inline Instance* self(const clap_plugin_t* p)clapwrap::self · clap_wrapper.h:1174 - fn
inline bool hasClapBypassParam()Effects get a bypass, instruments do not -- bypassing a synth would mean routing its input to its output and it has no input. The same rule the VST3 and LV2 wrappers already follow, so one DSP behaves the same way everywhere.clapwrap::hasClapBypassParam · clap_wrapper.h:1184 - fn
inline uint32_t paramsCount(const clap_plugin_t*)clapwrap::paramsCount · clap_wrapper.h:1194 - fn
inline bool paramsGetInfo(const clap_plugin_t* plugin, uint32_t index, clap_param_info_t* info)clapwrap::paramsGetInfo · clap_wrapper.h:1198 - fn
inline bool paramsGetValue(const clap_plugin_t* plugin, clap_id id, double* out)clapwrap::paramsGetValue · clap_wrapper.h:1253 - fn
inline bool paramsValueToText(const clap_plugin_t*, clap_id id, double value, char* out, uint32_t capacity)clapwrap::paramsValueToText · clap_wrapper.h:1263 - fn
inline bool paramsTextToValue(const clap_plugin_t*, clap_id id, const char* text, double* out)clapwrap::paramsTextToValue · clap_wrapper.h:1274 - fn
template <typename T> inline const T* eventAs(const clap_event_header_t* hdr)The event as its type's struct -- if the header says there is enough of it.clapwrap::eventAs · clap_wrapper.h:1296More
sizeis in the header so that a plugin can check it, and the first version of this did not: a header that claimed sixteen bytes was cast to a fifty-byte sysex event and its buffer pointer read from whatever followed the host's storage. The event fuzz in the host test found it in the plugin that accepts MIDI. A short event is ignored, never partially read. - fn
inline int eventTime(const clap_event_header_t* hdr, uint32_t frames)Where in a block offramesan event lands: its own time, clamped into the block.clapwrap::eventTime · clap_wrapper.h:1308More
The host's time was passed on as it came. A note-off at or past the end -- one frame of rounding in a host's scheduler -- sat where no DSP that stops at the last frame reached it, and the note hung; a time of 2^31 became a negative position. With no block (flush, a zero-length call) everything is at 0.
- fn
inline void applyEvent(Instance* inst, const clap_event_header_t* hdr, uint32_t frames)Apply one event to our state. Shared by process() and flush() so a parameter can never mean two different things depending on which path delivered it.clapwrap::applyEvent · clap_wrapper.h:1316framesis the block the event belongs to: 0 from flush. - fn
inline void discardEvents(Instance* inst)Forget the notes and expression waiting for a block: the DSP has had them, or there is no DSP block to give them to.clapwrap::discardEvents · clap_wrapper.h:1461 - fn
inline void drainUiEvents(Instance* inst, const clap_output_events_t* out)Drain the UI queue: apply each edit locally AND tell the host, so a knob turned on our webview records as automation just like a host-side control. Runs on the audio thread; the queue is lock-free and this allocates nothing.clapwrap::drainUiEvents · clap_wrapper.h:1469 - fn
inline void paramsFlush(const clap_plugin_t* plugin, const clap_input_events_t* in, const clap_output_events_t* out)clapwrap::paramsFlush · clap_wrapper.h:1531 - fn
inline uint32_t audioPortsCount(const clap_plugin_t*, bool isInput)clapwrap::audioPortsCount · clap_wrapper.h:1566 - fn
inline bool audioPortsGet(const clap_plugin_t* plugin, uint32_t index, bool isInput, clap_audio_port_info_t* info)clapwrap::audioPortsGet · clap_wrapper.h:1575 - fn
inline uint32_t audioPortsConfigCount(const clap_plugin_t*)clapwrap::audioPortsConfigCount · clap_wrapper.h:1615 - fn
inline bool audioPortsConfigGet(const clap_plugin_t*, uint32_t index, clap_audio_ports_config_t* config)clapwrap::audioPortsConfigGet · clap_wrapper.h:1619 - fn
inline bool audioPortsConfigSelect(const clap_plugin_t* plugin, clap_id configId)clapwrap::audioPortsConfigSelect · clap_wrapper.h:1640 - fn
inline bool surroundIsMaskSupported(const clap_plugin_t*, uint64_t mask)clapwrap::surroundIsMaskSupported · clap_wrapper.h:1655 - fn
inline uint32_t surroundGetChannelMap(const clap_plugin_t* plugin, bool isInput, uint32_t portIndex, uint8_t* channelMap, uint32_t channelMapCapacity)clapwrap::surroundGetChannelMap · clap_wrapper.h:1662 - fn
inline bool audioPortsActivationCanActivateWhileProcessing(const clap_plugin_t*)clapwrap::audioPortsActivationCanActivateWhileProcessing · clap_wrapper.h:1687 - fn
inline bool audioPortsActivationSetActive(const clap_plugin_t* plugin, bool isInput, uint32_t portIndex, bool isActive, uint32_t)clapwrap::audioPortsActivationSetActive · clap_wrapper.h:1694 - fn
inline void prepareDsp(Instance* inst)Re-run prepare() with the settings the host ACTUALLY negotiated.clapwrap::prepareDsp · clap_wrapper.h:1719More
Worth having for a second reason: the reset path used to build a ProcessSpec from DEFAULTS -- 128 samples, 2 channels -- whatever the session really was. A DSP that sizes a buffer from maximumBlockSize would have shrunk it to 128 on every reset and then been handed larger blocks.
- fn
template <typename T> inline void resetDspOf(T& dsp, Instance* inst)A host's "start over" at the same rate and block size: CLAP's reset() and an AU's Reset, both on the audio thread. The DSP's own reset() when it has one, so nothing prepare() sized is freed or allocated there; prepare() again when it has not, as it always was. Held notes go either way.clapwrap::resetDspOf · clap_wrapper.h:1747 - fn
inline void resetDsp(Instance* inst)clapwrap::resetDsp · clap_wrapper.h:1756 - fn
inline bool renderHasHardRealtimeRequirement(const clap_plugin_t*)clapwrap::renderHasHardRealtimeRequirement · clap_wrapper.h:1758 - fn
inline bool renderSet(const clap_plugin_t* plugin, clap_plugin_render_mode mode)clapwrap::renderSet · clap_wrapper.h:1765 - fn
template <typename T> inline bool fillVoiceInfo(const T& dsp, clap_voice_info_t* info)Templated so the branch that is not taken is never INSTANTIATED.clapwrap::fillVoiceInfo · clap_wrapper.h:1826More
if constexprinside a plain function still requires both branches to compile, so asking a monophonic DSP for a voiceCapacity() it does not have is an error even when the condition is false. Making the DSP type a template parameter is what actually discards it. - fn
inline bool voiceInfoGet(const clap_plugin_t* plugin, clap_voice_info_t* info)clapwrap::voiceInfoGet · clap_wrapper.h:1844 - fn
inline const GroupTable& descriptorGroups()The descriptor's parameter groups, worked out once.clapwrap::descriptorGroups · clap_wrapper.h:1880More
collectGroups() walks the whole table, comparing every parameter's group with each group found so far, and every format walked it again for each question about ONE parameter: VST3's getParameterInfo for its unit, an AU for its clump, a CLAP host for each remote-controls page. A host reads every parameter's info as it makes an instance, so a VST3 of 256 parameters walked its table 256 times, a millisecond per instance and most of what making one cost. kDesc does not change while the module is loaded, and neither do its groups: the same table, built on first use.
- fn
inline int descriptorGroupOf(int index)Which of descriptorGroups() parameterclapwrap::descriptorGroupOf · clap_wrapper.h:1890indexis in, -1 for none: what descriptorGroups().indexOf(kDesc.params[index].group) says, looked up. The remote-controls pages ask it of every parameter, for every group, for every page -- a host listing the 33 pages of a 256-parameter plugin made some 400,000 string comparisons of it, 1.6 ms per instance. - fn
inline bool remoteControlsPageAt(uint32_t pageIndex, clap_remote_controls_page_t* page)Which parameters belong on pageclapwrap::remoteControlsPageAt · clap_wrapper.h:1902pageIndex, and what to call it. Returns false when the page does not exist. - fn
inline uint32_t remoteControlsCount(const clap_plugin_t*)clapwrap::remoteControlsCount · clap_wrapper.h:1985 - fn
inline bool remoteControlsGet(const clap_plugin_t*, uint32_t pageIndex, clap_remote_controls_page_t* page)clapwrap::remoteControlsGet · clap_wrapper.h:1992 - fn
inline bool configurableAudioPortsCheck(const clap_plugin_t* plugin, const clap_audio_port_configuration_request_t* requests, uint32_t count, uint32_t* agreedMain, const uint8_t** agreedMap)clapwrap::configurableAudioPortsCheck · clap_wrapper.h:2015 - fn
inline bool configurableAudioPortsCanApply(const clap_plugin_t* plugin, const clap_audio_port_configuration_request_t* requests, uint32_t count)clapwrap::configurableAudioPortsCanApply · clap_wrapper.h:2061 - fn
inline bool configurableAudioPortsApply(const clap_plugin_t* plugin, const clap_audio_port_configuration_request_t* requests, uint32_t count)clapwrap::configurableAudioPortsApply · clap_wrapper.h:2067 - fn
inline uint32_t notePortsCount(const clap_plugin_t*, bool isInput)clapwrap::notePortsCount · clap_wrapper.h:2086 - fn
inline bool notePortsGet(const clap_plugin_t*, uint32_t index, bool isInput, clap_note_port_info_t* info)clapwrap::notePortsGet · clap_wrapper.h:2091 - fn
template <typename Put> inline bool writeStateFields(Put&& put, const float* values, uint8_t bypassByte, int32_t preset, int32_t editorWidth, int32_t editorHeight, const PresetManager::Selection& selection, const std::vector<uint8_t>* bag)The fields of the blob, in order, from wherever they come from.clapwrap::writeStateFields · clap_wrapper.h:2130More
Shared by saveStateBody (an instance's own) and factoryPresetState (a factory preset's, with no instance at all), so the format is written in one place however many things write it.
bagnull writes no bag, which a loader reads as "this blob does not describe the DSP's own state" and leaves that state alone. - fn
template <typename Put> inline bool saveStateBody(const Instance* inst, Put&& put)The state format itself, told nothing about streams: saveStateBody writes an instance's blob and loadStateBody below reads one back.clapwrap::saveStateBody · clap_wrapper.h:2206More
This existed TWICE -- once here for CLAP and once in the VST3 wrapper -- and the two had already drifted. The CLAP copy learned to reset unknown parameters to their defaults, to treat a v1 blob's bypass as off, and to hand an empty bag to a DSP whose blob predates bags. The VST3 copy learned none of it, so the same session file restored differently depending on which build of the same plugin opened it.
putandgetmove bytes and return how many they moved; zero means the stream is finished. Everything else about the format lives here, once. - fn
template <typename Get> inline bool loadStateBody(Instance* inst, Get&& get, StateScope scope = StateScope::Session)clapwrap::loadStateBody · clap_wrapper.h:2222 - fn
inline bool stateSave(const clap_plugin_t* plugin, const clap_ostream_t* stream)clapwrap::stateSave · clap_wrapper.h:2433 - fn
inline bool stateLoad(const clap_plugin_t* plugin, const clap_istream_t* stream)clapwrap::stateLoad · clap_wrapper.h:2441 - fn
inline bool captureStateBlob(const Instance* inst, std::vector<uint8_t>& out)[main-thread] The blob, exactly as the host's save path writes it.clapwrap::captureStateBlob · clap_wrapper.h:2477 - fn
inline uint64_t presetSoundHash(const Instance* inst, int numParams)[main-thread] A hash of the SOUND: the parameters and the DSP's own state, not the editor size, the bypass or the selection. What "modified" is judged by, so resizing the window or bypassing the track does not mark a preset changed.clapwrap::presetSoundHash · clap_wrapper.h:2493numParamsfewer than this build's is the hash an older build with that many parameters gave the same sound. - fn
inline bool factoryPresetValues(const PluginDescriptor& d, int index, float* out, int capacity)Factory presetindexofd, through applyPreset, intoout, which holdscapacityvalues. Refused -- false, nothing written -- when the descriptor declares more parameters than that.clapwrap::factoryPresetValues · clap_wrapper.h:2526More
applyPreset writes one value for every parameter the DESCRIPTOR declares, and every array a wrapper hands it, like the parameters the values are then copied to, holds SONORE_NUM_PARAMS. A release build loads a descriptor that declares more (entryInit refuses one only in debug; see makeEditorHost), and loading any of its presets wrote past the end of the array: the preset discovery provider and the preset bar through factoryPresetState, and VST3's program change on the controller, in process() and from the page. Refused, as a stale preset is: a preset the arrays cannot hold is not one to apply half of.
- fn
inline bool factoryPresetState(int index, std::vector<uint8_t>& out)Factory presetclapwrap::factoryPresetState · clap_wrapper.h:2539indexas a state blob: its values through applyPreset (the one rule for a stale preset), the preset index, and NO bag -- a factory preset is parameter values, and a blob without a bag leaves the DSP's own state as it is, which is what loading one by index always did. Needs no instance, so the preset discovery provider writes the same bytes. - fn
inline void announceOwnStateLoad(Instance* inst)[main-thread] Tell the host the plugin changed its own state.clapwrap::announceOwnStateLoad · clap_wrapper.h:2566More
CLAP, by clap_plugin_params' "loading a preset" scenario: rescan the values (a host's generic panel and automation lanes otherwise go on showing the old ones), mark the session dirty (the user changed it, and closing without a prompt would lose it), and ask for a restart if the latency moved -- a latency is announced in activate() and nowhere else. VST3 and AU speak through their own objects and set onOwnStateLoad.
- fn
inline bool applyOwnState(Instance* inst, const std::vector<uint8_t>& blob, StateScope scope, std::string* why)[main-thread] A blob through the restore path, then the host told.clapwrap::applyOwnState · clap_wrapper.h:2582 - fn
inline std::string factoryPresetFile(int index)Where factory presetclapwrap::factoryPresetFile · clap_wrapper.h:2604indexis as a file, if the discovery provider has written it (syncFactoryPresetFiles); empty otherwise. - fn
inline void announcePresetToHost(Instance* inst, const PresetManager::Selection& selection)[main-thread] After the PLUGIN loaded a preset (its own browser, not the host's): tell a CLAP host which one, through clap_host_preset_load's "loaded", so the host's preset browser can show the same preset as the plugin's. A preset the host loaded through preset-load is not reported back -- the host already knows.clapwrap::announcePresetToHost · clap_wrapper.h:2623 - fn
inline PresetManager::Identity presetIdentity()The descriptor's identity, as the preset files carry it.clapwrap::presetIdentity · clap_wrapper.h:2647 - fn
inline PresetManager& presetsOf(Instance* inst)[main-thread] This instance's presets, set up the first time anything asks: an editor opening, a host loading a preset.clapwrap::presetsOf · clap_wrapper.h:2660 - fn
template <typename T> inline uint32_t dspLatency(const T& dsp)clapwrap::dspLatency · clap_wrapper.h:2699 - fn
template <typename T> inline float dspGainReduction(const T& dsp)The DSP's gain reduction in positive decibels, 0 when it declares none. A NaN, a negative number or a runaway is not a reading and reads 0.clapwrap::dspGainReduction · clap_wrapper.h:2736 - fn
template <typename T> inline bool dspConsumeStateDirty(T& dsp)clapwrap::dspConsumeStateDirty · clap_wrapper.h:2774 - fn
inline void markDirtyIfNeeded(Instance* inst)[main-thread] Tell the host the session changed underneath it.clapwrap::markDirtyIfNeeded · clap_wrapper.h:2791More
mark_dirty is [main-thread], and process() is not, so the audio thread sets a flag and asks to be called here, which is what on_main_thread is for. Losing a notification would be losing a save prompt, so the flag is only cleared once the host has actually been told.
- fn
inline void writeOutputGuardReport(const char* message)[main-thread] Where the output guard's report goes when no host log takes it: stderr, and on Windows the debugger's output as well -- a plugin is a DLL inside a GUI host, whose stderr is usually connected to nothing, and a developer chasing the bug has a debugger attached.clapwrap::writeOutputGuardReport · clap_wrapper.h:2808 - fn
inline void reportOutputGuard(Instance* inst)[main-thread] Say, once, that the output guard silenced something.clapwrap::reportOutputGuard · clap_wrapper.h:2827More
The audio thread counts the trips and, the first time, asks to be called here (guardOutputs). A warning in the host's log where it has one (CLAP_EXT_LOG), stderr otherwise; once per instance, naming the plugin and how many blocks it has lost so far. The formats with no request_callback call this from whatever main-thread hook they already poll: the count is there whenever they look.
- fn
inline void announceLatency(Instance* inst)ANNOUNCE a new latency. Legal in exactly one place.clapwrap::announceLatency · clap_wrapper.h:2856More
The header says "The latency is only allowed to change during plugin->activate", and clap-validator means it literally: not "while deactivated", but WITHIN the activate call. A host cannot re-plan its delay compensation under a running graph, and outside activate it has no defined moment to do the re-planning in.
So this is called from activate() and nowhere else. The first version called it from the parameter flush too -- legal-looking, since the plugin may well be deactivated when a knob is turned with the transport stopped, and refused all the same.
- fn
inline void requestRestartIfLatencyChanged(Instance* inst)ASK to be restarted, because the latency moved and this is not activate.clapwrap::requestRestartIfLatencyChanged · clap_wrapper.h:2879More
The other half of the same rule, and the only thing a plugin may do about a latency change anywhere else. request_restart is [thread-safe], which is what lets process() and the parameter flush share one function instead of needing a main-thread callback between them.
The reported value is deliberately NOT updated here: the restart lands in activate(), which is where the new number is allowed to be spoken.
- fn
inline uint32_t latencyGet(const clap_plugin_t* plugin)clapwrap::latencyGet · clap_wrapper.h:2889 - fn
template <typename T> inline uint32_t dspTail(const T& dsp)clapwrap::dspTail · clap_wrapper.h:2948 - fn
inline void announceTailIfChanged(Instance* inst)Tell the host the tail changed. From the AUDIO THREAD, which is the interesting part.clapwrap::announceTailIfChanged · clap_wrapper.h:2978More
Every other notification in this wrapper is [main-thread] and has to be smuggled off the audio thread through request_callback or a host's own parameter poll. This one is the reverse: the header marks clap_host_tail::changed as [audio-thread], so process() is not merely allowed to call it, it is the right place.
Which makes sense once you see what each is for. A latency change forces the host to re-plan its delay compensation, which it cannot do under a running graph. A tail is only a hint about how long to keep processing after the transport stops -- nothing has to be re-planned, so nothing has to stop.
It matters for exactly one thing and it matters a lot there: a reverb whose decay is a parameter has a tail that moves, and a host bouncing offline with a stale short tail cuts the reverb off mid-decay.
- fn
inline uint32_t tailGet(const clap_plugin_t* plugin)clapwrap::tailGet · clap_wrapper.h:2994 - fn
inline void refreshTrackInfo(Instance* inst)clapwrap::refreshTrackInfo · clap_wrapper.h:3011 - fn
inline void trackInfoChanged(const clap_plugin_t* plugin)clapwrap::trackInfoChanged · clap_wrapper.h:3046 - fn
inline bool pluginInit(const clap_plugin_t* plugin)clapwrap::pluginInit · clap_wrapper.h:3052 - fn
inline void pluginDestroy(const clap_plugin_t* plugin)clapwrap::pluginDestroy · clap_wrapper.h:3066 - fn
inline bool pluginActivate(const clap_plugin_t* plugin, double sampleRate, uint32_t, uint32_t maxFrames)clapwrap::pluginActivate · clap_wrapper.h:3072 - fn
inline void pluginDeactivate(const clap_plugin_t* plugin)clapwrap::pluginDeactivate · clap_wrapper.h:3100 - fn
inline bool pluginStartProcessing(const clap_plugin_t*)clapwrap::pluginStartProcessing · clap_wrapper.h:3101 - fn
inline void pluginStopProcessing(const clap_plugin_t*)clapwrap::pluginStopProcessing · clap_wrapper.h:3102 - fn
inline void pluginReset(const clap_plugin_t* plugin)clapwrap::pluginReset · clap_wrapper.h:3104 - fn
inline void clearAuxConstantMasks(const clap_process_t* process, uint32_t numAux)constant_mask is an OUTPUT field on an output buffer: the plugin says which channels it left constant. The main port always said 0; the aux ports said whatever the host's memory held, and a host that reads a set bit skips a channel the DSP wrote.clapwrap::clearAuxConstantMasks · clap_wrapper.h:3125 - fn
inline void finishProcess(Instance* inst, const clap_process_t* process, uint32_t frames)Everything a process() call owes the host AFTER the DSP has run, at either precision. One function, called from both paths, because the 64-bit path used to return as soon as its meters were fed and so did none of it: a double-precision host got no MIDI out of an arpeggiator, was never asked to restart for a latency change, never heard of a new tail, and never learned the session had changed.clapwrap::finishProcess · clap_wrapper.h:3138 - fn
template <typename Sample> inline bool guardOutputs(Instance* inst, Sample* const* main, uint32_t numMain, const AudioBlock<Sample>* aux, uint32_t numAux, uint32_t frames)[audio-thread] The output guard (output_guard.h) over everything a block hands back: the main bus's channels and every aux bus. A NaN or an infinity on any of them and all of them leave as silence.clapwrap::guardOutputs · clap_wrapper.h:3220More
Called after the bypass, so what is checked is what the host gets, and before the meters and the scope, so an editor shows that silence rather than a NaN. The first trip asks for the main thread, which says so once (reportOutputGuard); request_callback is [thread-safe]. Every format's process path calls this, and only CLAP has a host to ask: the others find the count from a main-thread hook of their own, or -- LV2's worker -- act on the true this returns for that first trip.
- fn
inline clap_process_status processBlock(Instance* inst, const clap_process_t* process)One block of at most maxFrames: the whole of process(), for a host that keeps to what it promised in activate(), and one piece of it otherwise (see pluginProcess). An oversized block is guarded piece by piece, each as the block the DSP ran.clapwrap::processBlock · clap_wrapper.h:3232 - fn
inline uint32_t sliceEventsSize(const clap_input_events_t* list)clapwrap::sliceEventsSize · clap_wrapper.h:3514 - fn
inline const clap_event_header_t* sliceEventsGet(const clap_input_events_t* list, uint32_t i)clapwrap::sliceEventsGet · clap_wrapper.h:3518 - fn
inline bool sliceEventsPush(const clap_output_events_t* list, const clap_event_header_t* hdr)clapwrap::sliceEventsPush · clap_wrapper.h:3539 - fn
inline void sliceBuffer(const clap_audio_buffer_t& from, uint32_t offset, clap_audio_buffer_t& to, float** p32, double** p64)One of the host's buses, from frameclapwrap::sliceBuffer · clap_wrapper.h:3551offseton. The channel pointers live inp32/p64, which hold kMaxAudioChannels: no more is ever read. - fn
inline void advanceTransport(clap_event_transport_t& t, uint32_t frames, double sampleRate)The transportclapwrap::advanceTransport · clap_wrapper.h:3572frameslater, while it plays: the song position in seconds and in beats, and the bar it is in. Not a loop's wrap -- a host that loops ends the block there itself. - fn
inline clap_process_status pluginProcess(const clap_plugin_t* plugin, const clap_process_t* process)clapwrap::pluginProcess · clap_wrapper.h:3592 - fn
inline void requestParamFlush(Instance* inst)Ask the host to drain the UI queue now. [main-thread]clapwrap::requestParamFlush · clap_wrapper.h:3670More
Everything an editor does goes down uiEvents, and process() drains it -- so while the transport is stopped, the plugin deactivated or the track asleep, an edit sits in the queue: the host records nothing, shows the old value in its own controls, and saves a project that does not have it. request_flush is how CLAP says "something is waiting although I am idle"; the host answers with flush() or process(), both of which drain the queue. Harmless while processing, and never from the audio thread, which is already inside one.
- fn
inline bool loadPresetByIndex(Instance* inst, int index, bool announce = true)clapwrap::loadPresetByIndex · clap_wrapper.h:3693announceis false when the HOST asked (preset-load): it already knows. - fn
inline bool presetFromLocation(const clap_plugin_t* plugin, uint32_t locationKind, const char* location, const char* loadKey)[main-thread] clap_plugin_preset_load: a factory preset by index, or a preset file by path -- the user's, a factory one written out, any file the host's browser found. A refusal is told to the host with its reason (clap_host_preset_load's on_error), not only returned as false.clapwrap::presetFromLocation · clap_wrapper.h:3702 - fn
inline const std::string& uiHtml()The page source, for a webview editor: the project'sclapwrap::uiHtml · clap_wrapper.h:3741uihtmlwhen it defines one, else a plain panel generated from the parameter table. - fn
inline std::string bridgeScriptFor(const Instance& inst)The bridge for this instance's page, with the values it has NOW.clapwrap::bridgeScriptFor · clap_wrapper.h:3759More
Built at each open: a page draws its first frame from the parameter table the bridge hands it, before the editor's tick has pushed anything, and a table holding the defaults drew every control at its default for that frame and up to 33 ms after it. A parameter past SONORE_NUM_PARAMS (a release build loading a descriptor the two disagree on) keeps its default, as it has no slot to read.
- fn
inline bool hasWebPage()Whether this plugin supplied a face of its own.clapwrap::hasWebPage · clap_wrapper.h:3767 - fn
inline EditorChoice editorChoice()Which editor this build, this platform and this descriptor add up to.clapwrap::editorChoice · clap_wrapper.h:3776 - fn
inline gfx::EditorHost makeEditorHost(Instance* inst)[main-thread] The four things the native editor needs from the plugin.clapwrap::makeEditorHost · clap_wrapper.h:3796More
Every edit goes down the SAME queue the page uses, so a knob turned here is automation the host records, an undo step the host can collapse, and a value the DSP receives on the audio thread -- not a private number the editor keeps to itself.
- fn
inline const char* nativeWindowApi()Which windowing API this OS speaks. Keyed on the PLATFORM, never on whether a webview backend exists: a headless build still has to tell the host the truth about what kind of window it would embed into.clapwrap::nativeWindowApi · clap_wrapper.h:3902 - fn
inline bool guiIsApiSupported(const clap_plugin_t*, const char* api, bool isFloating)clapwrap::guiIsApiSupported · clap_wrapper.h:3912 - fn
inline bool guiGetPreferredApi(const clap_plugin_t*, const char** api, bool* isFloating)clapwrap::guiGetPreferredApi · clap_wrapper.h:3917 - fn
inline std::string uiTickScript(Instance& inst)Everything the page does not know yet, as one script.clapwrap::uiTickScript · clap_wrapper.h:3936More
This body existed three times -- once per format with an editor -- and was identical in all three down to the comment about setlocale(). That is the shape a bug takes when it gets fixed in one copy: the AU editor would keep a defect the CLAP one had lost, and nothing would fail until somebody opened the plugin in Logic. So the formats now differ only in how they reach their Instance, which is the only thing about them that is actually different.
[main-thread] at the editor's ~30 Hz clock.
- fn
inline int64_t steadyNowMs()clapwrap::steadyNowMs · clap_wrapper.h:4017 - fn
inline int64_t webviewParkAfterMs()How long a hidden web editor keeps its renderer, in milliseconds.clapwrap::webviewParkAfterMs · clap_wrapper.h:4039More
webview_bench measured what the alternatives buy. Suspending a hidden renderer (TrySuspend + the LOW memory target, both confirmed succeeding) returned -2 MB across six editors: freezing stops CPU, it does not return commit, and ~30 MB per editor stays resident for as long as the host keeps the editor object alive -- which for a hidden editor in a session is forever. Destroying the webview returns all of it, measured, and our pages rebuild from C++ state by design (parameters, meters and MIDI state all live on this side of the bridge), so the cost is ~half a second of blank on the next open of an editor nobody had looked at for half a minute.
0 disables parking. The env override exists for tuning and for the bench, which cannot wait thirty real seconds per measurement.
- fn
inline bool attachWebviewTo(Instance* inst, void* parent, uint32_t w, uint32_t h)Attach the webview to a host-provided parent. The one place the void* the shared code carries is cast back to the handle type this platform's backend declares -- used from set_parent, and again when a parked editor is shown.clapwrap::attachWebviewTo · clap_wrapper.h:4060 - fn
inline void guiTick(Instance* inst)Push whatever the page doesn't know yet. Runs on the main thread from the backend's ~30 Hz timer.clapwrap::guiTick · clap_wrapper.h:4074 - fn
inline bool showHostContextMenu(Instance* inst, int paramIndex, int x, int y)[main-thread] Ask the host to show ITS menu for one parameter.clapwrap::showHostContextMenu · clap_wrapper.h:4135More
MIDI learn, "remove automation", "show automation lane" -- a plugin cannot offer any of them and the host cannot offer them if the plugin swallows the right-click. So the click is handed straight back.
Two things can honestly go wrong and neither is an error: a host may not implement the extension at all, and one that does may still say it cannot pop up a menu right now -- can_popup depends on the windowing system and is explicitly invalidated when the plugin window is created. Both end in the page's right-click doing nothing, which is what it did before.
- fn
inline void chooseFileForPage(Instance* inst, const BridgeMessage& msg)[main-thread] Show a native file browser and tell everyone what came back.clapwrap::chooseFileForPage · clap_wrapper.h:4163More
The DSP first, then the page. That order matters on a reload: the page usually reacts by showing the filename, and showing it before the DSP has accepted the file would put a name on screen for a sample that is not loaded yet.
- fn
inline void pageReady(Instance* inst)[main-thread] The page's bridge came up in a real document.clapwrap::pageReady · clap_wrapper.h:4201More
The tick pushes the state bag only when it is dirty, and parameters only when they differ from what it last pushed. The webview accepts scripts from the moment it exists, but the page's document is created later, by the navigation -- and a push made in between ran in the blank document that navigation replaced. The flag was cleared all the same, so the page never received the state at all: a sampler's slot drawn empty beside the file it holds, and an intermittent GUI-bridge test failure, worse under load. Every format answers this the same way: push it all again.
All of it, the held keys too. They are sent only when they change, and this list once left them out: an editor reopened over a held chord, or rebuilt after a park, drew its keyboard with every key up -- the new page had never been told, and nothing changed to tell it.
- fn
inline void guiOnMessage(Instance* inst, const BridgeMessage& msg)A page message arriving on the main thread. It never touches audio state directly: everything crosses to the audio thread through the lock-free queue, which is what keeps a UI event out of the audio callback's way.clapwrap::guiOnMessage · clap_wrapper.h:4220 - fn
inline void editorOnFd(const clap_plugin_t* plugin, int fd, clap_posix_fd_flags_t)clapwrap::editorOnFd · clap_wrapper.h:4322 - fn
inline void editorOnTimer(const clap_plugin_t* plugin, clap_id timerId)clapwrap::editorOnTimer · clap_wrapper.h:4328 - fn
inline bool registerEditorPump(Instance* inst)Ask the host to drive the editor, and say whether anyone will.clapwrap::registerEditorPump · clap_wrapper.h:4346More
Returns true when the SDK can drive it alone -- Windows -- or when a host service was successfully registered.
- fn
inline void unregisterEditorPump(Instance* inst)clapwrap::unregisterEditorPump · clap_wrapper.h:4373 - fn
inline bool guiCreate(const clap_plugin_t* plugin, const char* api, bool isFloating)clapwrap::guiCreate · clap_wrapper.h:4394 - fn
inline void guiDestroy(const clap_plugin_t* plugin)clapwrap::guiDestroy · clap_wrapper.h:4418 - fn
inline uint32_t scaledPixels(uint32_t logical, double factor)Logical pixels to device pixels, rounded rather than truncated: at 150% a 620-pixel editor is 930 and at 125% it is 775, and truncation loses a pixel off the edge on every odd multiple.clapwrap::scaledPixels · clap_wrapper.h:4441 - fn
inline bool guiSetScale(const clap_plugin_t* plugin, double scale)The host telling us what a logical pixel is worth on this display.clapwrap::guiSetScale · clap_wrapper.h:4459More
This used to return FALSE, meaning "I ignore your scale, I handle DPI myself", with a comment saying the webview does it through the OS. Half true, and the wrong half: the webview draws each CSS pixel as
scaleDEVICE pixels, which is exactly why the window has to bescaletimes bigger. It was already right in principle and still produced a 620-pixel window showing the left two thirds of a 1240-pixel page at 200%.The VST3 side implements the same arithmetic through IPlugViewContentScaleSupport; the two formats disagreeing about the same webview would be a plugin that is sharp in one host and clipped in another.
- fn
inline bool guiGetSize(const clap_plugin_t* plugin, uint32_t* width, uint32_t* height)In DEVICE pixels, which is what the extension asks for and what the host makes the window out of.clapwrap::guiGetSize · clap_wrapper.h:4485 - fn
inline bool guiCanResize(const clap_plugin_t*)A fixed-size interface says so, and a host that asks stops drawing a grip the user cannot use. Either axis being resizable counts: CLAP asks the yes/no question here and the per-axis one in the hints below.clapwrap::guiCanResize · clap_wrapper.h:4495 - fn
inline bool guiGetResizeHints(const clap_plugin_t*, clap_gui_resize_hints_t* hints)clapwrap::guiGetResizeHints · clap_wrapper.h:4499 - fn
inline bool guiAdjustSize(const clap_plugin_t* plugin, uint32_t* width, uint32_t* height)clapwrap::guiAdjustSize · clap_wrapper.h:4522 - fn
inline bool guiSetSize(const clap_plugin_t* plugin, uint32_t width, uint32_t height)clapwrap::guiSetSize · clap_wrapper.h:4542 - fn
inline void* parentHandle(const clap_window_t* window)The handle a host hands over, as a void*. The one place platforms genuinely differ: the handle is a different type on each OS (HWND / NSView* / X window id). The three names are the same union member; keeping the branch here means nothing below it branches.clapwrap::parentHandle · clap_wrapper.h:4579 - fn
inline void requestHostResize(Instance* inst, int width, int height)The user dragged an edge. ASK the host for that size.clapwrap::requestHostResize · clap_wrapper.h:4619More
A plugin editor does not own its window and must never resize itself: the frame belongs to the host, and a view that changed size on its own would sit at one size inside a frame of another. So this asks, and nothing here changes until the host answers -- which it may decline to do, and declining is a legitimate answer.
The answer is request_resize's own return value. True means the host has taken the size and, the extension says, "doesn't have to call set_size()". This waited for set_size all the same, so in a host that took the extension at its word the frame grew and the editor did not: the window bigger than what was drawn in it, get_size still reporting the old size, and the session saving that one. An accepted size is applied here, through set_size's own path; a host that calls set_size as well applies the same size twice.
Clamped before asking, through the same function every other size path uses. The border already constrains the drag with the same numbers, so this is belt and braces -- but it is the entry point a host sees, and an entry point that trusts its caller because another one checks is an unchecked entry point.
In DEVICE pixels, because that is what the extension is defined in and what the host builds the window out of.
- fn
template <typename T> inline gfx::NativeEditor::ContentFactory customEditorFactory(T& dsp)[main-thread] The DSP's own editor, as what the native editor builds its window's content from -- or nothing, and the generic editor it always had.clapwrap::customEditorFactory · clap_wrapper.h:4655More
Every format reaches it through the same Instance, so a plugin's face is the same in CLAP, VST3 and AU. A template, as sendFile is, so a DSP without the method never instantiates the call.
- fn
inline void prepareNativeEditor(Instance* inst)Everything a native editor needs told before it opens. Two call sites -- the ordinary one and the fallback when a webview fails to start -- and a helper so the second cannot quietly lack what the first has.clapwrap::prepareNativeEditor · clap_wrapper.h:4669 - fn
inline bool guiSetParent(const clap_plugin_t* plugin, const clap_window_t* window)clapwrap::guiSetParent · clap_wrapper.h:4675 - fn
inline bool guiSetTransient(const clap_plugin_t*, const clap_window_t*)clapwrap::guiSetTransient · clap_wrapper.h:4743 - fn
inline void guiSuggestTitle(const clap_plugin_t*, const char*)clapwrap::guiSuggestTitle · clap_wrapper.h:4747 - fn
inline bool guiShow(const clap_plugin_t* plugin)clapwrap::guiShow · clap_wrapper.h:4749 - fn
inline bool guiHide(const clap_plugin_t* plugin)clapwrap::guiHide · clap_wrapper.h:4782 - fn
inline const void* pluginGetExtension(const clap_plugin_t*, const char* id)clapwrap::pluginGetExtension · clap_wrapper.h:4812 - fn
inline void pluginOnMainThread(const clap_plugin_t* plugin)clapwrap::pluginOnMainThread · clap_wrapper.h:4864 - fn
inline const char* const* features()clapwrap::features · clap_wrapper.h:4874 - fn
inline const clap_plugin_descriptor_t* descriptor()clapwrap::descriptor · clap_wrapper.h:4883 - fn
inline uint32_t factoryCount(const clap_plugin_factory*)clapwrap::factoryCount · clap_wrapper.h:4902 - fn
inline const clap_plugin_descriptor_t* factoryGetDescriptor(const clap_plugin_factory*, uint32_t index)clapwrap::factoryGetDescriptor · clap_wrapper.h:4904 - fn
inline const clap_plugin_t* factoryCreate(const clap_plugin_factory*, const clap_host_t* host, const char* pluginId)clapwrap::factoryCreate · clap_wrapper.h:4909 - fn
inline const clap_preset_discovery_provider_descriptor_t* presetProviderDescriptor()clapwrap::presetProviderDescriptor · clap_wrapper.h:4970 - fn
inline std::string existingUserPresetFolder()The folder the user's presets are in, or empty when it does not exist.clapwrap::existingUserPresetFolder · clap_wrapper.h:4997 - fn
inline bool hasPresetsToDiscover()clapwrap::hasPresetsToDiscover · clap_wrapper.h:5002 - fn
inline bool presetProviderInit(const clap_preset_discovery_provider_t* provider)clapwrap::presetProviderInit · clap_wrapper.h:5006 - fn
inline void presetProviderDestroy(const clap_preset_discovery_provider_t* provider)clapwrap::presetProviderDestroy · clap_wrapper.h:5055 - fn
inline bool presetProviderPluginMetadata(const clap_preset_discovery_metadata_receiver_t* receiver)The compiled-in presets as one container: the fallback location.clapwrap::presetProviderPluginMetadata · clap_wrapper.h:5060 - fn
inline bool presetProviderFileMetadata(const char* location, const clap_preset_discovery_metadata_receiver_t* receiver)One preset file. Read from its header alone -- a host indexes thousands of these and "get_metadata has to be fast" -- and a file that is not a preset of this plugin is reported through on_error with the reason. A file for ANOTHER plugin that happens to be in the folder declares nothing and is not an error: it is simply not ours.clapwrap::presetProviderFileMetadata · clap_wrapper.h:5083 - fn
inline bool presetProviderGetMetadata(const clap_preset_discovery_provider_t*, uint32_t locationKind, const char* location, const clap_preset_discovery_metadata_receiver_t* receiver)clapwrap::presetProviderGetMetadata · clap_wrapper.h:5109 - fn
inline const void* presetProviderGetExtension(const clap_preset_discovery_provider_t*, const char*)clapwrap::presetProviderGetExtension · clap_wrapper.h:5120 - fn
inline uint32_t presetFactoryCount(const clap_preset_discovery_factory_t*)Offered when there is something to browse: factory presets, or a user folder. A provider that indexes nothing is worse than no provider.clapwrap::presetFactoryCount · clap_wrapper.h:5127 - fn
inline const clap_preset_discovery_provider_descriptor_t* presetFactoryGetDescriptor(const clap_preset_discovery_factory_t*, uint32_t index)clapwrap::presetFactoryGetDescriptor · clap_wrapper.h:5131 - fn
inline const clap_preset_discovery_provider_t* presetFactoryCreate(const clap_preset_discovery_factory_t*, const clap_preset_discovery_indexer_t* indexer, const char* providerId)clapwrap::presetFactoryCreate · clap_wrapper.h:5137 - fn
inline const char* pluginDescriptorProblem(int* badParam = nullptr)What is wrong with kDesc, if anything: descriptorProblem(), plus the one thing only the wrapper can know -- that the table is the size every parameter array here was given. A user's build never asked: only the real-time test ran the check, so a duplicate id or an inverted channel range reached a host.clapwrap::pluginDescriptorProblem · clap_wrapper.h:5161 - fn
inline bool entryInit(const char*)A debug build will not load with a malformed descriptor, and says why on stderr; a release build loads as it always did. The standalone's --verify fails on one in every build.clapwrap::entryInit · clap_wrapper.h:5172 - fn
inline void entryDeinit()clapwrap::entryDeinit · clap_wrapper.h:5187 - fn
inline const void* entryGetFactory(const char* factoryId)clapwrap::entryGetFactory · clap_wrapper.h:5189
Aliases 1
- using
using PlatformWebView = win32::WebViewHostOne alias per platform, so everything below this line is written once. Only set_parent still branches: the handle a host hands over is genuinely a different type on each OS.clapwrap::PlatformWebView · clap_wrapper.h:100
Enums 1
- enum
enum class StateScopeWhat a state restore is FOR, which decides the fields that are the session's rather than the sound's.clapwrap::StateScope · clap_wrapper.h:491More
- 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.
Constants 5
- const
constexpr uint32_t kMaxAudioChannels = 8clapwrap::kMaxAudioChannels · clap_wrapper.h:113 - const
constexpr uint32_t kMaxAuxOutputs = 32clapwrap::kMaxAuxOutputs · clap_wrapper.h:114 - const
constexpr uint32_t kStateVersion = 6v1: header + values. v2: appends one byte, the host bypass flag. v3: appends a StateBag, the key/value state a plugin has beyond its parameters, which is the only way a sampler can remember WHICH FILE it loaded. Loaders accept all three, and an older blob simply leaves the newer fields at their defaults, so no saved session is ever orphaned. v4: the selected factory preset. v5: the editor's size. v6: the preset the user is on (user_presets.h) -- factory or their own, its name, and a hash of its sound -- so a project reopens showing the preset it was saved on, marked modified if it was.clapwrap::kStateVersion · clap_wrapper.h:476 - const
constexpr uint32_t kMaxStatePresetText = 4096The longest preset name or file name the v6 field carries.clapwrap::kMaxStatePresetText · clap_wrapper.h:494 - const
constexpr clap_id kClapBypassParamId = 0x62797073uThe bypass's id: fixed, and far from every index a parameter table can reach ('byps' in ASCII, below 2^31). It was one past the DSP's own parameters, so a version that APPENDED a parameter moved it -- and a session's bypass automation, saved against the old number, drove the new parameter. Now appending moves nothing.clapwrap::kClapBypassParamId · clap_wrapper.h:1191
Macros 2
- macro
#define SONORE_UI_WIDTH 700The plugin's face. A project that defines SONORE_UI_HTML to its own page gets it in a webview; without one the plugin gets the SDK-drawn native editor (EditorKind::Auto, plugin.h), or the generated HTML panel below when it asks for a webview anyway.clap_wrapper.h:87 - macro
#define SONORE_UI_HEIGHT 420clap_wrapper.h:90