lv2_wrapper.h
The LV2 format wrapper.
#include <sonore/lv2_wrapper.h>- 4types
- 49other declarations
- Guides: Getting started, Platforms and formats
On this page
Design notes
LV2 is the open standard (ISC-licensed headers, vendored) that Linux DAWs (Ardour, Reaper on Linux, Carla, Zrythm) treat as native. The plugin is a shared library plus a bundle of Turtle (.ttl) metadata describing every port; hosts read the TTL to know the plugin's shape BEFORE loading any code.
Include it AFTER clap_wrapper.h, like the other formats: the CLAP wrapper owns the shared machinery (Instance, parameters, the UI queue) and this file adapts it, so one source builds .clap, .vst3 and .lv2 that cannot drift.
The TTL is GENERATED, never hand-written: compiling the same source with SONORE_LV2_TTLGEN yields a tiny program that emits manifest.ttl and the plugin's .ttl from the descriptor. Metadata written by hand drifts from the code the first time anyone adds a parameter: generating both from one table is the whole reason the descriptor exists.
Port layout (index order is the contract, and the TTL states it):
effect: [0..P-1] control in | [P] audio in L | [P+1] in R
| [P+2] out L | [P+3] out R
instrument: [0..P-1] control in | [P] atom MIDI in | [P+1] out L | [P+2] out R
LV2's control ports are one float* each, connected by the host and read per run(): parameters land at block granularity, which the toolkit's smoothers exist to make inaudible (same story as every other format here).
Types 4
LV2_Worker_Schedule, the host's half: schedule_work() hands a job to the worker thread. Spelled here for its layout; see above.
LV2_Worker_Interface, the plugin's half: work() runs on the worker thread, work_response() back on the audio thread before the next run(), and end_run() after each run().
LV2_Log_Log: the host's log, for non-realtime contexts only.
One LV2 instance: the Instance every format wrapper shares, plus what LV2 adds -- the host's port pointers, the URIDs it mapped, the worker and the log it offered, and the MIDI and transport of the current run().
Functions 44
- fn
inline const char* outputGuardWork()What run() hands the worker to have the output guard's report made: an exact byte string, which no file-load payload can equal (a payload ends with a path after the purpose's terminator, and this ends at its own).lv2::outputGuardWork · lv2_wrapper.h:122 - fn
inline void pluginUri(char* out, size_t capacity)The plugin URI: derived from the reverse-DNS id, which is already unique per product. LV2 identifies plugins by URI exactly as VST3 does by UID.lv2::pluginUri · lv2_wrapper.h:133 - fn
inline LV2_Handle instantiate(const LV2_Descriptor*, double sampleRate, const char*, const LV2_Feature* const* features)lv2::instantiate · lv2_wrapper.h:280 - fn
inline void updateFreeWheel(Plugin* self)Re-run prepare() when the host starts or stops freewheeling.lv2::updateFreeWheel · lv2_wrapper.h:345 - fn
inline void connectPort(LV2_Handle handle, uint32_t port, void* data)lv2::connectPort · lv2_wrapper.h:360 - fn
inline void refreshUiState(Plugin* self)Rebuild the answer an interface gets when it asks what the plugin has.lv2::refreshUiState · lv2_wrapper.h:444 - fn
inline void freeRetiredUiText(Plugin* self)[worker-thread or main-thread] Free what work_response() has finished with. Never on the audio thread, which only pushes.lv2::freeRetiredUiText · lv2_wrapper.h:454 - fn
inline void activate(LV2_Handle handle)lv2::activate · lv2_wrapper.h:468 - fn
inline void claimMidiOut(Plugin* self)Claim the MIDI output buffer for this run(): note the host's capacity and leave an empty sequence, whatever follows. First thing in run(), because a host reads atom.size back as how much was written -- and a run() of zero frames, or one that returned early, claimed it nowhere and left the capacity there, which reads as a buffer full of events.lv2::claimMidiOut · lv2_wrapper.h:500 - fn
inline void writeMidiOut(Plugin* self, uint32_t frameOffset, uint32_t frames)Append this slice's emitted MIDI to the host's output sequence.lv2::writeMidiOut · lv2_wrapper.h:516 - fn
inline void writeUiNotify(Plugin* self)[audio-thread] Put the state on the notify port, if anything is owed.lv2::writeUiNotify · lv2_wrapper.h:582 - fn
inline bool atomNumber(const Plugin* self, const LV2_Atom* a, double* out)A number out of an atom of any of the four types a host may send one in.lv2::atomNumber · lv2_wrapper.h:644 - fn
inline bool readTimePosition(Plugin* self, const LV2_Atom* obj)[audio-thread] Take a time:Position into the transport.lv2::readTimePosition · lv2_wrapper.h:663 - fn
inline void advanceTransport(Plugin* self, uint32_t frames)[audio-thread] Carry the transport overlv2::advanceTransport · lv2_wrapper.h:714framesat the host's speed, so the next run() starts where this one ended. - fn
inline void ttlNumber(char* out, size_t capacity, float value)A float as Turtle: the fewest significant digits that read back as the same float, with a full stop whatever the locale. %g gave six, so a default of 0.70710678 was published as 0.707107 -- a different float, and a host that resets a control to its default put it somewhere the plugin never did. Nine is always enough; 0.1 still prints as 0.1.lv2::ttlNumber · lv2_wrapper.h:737 - fn
inline int frameInRun(int64_t frame, uint32_t sampleCount)An atom event's frame, clamped into a run oflv2::frameInRun · lv2_wrapper.h:749sampleCount: at or past the end a note-off was never reached and the note hung, and a frame past what an int holds came out negative (see clapwrap::eventTime). - fn
inline void run(LV2_Handle handle, uint32_t sampleCount)lv2::run · lv2_wrapper.h:754 - fn
inline void reportOutputGuard(Plugin* self)[worker-thread or main-thread] The output guard's report, once: a warning in the host's log where it gave one, stderr otherwise. Never run(): the log's printf is for non-realtime contexts only.lv2::reportOutputGuard · lv2_wrapper.h:987 - fn
inline Lv2WorkerStatus workerWork(LV2_Handle handle, Lv2WorkerRespondFunction respond, Lv2WorkerRespondHandle responder, uint32_t size, const void* data)[worker-thread] Read the file the interface picked.lv2::workerWork · lv2_wrapper.h:1009 - fn
inline Lv2WorkerStatus workerResponse(LV2_Handle handle, uint32_t size, const void* data)[audio-thread] The worker finished. Swapping two std::strings moves two pointers and allocates nothing, and pushing the husk is a compare-and-swap, which is what makes this legal here.lv2::workerResponse · lv2_wrapper.h:1059 - fn
inline void deactivate(LV2_Handle handle)Instantiation class, so not realtime: the output guard's report for a host with no worker, or one whose worker has not got to it yet.lv2::deactivate · lv2_wrapper.h:1082 - fn
inline void cleanup(LV2_Handle handle)lv2::cleanup · lv2_wrapper.h:1084 - fn
inline LV2_State_Status stateSave(LV2_Handle handle, LV2_State_Store_Function store, LV2_State_Handle host, uint32_t, const LV2_Feature* const*)lv2::stateSave · lv2_wrapper.h:1099 - fn
inline LV2_State_Status stateRestore(LV2_Handle handle, LV2_State_Retrieve_Function retrieve, LV2_State_Handle host, uint32_t, const LV2_Feature* const*)lv2::stateRestore · lv2_wrapper.h:1132 - fn
inline const void* extensionData(const char* uri)lv2::extensionData · lv2_wrapper.h:1162 - fn
inline const LV2_Descriptor* descriptor()lv2::descriptor · lv2_wrapper.h:1177 - fn
inline const char* audioPortSymbol(int c)The suffix of main-bus audio portlv2::audioPortSymbol · lv2_wrapper.h:1202c's symbol and name. "l"/"r" for the first two, which every existing session's port symbols already say and which must not change; the channel's number after that. It used to bec ? 'r' : 'l'for every channel, so a quad bus declared four ports and three of them were "in_r" -- symbols LV2 requires to be unique. - fn
inline const char* audioPortName(int c)lv2::audioPortName · lv2_wrapper.h:1206 - fn
inline void writeTurtleString(std::FILE* out, const char* text)Escape a string for a Turtle literal.lv2::writeTurtleString · lv2_wrapper.h:1217 - fn
inline void writeTurtleIri(std::FILE* out, const char* text)An IRI inside <...>: the characters Turtle forbids there are percent-encoded, so a URL with a space or a stray quote yields a file every host can still parse rather than one none can.lv2::writeTurtleIri · lv2_wrapper.h:1233 - fn
inline const char* lv2UnitFor(const char* unit)LV2's own unit for a parameter's unit string -- the units extension's spelling of the ones the SDK uses (the AU wrapper maps the same set) -- or null for one it has no word for, which the TTL then declares itself.lv2::lv2UnitFor · lv2_wrapper.h:1247 - fn
inline void lv2Symbol(const char* id, char* out, size_t capacity)lv2:symbol is [_a-zA-Z][_a-zA-Z0-9]*. A parameter id outside that -- a hyphen, a leading digit -- is mapped onto it, and the SAME mapping is used for the presets file, so the two always name the same port.lv2::lv2Symbol · lv2_wrapper.h:1264 - fn
inline bool lv2ReservedSymbol(const char* symbol)Whetherlv2::lv2ReservedSymbol · lv2_wrapper.h:1281symbolis one the wrapper may give a port of its own: the designated controls, the meters, the atom ports, and every audio port name it can write (in_/out_ l, r, 3..8 and x, sc_l and sc_r, auxB_C). - fn
inline std::vector<std::string> lv2ParamSymbols()Every parameter's lv2:symbol, in table order.lv2::lv2ParamSymbols · lv2_wrapper.h:1319 - fn
inline int numFactoryPresets()How many factory presets are genuinely usable.lv2::numFactoryPresets · lv2_wrapper.h:1343 - fn
inline void writePresetsTtl(std::FILE* out, const char* uri)Factory presets in LV2's own spelling: one pset:Preset per preset, naming each port by the symbol the plugin published and the value it should take.lv2::writePresetsTtl · lv2_wrapper.h:1354 - fn
inline const char* lv2ClassFor(const PluginDescriptor& d)Map the descriptor's plain-word category onto lv2core's plugin taxonomy. Hosts (and lv2lint) treat the bare base class as "uncategorised", so a known word buys real browser placement; an unknown one falls back rather than guessing.lv2::lv2ClassFor · lv2_wrapper.h:1408 - fn
inline void lv2VersionFromProduct(const char* version, int* outMinor, int* outMicro)LV2's version pair is its own numbering domain: even minor >= 2 announces a stable public release, 0 or odd reads as pre-release/development to every host (and lv2lint flags it). Copying the product's semver digits verbatim would brand 1.0.1 unstable, so map monotonically onto the convention: doubling keeps ordering and keeps everything even.lv2::lv2VersionFromProduct · lv2_wrapper.h:1439 - fn
inline void writeTtl(std::FILE* out, const char* binaryName)lv2::writeTtl · lv2_wrapper.h:1455 - fn
inline int ttlGeneratorMain(int argc, char** argv)lv2::ttlGeneratorMain · lv2_wrapper.h:1927 - fn
LV2_SYMBOL_EXPORT const sonore::lv2ui::Lv2UiDescriptor* lv2ui_descriptor( uint32_t index)The interface entry point, beside the plugin's own.lv2_wrapper.h:2044 - fn
int main(int argc, char** argv)lv2_wrapper.h:2051 - fn
LV2_SYMBOL_EXPORT const LV2_Descriptor* lv2_descriptor(uint32_t index)The host's entry point.lv2_wrapper.h:2054 - fn
extern "C" int sonore_lv2_ttlgen_main(int argc, char** argv)lv2_wrapper.h:2066
Enums 1
Constants 1
- const
constexpr uint32_t kUiNotifyMinimumBytes = 65536What the notify port asks a host for (rsz:minimumSize): room for the state as one JSON message.lv2::kUiNotifyMinimumBytes · lv2_wrapper.h:566
Macros 3
- macro
#define SONORE_LV2_WORKER__schedule "http://lv2plug.in/ns/ext/worker#schedule"Declared here rather than vendored, the same way lv2_ui.h declares the UI ABI: the SDK vendors the headers it uses heavily and spells out the small ones, and a file of ours claiming to BE upstream's would be worse than one that says it mirrors the spec.lv2_wrapper.h:73 - macro
#define SONORE_LV2_TIME "http://lv2plug.in/ns/ext/time#"The time extension is URIs only -- time:Position is an atom object whose properties a host fills in -- so its header is not vendored for one prefix.lv2_wrapper.h:77 - macro
#define SONORE_LV2_LOG__log "http://lv2plug.in/ns/ext/log#log"Declared the same way, for one message: the output guard's report, which goes to the host's log as a warning where the host offers one. The spec allows its printf from any NON-realtime context, so it is called from the worker or from deactivate()/cleanup(), never from run().lv2_wrapper.h:107