Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
lv2_adapter.hpp
Go to the documentation of this file.
1#pragma once
2
3// LV2 Adapter for Pulp
4// Implements the LV2 plugin interface wrapping pulp::format::Processor
5// Built from LV2 specification headers (ISC license)
6//
7// LV2 uses a C API with:
8// - URI-based plugin identification
9// - Port-based I/O (audio ports + control ports for parameters)
10// - TTL manifest files for discovery
11
12#include <pulp/format/detail/playhead_diff.hpp>
14#include <pulp/midi/buffer.hpp>
16
17#include <lv2/core/lv2.h>
18#include <lv2/options/options.h>
19#include <lv2/urid/urid.h>
20
21#include <cstdint>
22
24
25// Per-bus channel ceiling. Independent of, but intentionally equal to,
26// boundary::kBoundaryMaxChannels (adapter_boundary.hpp) — this lightweight LV2
27// header deliberately does not include the boundary header, so the value is
28// duplicated rather than aliased. If the shared ceiling changes, change this too.
29static constexpr int kMaxChannels = 8;
30
31// Block size assumed when the host supplies no `bufsz:maxBlockLength` option.
32// A host that declares `bufsz:boundedBlockLength` always supplies the option;
33// this is the floor for one that does not.
34static constexpr int kDefaultMaxBlockLength = 4096;
35
36// Byte capacity requested for each atom port via `lv2:minimumSize`.
37//
38// An LV2 host allocates the sequence buffer, so this is the plugin's only say
39// in how many events fit. Sized for the adapter's worst-case block: the
40// realtime MIDI capacity (see lv2_entry.hpp's kRealtimeMidiEventCapacity)
41// times 24 bytes per event — a 16-byte LV2_Atom_Event header plus a 3-byte
42// MIDI message padded to the sequence's 8-byte alignment — plus the sequence
43// header, rounded up to a power of two. lv2_entry.hpp static_asserts the
44// relationship so raising the event capacity cannot silently outgrow this.
45static constexpr uint32_t kAtomPortMinimumSize = 32768;
46
47// Property key URI for the single host-facing state blob stored through
48// `state:interface`. One `atom:Chunk` carrying the same versioned envelope
49// `plugin_state_io::serialize()` hands every other format.
50inline constexpr const char* kStateBlobUri = "http://pulp.audio/ns/state#blob";
51
62
82 int num_params = 0;
83 bool accepts_midi = false;
84 bool produces_midi = false;
85
86 [[nodiscard]] constexpr int audio_in_begin() const noexcept {
87 return 0;
88 }
89 [[nodiscard]] constexpr int audio_out_begin() const noexcept {
90 return num_audio_inputs;
91 }
92 [[nodiscard]] constexpr int control_begin() const noexcept {
94 }
95 [[nodiscard]] constexpr int atom_in_begin() const noexcept {
96 return control_begin() + num_params;
97 }
98 [[nodiscard]] constexpr int atom_out_begin() const noexcept {
99 return atom_in_begin() + (accepts_midi ? 1 : 0);
100 }
101 [[nodiscard]] constexpr int latency_index() const noexcept {
102 return atom_out_begin() + (produces_midi ? 1 : 0);
103 }
104
105 [[nodiscard]] constexpr int port_count() const noexcept {
106 return latency_index() + 1;
107 }
108
111 [[nodiscard]] constexpr Lv2PortKind kind_of(int port) const noexcept {
112 if (port < 0)
113 return Lv2PortKind::None;
114 if (port >= audio_in_begin() && port < audio_out_begin())
116 if (port >= audio_out_begin() && port < control_begin())
118 if (port >= control_begin() && port < atom_in_begin())
120 if (accepts_midi && port == atom_in_begin())
121 return Lv2PortKind::AtomIn;
122 if (produces_midi && port == atom_out_begin())
124 if (port == latency_index())
126 return Lv2PortKind::None;
127 }
128
132 [[nodiscard]] constexpr int slot_of(int port) const noexcept {
133 switch (kind_of(port)) {
135 return port - audio_in_begin();
137 return port - audio_out_begin();
139 return port - control_begin();
140 default:
141 return 0;
142 }
143 }
144};
145
150 LV2_URID atom_object = 0;
151 LV2_URID atom_blank = 0; // pre-1.8 hosts still send Blank for an object
152 LV2_URID atom_float = 0;
153 LV2_URID atom_double = 0;
154 LV2_URID atom_int = 0;
155 LV2_URID atom_long = 0;
156 LV2_URID atom_bool = 0;
157
158 LV2_URID time_position = 0;
159 LV2_URID time_bar = 0;
160 LV2_URID time_beat = 0;
161 LV2_URID time_beat_unit = 0;
162 LV2_URID time_beats_per_bar = 0;
164 LV2_URID time_frame = 0;
165 LV2_URID time_speed = 0;
166};
167
176 bool has_speed = false;
177 bool has_frame = false;
178 bool has_bar = false;
179 bool has_beat = false;
180 bool has_beats_per_bar = false;
181 bool has_beat_unit = false;
183
184 float speed = 0.0f;
185 int64_t frame = 0;
186 int64_t bar = 0;
187 double beat = 0.0;
188 float beats_per_bar = 4.0f;
189 int32_t beat_unit = 4;
190 float beats_per_minute = 120.0f;
191
192 [[nodiscard]] bool any() const noexcept {
195 }
196};
197
198// LV2 plugin instance — wraps a Pulp Processor
200 // The store is declared before the Processor so it is destroyed after it.
201 // `Processor::state()` dereferences a pointer to this store, and a Processor
202 // may read it from its destructor or from a worker thread that destructor is
203 // about to join. Reversing these two lines hands that thread a freed store.
205 std::unique_ptr<Processor> processor;
206 // Param-events sidecar, set on the Processor each run() so the contract is
207 // uniform across formats. LV2 control-port values are applied through
208 // `store` as before; this queue carries no events yet (atom-based
209 // sample-accurate param events are future work), but it gives the Processor
210 // a non-null queue and pairs with the RT-safety guard around process().
213
214 // Pre-reserved MIDI scratch, owned by the instance so run() never
215 // heap-allocates on the audio thread. Sized once in instantiate() with
216 // set_realtime_capacity_limit(true) — mirrors the CLAP/VST3 adapters
217 // (clap_adapter.cpp reserve() + set_realtime_capacity_limit(true)).
218 // run() clear()s and reuses these every block instead of constructing
219 // fresh stack-local buffers whose first add() would allocate.
222
223 // Audio working state
224 double sample_rate = 48000.0;
225
226 // Block ceiling the Processor was prepared for. Read from the host's
227 // `bufsz:maxBlockLength` option in instantiate() when the options feature
228 // is present, else kDefaultMaxBlockLength. run() clamps to it so a host
229 // that neither bounds its block length nor declares the option cannot
230 // overrun scratch sized by prepare().
232
233 // Port connections (set by connect_port). `Lv2PortLayout` above owns the
234 // index → slot mapping; the audio arrays are fixed at kMaxChannels, and
235 // instantiate() refuses a descriptor whose buses sum past that ceiling in
236 // either direction, so a declared port index can never run off them.
239 float** control_in_ports = nullptr; // One per parameter
240 float** control_out_ports = nullptr; // One per parameter (for output)
241
243 // Width of the first input bus. Ports stay one flat run (the manifest's
244 // numbering is a compatibility contract), so run() splits the connected
245 // input ports at this count: the first ones are the main input, the rest
246 // the Sidechain bus a second declared input bus maps to.
249 int num_params = 0;
250 std::vector<state::ParamID> param_ids; // Maps control port index → ParamID
251
252 // URID feature resolution. The LV2 host passes an LV2_URID_Map feature in
253 // instantiate(); we cache the map function plus the URIDs we need at
254 // runtime so inner-loop code does not call map() on hot paths. All fields
255 // are 0 when the feature is absent; instantiate() returns nullptr in that
256 // case, so real plugin code always sees non-zero values here.
257 LV2_URID_Map* urid_map = nullptr;
258 LV2_URID urid_midi_event = 0; // LV2_MIDI__MidiEvent
259 LV2_URID urid_atom_sequence = 0; // LV2_ATOM__Sequence
260 LV2_URID urid_atom_chunk = 0; // LV2_ATOM__Chunk
261 LV2_URID urid_state_blob = 0; // kStateBlobUri
263
264 // Latched host transport. LV2 hosts are not required to send a
265 // `time:Position` every cycle — some send one only when the transport
266 // changes — so the last one received is retained and its position fields
267 // advanced by the block length while `speed` is non-zero. A fresh Position
268 // always overwrites the extrapolation, so a host that does send one per
269 // cycle never drifts. Without the latch a stale sample position would read
270 // as a seek on every block and reset tempo-synced DSP continuously.
272 bool has_transport = false;
273
274 // Previous-block transport snapshot backing the shared change-flag diff
275 // (`tempo_changed` / `transport_jump` / ...), exactly as the other format
276 // adapters keep one.
277 detail::PlayheadSnapshot playhead_prev{};
278
279 // Atom-port MIDI input. When the plug-in declares accepts_midi, the host
280 // connects an LV2_Atom_Sequence buffer to the port after the control
281 // ports. run() iterates it, extracts MIDI events whose atom `type` field is
282 // urid_midi_event, and feeds them to the Processor.
283 bool accepts_midi = false;
284 void* midi_in_atom = nullptr;
285
286 // Parallel output-atom port for plugins that emit MIDI. The host pre-sizes
287 // the buffer via lv2:minimumSize and signals capacity in the atom.size
288 // field on entry to run(); the plugin overwrites the sequence using the
289 // lv2_atom_sequence_clear + append_event helpers.
290 bool produces_midi = false;
291 void* midi_out_atom = nullptr;
292
293 // Latency-reporting output control port. Emitted unconditionally by
294 // generate_plugin_ttl() as the last port (designation lv2:latency,
295 // portProperty lv2:reportsLatency); connect_port() wires the host's
296 // single-float buffer here and run() writes the processor's current
297 // latency_samples() into it each block, so a latent Pulp processor is
298 // PDC-compensated under LV2 like it is under every other format.
299 float* latency_port = nullptr;
300
303 [[nodiscard]] Lv2PortLayout port_layout() const noexcept {
305 }
306};
307
310LV2_URID_Map* find_urid_map(const LV2_Feature* const* features);
311
315const LV2_Options_Option* find_options(const LV2_Feature* const* features);
316
323int max_block_length_from_options(const LV2_Options_Option* options, LV2_URID key_max_block_length,
324 LV2_URID urid_atom_int, LV2_URID urid_atom_long, int fallback);
325
326// Generate an LV2 TTL manifest string for a plugin
327// This creates the plugin.ttl content with port definitions
328std::string generate_plugin_ttl(const PluginDescriptor& desc,
329 const state::StateStore& store,
330 const std::string& uri);
331
332// Generate the manifest.ttl that points to the plugin binary
333std::string generate_manifest_ttl(const std::string& plugin_uri,
334 const std::string& binary_name);
335
336} // namespace pulp::format::lv2_adapter
Collection of timestamped MIDI events within a single audio buffer period.
Definition buffer.hpp:31
Definition parameter_event_queue.hpp:82
Centralized parameter storage with lock-free value access.
Definition store.hpp:164
Definition lv2_adapter.hpp:23
std::string generate_manifest_ttl(const std::string &plugin_uri, const std::string &binary_name)
const LV2_Options_Option * find_options(const LV2_Feature *const *features)
Resolve the options:options feature from a features array.
constexpr const char * kStateBlobUri
Definition lv2_adapter.hpp:50
static constexpr int kMaxChannels
Definition lv2_adapter.hpp:29
Lv2PortKind
What a host-supplied LV2 port index addresses on a Pulp instance.
Definition lv2_adapter.hpp:53
@ AtomIn
Definition lv2_adapter.hpp:58
@ Latency
Definition lv2_adapter.hpp:60
@ None
Outside this instance's declared port range.
Definition lv2_adapter.hpp:54
@ AudioIn
Definition lv2_adapter.hpp:55
@ Control
Definition lv2_adapter.hpp:57
@ AtomOut
Definition lv2_adapter.hpp:59
@ AudioOut
Definition lv2_adapter.hpp:56
LV2_URID_Map * find_urid_map(const LV2_Feature *const *features)
Resolve LV2_URID_Map from a features array.
static constexpr uint32_t kAtomPortMinimumSize
Definition lv2_adapter.hpp:45
static constexpr int kDefaultMaxBlockLength
Definition lv2_adapter.hpp:34
int max_block_length_from_options(const LV2_Options_Option *options, LV2_URID key_max_block_length, LV2_URID urid_atom_int, LV2_URID urid_atom_long, int fallback)
Read bufsz:maxBlockLength out of a host options array.
std::string generate_plugin_ttl(const PluginDescriptor &desc, const state::StateStore &store, const std::string &uri)
std::unique_ptr< Processor >(*)() ProcessorFactory
Factory function type — plugins provide this to create processor instances.
Definition processor.hpp:1416
Plugin metadata — declared once, immutable.
Definition plugin_descriptor.hpp:245
The single definition of a Pulp LV2 plugin's port ordering.
Definition lv2_adapter.hpp:79
constexpr int audio_in_begin() const noexcept
Definition lv2_adapter.hpp:86
constexpr int slot_of(int port) const noexcept
Offset of port within its own kind — the array index for the multi-slot kinds, and 0 for the single-s...
Definition lv2_adapter.hpp:132
int num_params
Definition lv2_adapter.hpp:82
constexpr int atom_in_begin() const noexcept
Definition lv2_adapter.hpp:95
int num_audio_outputs
Definition lv2_adapter.hpp:81
bool accepts_midi
Definition lv2_adapter.hpp:83
constexpr int atom_out_begin() const noexcept
Definition lv2_adapter.hpp:98
int num_audio_inputs
Definition lv2_adapter.hpp:80
constexpr Lv2PortKind kind_of(int port) const noexcept
Classify a port number.
Definition lv2_adapter.hpp:111
constexpr int control_begin() const noexcept
Definition lv2_adapter.hpp:92
constexpr int port_count() const noexcept
Total ports this plugin declares, latency port included.
Definition lv2_adapter.hpp:105
constexpr int audio_out_begin() const noexcept
Definition lv2_adapter.hpp:89
bool produces_midi
Definition lv2_adapter.hpp:84
constexpr int latency_index() const noexcept
Definition lv2_adapter.hpp:101
A time:Position object decoded into LV2's own units and conventions.
Definition lv2_adapter.hpp:175
bool has_beats_per_minute
Definition lv2_adapter.hpp:182
bool has_bar
Definition lv2_adapter.hpp:178
float beats_per_bar
time-signature numerator
Definition lv2_adapter.hpp:188
bool has_beat_unit
Definition lv2_adapter.hpp:181
bool has_speed
Definition lv2_adapter.hpp:176
double beat
running beat count, in beat_unit beats
Definition lv2_adapter.hpp:187
int64_t frame
sample position on the host timeline
Definition lv2_adapter.hpp:185
bool has_beat
Definition lv2_adapter.hpp:179
int64_t bar
zero-based bar number
Definition lv2_adapter.hpp:186
float beats_per_minute
Definition lv2_adapter.hpp:190
bool has_beats_per_bar
Definition lv2_adapter.hpp:180
float speed
0 = stopped, 1 = normal-rate playback
Definition lv2_adapter.hpp:184
int32_t beat_unit
time-signature denominator
Definition lv2_adapter.hpp:189
bool any() const noexcept
Definition lv2_adapter.hpp:192
bool has_frame
Definition lv2_adapter.hpp:177
URIDs the time:Position decode needs, resolved once in instantiate().
Definition lv2_adapter.hpp:149
LV2_URID time_beat_unit
Definition lv2_adapter.hpp:161
LV2_URID time_speed
Definition lv2_adapter.hpp:165
LV2_URID time_position
Definition lv2_adapter.hpp:158
LV2_URID time_frame
Definition lv2_adapter.hpp:164
LV2_URID time_beats_per_bar
Definition lv2_adapter.hpp:162
LV2_URID atom_blank
Definition lv2_adapter.hpp:151
LV2_URID atom_double
Definition lv2_adapter.hpp:153
LV2_URID time_beats_per_minute
Definition lv2_adapter.hpp:163
LV2_URID atom_long
Definition lv2_adapter.hpp:155
LV2_URID atom_bool
Definition lv2_adapter.hpp:156
LV2_URID atom_object
Definition lv2_adapter.hpp:150
LV2_URID atom_float
Definition lv2_adapter.hpp:152
LV2_URID time_bar
Definition lv2_adapter.hpp:159
LV2_URID atom_int
Definition lv2_adapter.hpp:154
LV2_URID time_beat
Definition lv2_adapter.hpp:160
Definition lv2_adapter.hpp:199
LV2_URID urid_midi_event
Definition lv2_adapter.hpp:258
std::vector< state::ParamID > param_ids
Definition lv2_adapter.hpp:250
float ** control_out_ports
Definition lv2_adapter.hpp:240
LV2_URID urid_atom_sequence
Definition lv2_adapter.hpp:259
float ** control_in_ports
Definition lv2_adapter.hpp:239
int num_params
Definition lv2_adapter.hpp:249
Lv2TimePosition transport
Definition lv2_adapter.hpp:271
int num_audio_outputs
Definition lv2_adapter.hpp:248
void * midi_out_atom
Definition lv2_adapter.hpp:291
float * audio_out_ports[kMaxChannels]
Definition lv2_adapter.hpp:238
LV2_URID urid_state_blob
Definition lv2_adapter.hpp:261
bool accepts_midi
Definition lv2_adapter.hpp:283
bool produces_midi
Definition lv2_adapter.hpp:290
detail::PlayheadSnapshot playhead_prev
Definition lv2_adapter.hpp:277
int num_main_audio_inputs
Definition lv2_adapter.hpp:247
std::unique_ptr< Processor > processor
Definition lv2_adapter.hpp:205
int num_audio_inputs
Definition lv2_adapter.hpp:242
state::ParameterEventQueue param_events
Definition lv2_adapter.hpp:211
int max_block_length
Definition lv2_adapter.hpp:231
void * midi_in_atom
Definition lv2_adapter.hpp:284
LV2_URID urid_atom_chunk
Definition lv2_adapter.hpp:260
bool has_transport
Definition lv2_adapter.hpp:272
midi::MidiBuffer midi_in
Definition lv2_adapter.hpp:220
Lv2TimeUrids time_urids
Definition lv2_adapter.hpp:262
state::StateStore store
Definition lv2_adapter.hpp:204
Lv2PortLayout port_layout() const noexcept
This instance's port ordering — the same one generate_plugin_ttl() numbered the manifest with.
Definition lv2_adapter.hpp:303
midi::MidiBuffer midi_out
Definition lv2_adapter.hpp:221
float * latency_port
Definition lv2_adapter.hpp:299
double sample_rate
Definition lv2_adapter.hpp:224
LV2_URID_Map * urid_map
Definition lv2_adapter.hpp:257
ProcessorFactory factory
Definition lv2_adapter.hpp:212
float * audio_in_ports[kMaxChannels]
Definition lv2_adapter.hpp:237