Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
lv2_entry.hpp
Go to the documentation of this file.
1#pragma once
2
3// Generic LV2 entry point generator
4// Plugin developers include this and call PULP_LV2_PLUGIN() with their factory function.
5// All LV2 boilerplate (descriptor, instantiate, run, cleanup) is generated automatically.
6//
7// Usage (in one .cpp file per plugin):
8// #include "my_processor.hpp"
9// #include <pulp/format/lv2_entry.hpp>
10// PULP_LV2_PLUGIN(my_namespace::create_my_processor, "http://pulp.audio/plugins/my_plugin")
11
17#include <pulp/runtime/log.hpp>
20
21#include <lv2/atom/atom.h>
22#include <lv2/atom/util.h>
23#include <lv2/buf-size/buf-size.h>
24#include <lv2/core/lv2.h>
25#include <lv2/midi/midi.h>
26#include <lv2/state/state.h>
27#include <lv2/time/time.h>
28#include <lv2/urid/urid.h>
29
30#include <algorithm>
31#include <array>
32#include <cstring>
33#include <fstream>
34#include <limits>
35#include <span>
36#include <vector>
37
39
40// Populated at static init by PULP_LV2_PLUGIN
41inline ProcessorFactory g_factory = nullptr;
42inline const char* g_uri = nullptr;
43
44// Worst-case MIDI events per block the RT MIDI buffers are sized to. Matches
45// the CLAP adapter's realtime MIDI capacity so behavior is uniform; add()
46// beyond this drops (set_realtime_capacity_limit) rather than allocating.
47inline constexpr std::size_t kRealtimeMidiEventCapacity =
49
50// Keep the advertised atom-port capacity ahead of what a full block of MIDI
51// actually needs, so raising kRealtimeMidiEventCapacity fails here rather than
52// silently truncating a host-allocated sequence at run time.
54 sizeof(LV2_Atom_Sequence) +
55 kRealtimeMidiEventCapacity * (sizeof(LV2_Atom_Event) + 8),
56 "lv2:minimumSize must cover a full block of outgoing MIDI");
57
58// ── time:Position decode ─────────────────────────────────────────────────
59
63inline bool read_numeric_atom(const LV2_Atom* atom, const lv2_adapter::Lv2TimeUrids& urids,
64 double& out) noexcept {
65 if (!atom)
66 return false;
67 const void* body = LV2_ATOM_BODY_CONST(atom);
68 if (urids.atom_float != 0 && atom->type == urids.atom_float && atom->size == sizeof(float)) {
69 float v = 0.0f;
70 std::memcpy(&v, body, sizeof(v));
71 out = static_cast<double>(v);
72 return true;
73 }
74 if (urids.atom_double != 0 && atom->type == urids.atom_double && atom->size == sizeof(double)) {
75 double v = 0.0;
76 std::memcpy(&v, body, sizeof(v));
77 out = v;
78 return true;
79 }
80 if (urids.atom_int != 0 && atom->type == urids.atom_int && atom->size == sizeof(int32_t)) {
81 int32_t v = 0;
82 std::memcpy(&v, body, sizeof(v));
83 out = static_cast<double>(v);
84 return true;
85 }
86 if (urids.atom_long != 0 && atom->type == urids.atom_long && atom->size == sizeof(int64_t)) {
87 int64_t v = 0;
88 std::memcpy(&v, body, sizeof(v));
89 out = static_cast<double>(v);
90 return true;
91 }
92 if (urids.atom_bool != 0 && atom->type == urids.atom_bool && atom->size == sizeof(int32_t)) {
93 int32_t v = 0;
94 std::memcpy(&v, body, sizeof(v));
95 out = v != 0 ? 1.0 : 0.0;
96 return true;
97 }
98 return false;
99}
100
106inline bool decode_time_position(const LV2_Atom_Object* obj, const lv2_adapter::Lv2TimeUrids& urids,
107 lv2_adapter::Lv2TimePosition& out) noexcept {
108 if (!obj || urids.time_position == 0)
109 return false;
110 if (obj->body.otype != urids.time_position)
111 return false;
112
113 LV2_ATOM_OBJECT_FOREACH(obj, prop) {
114 const LV2_Atom* value = &prop->value;
115 double numeric = 0.0;
116 if (!read_numeric_atom(value, urids, numeric))
117 continue;
118 const LV2_URID key = prop->key;
119 if (urids.time_speed != 0 && key == urids.time_speed) {
120 out.speed = static_cast<float>(numeric);
121 out.has_speed = true;
122 } else if (urids.time_frame != 0 && key == urids.time_frame) {
123 out.frame = static_cast<int64_t>(numeric);
124 out.has_frame = true;
125 } else if (urids.time_bar != 0 && key == urids.time_bar) {
126 out.bar = static_cast<int64_t>(numeric);
127 out.has_bar = true;
128 } else if (urids.time_beat != 0 && key == urids.time_beat) {
129 out.beat = numeric;
130 out.has_beat = true;
131 } else if (urids.time_beats_per_bar != 0 && key == urids.time_beats_per_bar) {
132 out.beats_per_bar = static_cast<float>(numeric);
133 out.has_beats_per_bar = true;
134 } else if (urids.time_beat_unit != 0 && key == urids.time_beat_unit) {
135 out.beat_unit = static_cast<int32_t>(numeric);
136 out.has_beat_unit = true;
137 } else if (urids.time_beats_per_minute != 0 && key == urids.time_beats_per_minute) {
138 out.beats_per_minute = static_cast<float>(numeric);
139 out.has_beats_per_minute = true;
140 }
141 }
142 return true;
143}
144
148inline void advance_time_position(lv2_adapter::Lv2TimePosition& pos, uint32_t n_samples,
149 double sample_rate) noexcept {
150 if (!pos.has_speed || pos.speed == 0.0f)
151 return;
152 if (n_samples == 0)
153 return;
154 const double frames = static_cast<double>(n_samples) * static_cast<double>(pos.speed);
155 if (pos.has_frame) {
156 pos.frame += static_cast<int64_t>(frames);
157 }
158 if (pos.has_beat && pos.has_beats_per_minute && sample_rate > 0.0 &&
159 pos.beats_per_minute > 0.0f) {
160 pos.beat += (frames / sample_rate) * (static_cast<double>(pos.beats_per_minute) / 60.0);
161 }
162}
163
173 boundary::HostTransport transport;
174 if (pos.has_speed) {
175 transport.is_playing = pos.speed != 0.0f;
177 }
178 if (pos.has_beats_per_minute) {
179 transport.tempo_bpm = static_cast<double>(pos.beats_per_minute);
181 }
182 if (pos.has_frame) {
183 transport.position_samples = pos.frame;
185 }
186 const bool time_sig =
187 pos.has_beats_per_bar && pos.has_beat_unit && pos.beats_per_bar > 0.0f && pos.beat_unit > 0;
188 if (time_sig) {
189 transport.time_sig_numerator = static_cast<int>(pos.beats_per_bar);
190 transport.time_sig_denominator = static_cast<int>(pos.beat_unit);
192 }
193 if (pos.has_beat) {
194 const double quarter_notes_per_beat = (pos.has_beat_unit && pos.beat_unit > 0)
195 ? 4.0 / static_cast<double>(pos.beat_unit)
196 : 1.0;
197 transport.position_beats = pos.beat * quarter_notes_per_beat;
199 }
200 if (pos.has_bar) {
201 transport.host_bar = pos.bar;
203 }
204 return transport;
205}
206
207// ── LV2 Callbacks ────────────────────────────────────────────────────────
208
209inline LV2_Handle instantiate(
210 const LV2_Descriptor*,
211 double sample_rate,
212 const char*,
213 const LV2_Feature* const* features)
214{
215 if (!g_factory) return nullptr;
216
217 // Resolve LV2_URID_Map, required by any real LV2 host. Fail instantiation
218 // loudly and return nullptr when the feature is absent so the host surfaces
219 // a clear error.
220 LV2_URID_Map* urid_map = lv2_adapter::find_urid_map(features);
221 if (!urid_map) {
223 "lv2: host did not provide LV2_URID__map; refusing to instantiate");
224 return nullptr;
225 }
226
227 auto* inst = new lv2_adapter::PulpLv2Instance();
228 inst->factory = g_factory;
229 inst->sample_rate = sample_rate;
230 inst->urid_map = urid_map;
231 inst->urid_midi_event = urid_map->map(urid_map->handle, LV2_MIDI__MidiEvent);
232 inst->urid_atom_sequence = urid_map->map(urid_map->handle, LV2_ATOM__Sequence);
233 inst->urid_atom_chunk = urid_map->map(urid_map->handle, LV2_ATOM__Chunk);
234 inst->urid_state_blob = urid_map->map(urid_map->handle, lv2_adapter::kStateBlobUri);
235
236 auto& time_urids = inst->time_urids;
237 time_urids.atom_object = urid_map->map(urid_map->handle, LV2_ATOM__Object);
238 time_urids.atom_blank = urid_map->map(urid_map->handle, LV2_ATOM__Blank);
239 time_urids.atom_float = urid_map->map(urid_map->handle, LV2_ATOM__Float);
240 time_urids.atom_double = urid_map->map(urid_map->handle, LV2_ATOM__Double);
241 time_urids.atom_int = urid_map->map(urid_map->handle, LV2_ATOM__Int);
242 time_urids.atom_long = urid_map->map(urid_map->handle, LV2_ATOM__Long);
243 time_urids.atom_bool = urid_map->map(urid_map->handle, LV2_ATOM__Bool);
244 time_urids.time_position = urid_map->map(urid_map->handle, LV2_TIME__Position);
245 time_urids.time_bar = urid_map->map(urid_map->handle, LV2_TIME__bar);
246 time_urids.time_beat = urid_map->map(urid_map->handle, LV2_TIME__beat);
247 time_urids.time_beat_unit = urid_map->map(urid_map->handle, LV2_TIME__beatUnit);
248 time_urids.time_beats_per_bar = urid_map->map(urid_map->handle, LV2_TIME__beatsPerBar);
249 time_urids.time_beats_per_minute = urid_map->map(urid_map->handle, LV2_TIME__beatsPerMinute);
250 time_urids.time_frame = urid_map->map(urid_map->handle, LV2_TIME__frame);
251 time_urids.time_speed = urid_map->map(urid_map->handle, LV2_TIME__speed);
252
253 // Block ceiling. LV2 has no argument for it; a host that supports
254 // bufsz:boundedBlockLength publishes bufsz:maxBlockLength through the
255 // options feature instead. Without it, fall back to the documented floor —
256 // Ardour and JACK allow blocks well past it, which is exactly the case
257 // run()'s clamp exists to survive.
258 inst->max_block_length = lv2_adapter::max_block_length_from_options(
260 urid_map->map(urid_map->handle, LV2_BUF_SIZE__maxBlockLength), time_urids.atom_int,
261 time_urids.atom_long, lv2_adapter::kDefaultMaxBlockLength);
262 inst->processor = g_factory();
263 if (!inst->processor) {
264 delete inst;
265 return nullptr;
266 }
267 inst->processor->set_state_store(&inst->store);
268
269 // Admission check: the audio port arrays on the instance are fixed at
270 // lv2_adapter::kMaxChannels, and every index the manifest declares is a
271 // port the host will connect. A descriptor whose buses sum past that
272 // ceiling in either direction therefore has no valid wiring at all, so it
273 // is refused here rather than dropped a port at a time in connect_port() —
274 // a dropped connection renders as silence, which reads as a DSP fault
275 // instead of a declaration fault. Same shape as the missing-urid:map
276 // refusal above: log and hand the host a null instance.
277 const auto admission_desc = inst->processor->descriptor();
278 int declared_inputs = 0;
279 for (const auto& bus : admission_desc.input_buses) {
280 declared_inputs += bus.default_channels;
281 }
282 int declared_outputs = 0;
283 for (const auto& bus : admission_desc.output_buses) {
284 declared_outputs += bus.default_channels;
285 }
286 if (declared_inputs < 0 || declared_inputs > lv2_adapter::kMaxChannels ||
287 declared_outputs < 0 || declared_outputs > lv2_adapter::kMaxChannels) {
289 "lv2: descriptor declares {} input and {} output channels; this adapter carries at "
290 "most {} per direction, refusing to instantiate",
291 declared_inputs, declared_outputs, lv2_adapter::kMaxChannels);
292 delete inst;
293 return nullptr;
294 }
295
296 // Define parameters
297 inst->processor->define_parameters(inst->store);
298 auto params = inst->store.all_params();
299 inst->num_params = static_cast<int>(params.size());
300 inst->param_ids.reserve(params.size());
301 for (const auto& p : params) {
302 inst->param_ids.push_back(p.id);
303 }
304
305 // Allocate control port pointer arrays
306 inst->control_in_ports = new float*[inst->num_params]();
307 inst->control_out_ports = new float*[inst->num_params]();
308
309 // Audio channel counts, already summed and bounds-checked above.
310 inst->num_audio_inputs = declared_inputs;
311 inst->num_main_audio_inputs = admission_desc.input_buses.empty()
312 ? 0
313 : std::max(0, admission_desc.input_buses[0].default_channels);
314 inst->num_audio_outputs = declared_outputs;
315 inst->accepts_midi = admission_desc.accepts_midi;
316 inst->produces_midi = admission_desc.produces_midi;
317
318 // Pre-reserve the RT MIDI buffers here (control thread) so run() never
319 // allocates on the audio thread. set_realtime_capacity_limit(true) makes
320 // add() drop-past-capacity instead of growing — mirrors the CLAP adapter.
321 inst->midi_in.reserve(kRealtimeMidiEventCapacity);
322 inst->midi_out.reserve(kRealtimeMidiEventCapacity);
323 inst->midi_in.set_realtime_capacity_limit(true);
324 inst->midi_out.set_realtime_capacity_limit(true);
325
326 // Prepare the processor
328 ctx.sample_rate = sample_rate;
329 ctx.max_buffer_size = inst->max_block_length;
330 // LV2 descriptors define the audio shape used by this instance. Preserve
331 // the historical defaults for descriptors without audio buses while
332 // passing concrete bus counts to processors that validate their layout.
333 if (inst->num_audio_inputs > 0)
334 ctx.input_channels = inst->num_audio_inputs;
335 if (inst->num_audio_outputs > 0)
336 ctx.output_channels = inst->num_audio_outputs;
337 inst->processor->prepare(ctx);
338
339 return static_cast<LV2_Handle>(inst);
340}
341
342inline void connect_port(LV2_Handle handle, uint32_t port, void* data) {
343 auto* inst = static_cast<lv2_adapter::PulpLv2Instance*>(handle);
344
345 // One ordering, shared with the manifest generator. Every slot index comes
346 // from the layout rather than from arithmetic repeated here, and the audio
347 // arrays are safe to index because instantiate() refuses a descriptor
348 // wider than lv2_adapter::kMaxChannels in either direction.
349 const lv2_adapter::Lv2PortLayout layout = inst->port_layout();
350
351 // LV2 port numbers are uint32_t. A value past INT_MAX is not a port this
352 // plugin declared; map it to a number the layout classifies as None rather
353 // than letting the narrowing produce a negative index.
354 const int idx = port <= static_cast<uint32_t>(std::numeric_limits<int>::max())
355 ? static_cast<int>(port)
356 : -1;
357
358 switch (layout.kind_of(idx)) {
360 inst->audio_in_ports[layout.slot_of(idx)] = static_cast<float*>(data);
361 break;
363 inst->audio_out_ports[layout.slot_of(idx)] = static_cast<float*>(data);
364 break;
366 inst->control_in_ports[layout.slot_of(idx)] = static_cast<float*>(data);
367 break;
369 // LV2 atom input port — host hands us an LV2_Atom_Sequence buffer.
370 inst->midi_in_atom = data;
371 break;
373 // LV2 atom output port — host pre-allocates an LV2_Atom_Sequence
374 // buffer sized by lv2:minimumSize in the TTL. run() writes
375 // outgoing MIDI events into it.
376 inst->midi_out_atom = data;
377 break;
379 // Latency output control port — host reads the value run() writes
380 // here for plugin delay compensation.
381 inst->latency_port = static_cast<float*>(data);
382 break;
384 break;
385 }
386}
387
388inline void activate(LV2_Handle) {
389 // Nothing needed — processor is prepared at instantiation
390}
391
392// Write a MidiBuffer into an LV2_Atom_Sequence output port. Uses the standard
393// lv2_atom_sequence_clear + append_event helpers. On entry the host's
394// out_seq->atom.size carries the buffer capacity; clear() resets it to
395// sizeof(body) and append_event() increments it per event. Events that don't
396// fit are dropped, not truncated, matching every other format adapter's
397// overflow behavior. Exposed non-static for unit testing; all arguments
398// validated so missing URIDs are a no-op.
400 LV2_Atom_Sequence* out_seq,
401 LV2_URID urid_atom_sequence,
402 LV2_URID urid_midi_event,
403 const midi::MidiBuffer& midi_out) {
404 if (!out_seq || urid_atom_sequence == 0 || urid_midi_event == 0) return;
405 const uint32_t capacity = out_seq->atom.size;
406 out_seq->atom.type = urid_atom_sequence;
407 out_seq->body.unit = 0; // frames
408 out_seq->body.pad = 0;
409 lv2_atom_sequence_clear(out_seq);
410
411 for (const auto& ev : midi_out) {
412 const uint32_t msg_size = ev.size();
413 if (msg_size == 0 || msg_size > 3) continue;
414 // Pack LV2_Atom_Event header + up to 3 MIDI bytes on the stack;
415 // append_event copies into out_seq.
416 struct alignas(8) {
417 LV2_Atom_Event hdr;
418 uint8_t payload[3];
419 } pkt{};
420 pkt.hdr.time.frames = ev.sample_offset;
421 pkt.hdr.body.type = urid_midi_event;
422 pkt.hdr.body.size = msg_size;
423 std::memcpy(pkt.payload, ev.data(), msg_size);
424 if (!lv2_atom_sequence_append_event(out_seq, capacity, &pkt.hdr)) {
425 break; // out of capacity — drop remaining events
426 }
427 }
428}
429
430inline void run(LV2_Handle handle, uint32_t n_samples) {
431 auto* inst = static_cast<lv2_adapter::PulpLv2Instance*>(handle);
432 if (!inst->processor) return;
433
434 // Honor the block ceiling prepare() sized the Processor for. A host that
435 // declares bufsz:boundedBlockLength never exceeds it; one that declares
436 // neither the feature nor the option can, and the shared contract is to
437 // render the prefix and hand back silence for the tail rather than overrun
438 // scratch. Tail zero-fill happens after the output pointers resolve.
439 const uint32_t requested_samples = n_samples;
440 n_samples = static_cast<uint32_t>(
441 clamp_block_to_prepared_max(static_cast<int>(n_samples), inst->max_block_length));
442
443 // Hardware flush-to-zero for the whole audio-thread render, matching every
444 // other adapter (clap_adapter.cpp, vst3_adapter.cpp, au_adapter.mm, ...).
445 // Protects recursive DSP feedback from denormal stalls under LV2.
447
448 // Report current processing latency to the host's latency control port
449 // (see generate_plugin_ttl's lv2:reportsLatency port) for PDC.
450 //
451 // Clamp to the port's declared domain. The TTL gives this port a minimum of
452 // zero, so a processor that returns a negative latency_samples() would
453 // otherwise write an out-of-range value straight into the host's delay
454 // compensation and shift the track the wrong way.
455 if (inst->latency_port) {
456 const int reported = inst->processor->latency_samples();
457 *inst->latency_port = static_cast<float>(reported > 0 ? reported : 0);
458 }
459
460 // Read control port values into the parameter store. LV2 run() is
461 // the audio thread, so use the RT-safe path — atomic store + SPSC
462 // push for Main listeners, no allocation. Editor pumps via
463 // store.pump_listeners() from its UI tick.
464 for (int i = 0; i < inst->num_params; ++i) {
465 if (inst->control_in_ports[i]) {
466 float value = *inst->control_in_ports[i];
467 inst->store.set_value_rt(inst->param_ids[i], value);
468 }
469 }
470
471 // Build buffer views. The views below carry the instance's channel counts
472 // over these fixed-size pointer arrays, which is only sound because
473 // instantiate() refused any descriptor wider than kMaxChannels.
474 const float* in_ptrs[lv2_adapter::kMaxChannels] = {};
475 float* out_ptrs[lv2_adapter::kMaxChannels] = {};
476
477 for (int i = 0; i < inst->num_audio_inputs && i < lv2_adapter::kMaxChannels; ++i) {
478 in_ptrs[i] = inst->audio_in_ports[i];
479 }
480 for (int i = 0; i < inst->num_audio_outputs && i < lv2_adapter::kMaxChannels; ++i) {
481 out_ptrs[i] = inst->audio_out_ports[i];
482 }
483
484 // Silence the region past the prepared maximum so an over-long block reads
485 // as clean silence instead of whatever the host buffer last held.
486 if (requested_samples > n_samples) {
487 for (int i = 0; i < inst->num_audio_outputs && i < lv2_adapter::kMaxChannels; ++i) {
488 if (!out_ptrs[i])
489 continue;
490 std::fill(out_ptrs[i] + n_samples, out_ptrs[i] + requested_samples, 0.0f);
491 }
492 }
493
494 // The connected input ports split into the main bus and, past the first
495 // bus's width, the side chain; both views read the flat port run in place.
496 // A port the host left unconnected (lv2:connectionOptional) is null, which
497 // makes that bus read as inactive rather than handing out a null pointer.
498 const int main_inputs = std::min(inst->num_main_audio_inputs, inst->num_audio_inputs);
499 const int sidechain_inputs = inst->num_audio_inputs - main_inputs;
500 const auto bus_connected = [&](int begin, int count) {
501 for (int i = begin; i < begin + count; ++i)
502 if (in_ptrs[i] == nullptr)
503 return false;
504 return count > 0;
505 };
506 const bool main_connected = bus_connected(0, main_inputs);
507 const bool sidechain_connected = bus_connected(main_inputs, sidechain_inputs);
509 in_ptrs, static_cast<size_t>(main_connected ? main_inputs : 0), n_samples);
511 in_ptrs + main_inputs, static_cast<size_t>(sidechain_connected ? sidechain_inputs : 0),
512 n_samples);
513 audio::BufferView<float> output(out_ptrs,
514 static_cast<size_t>(inst->num_audio_outputs), n_samples);
515 std::array<ProcessBusBufferView<const float>, 2> input_buses{{
516 {
517 .info = {"Main In", 0, BusDirection::Input, BusRole::Main, main_inputs,
518 main_inputs == 0, main_connected},
519 .buffer = input,
520 },
521 {
522 .info = {"Sidechain", 1, BusDirection::Input, BusRole::Sidechain, sidechain_inputs,
523 true, sidechain_connected},
524 .buffer = sidechain,
525 },
526 }};
527 const std::size_t input_bus_count = sidechain_inputs > 0 ? 2 : 1;
528 std::array<ProcessBusBufferView<float>, 1> output_buses{{
529 {
530 .info = {"Main Out", 0, BusDirection::Output, BusRole::Main,
531 inst->num_audio_outputs, false, inst->num_audio_outputs > 0},
532 .buffer = output,
533 },
534 }};
535 ProcessBuffers process_buffers{
537 std::span<ProcessBusBufferView<const float>>(input_buses.data(), input_bus_count)),
538 .outputs = ProcessBusBufferSet<float>(output_buses),
539 };
540
541 format::ProcessContext proc_ctx;
542 proc_ctx.sample_rate = inst->sample_rate;
543 proc_ctx.num_samples = static_cast<int>(n_samples);
546
547 // Uniform param-events contract. The queue carries no atom-sourced events
548 // yet; control-port params still flow via store.
549 inst->param_events.clear();
550 inst->processor->set_param_events(&inst->param_events);
551
552 // RT-safety guard spans the whole audio-thread render: MIDI parse, the
553 // process() call, and MIDI serialization. Because midi_in/midi_out are
554 // pre-reserved instance members (see instantiate()), the parse below reuses
555 // that storage instead of heap-allocating — the very allocation this guard
556 // exists to catch. Reset them here, inside the guarded region.
557 {
558 pulp::runtime::ScopedNoAlloc no_alloc_guard;
559
560 // MIDI: parse the connected LV2_Atom_Sequence (if any) and promote each
561 // MidiEvent into the Processor's MidiBuffer. Sysex atoms are currently
562 // ignored here; LV2 variable-length event support is not wired yet.
563 inst->midi_in.clear();
564 inst->midi_out.clear();
565 bool transport_received = false;
566 if (inst->midi_in_atom && inst->urid_atom_sequence && inst->urid_midi_event) {
567 const auto* seq = static_cast<const LV2_Atom_Sequence*>(inst->midi_in_atom);
568 if (seq->atom.type == inst->urid_atom_sequence) {
569 const auto& time_urids = inst->time_urids;
570 LV2_ATOM_SEQUENCE_FOREACH(seq, ev) {
571 if (ev->body.type == inst->urid_midi_event) {
572 const auto* data = reinterpret_cast<const uint8_t*>(ev + 1);
573 const uint32_t size = ev->body.size;
574 if (size >= 1 && size <= 3 && (data[0] & 0x80)) {
576 me.message =
577 choc::midi::ShortMessage(data[0], size > 1 ? data[1] : uint8_t{0},
578 size > 2 ? data[2] : uint8_t{0});
579 me.sample_offset = static_cast<int32_t>(ev->time.frames);
580 inst->midi_in.add(me);
581 }
582 continue;
583 }
584 // Transport. The host delivers time:Position on the same
585 // sequence as MIDI; a block can carry several (a seek mid
586 // block), and the last one wins for this block's context.
587 const bool is_object =
588 (time_urids.atom_object != 0 && ev->body.type == time_urids.atom_object) ||
589 (time_urids.atom_blank != 0 && ev->body.type == time_urids.atom_blank);
590 if (!is_object)
591 continue;
592 const auto* obj = reinterpret_cast<const LV2_Atom_Object*>(&ev->body);
593 if (decode_time_position(obj, time_urids, inst->transport)) {
594 inst->has_transport = true;
595 transport_received = true;
596 }
597 }
598 }
599 }
600
601 // Project the latched transport onto this block, then let the shared
602 // mapper derive the bar and the change flags. Extrapolate only when the
603 // host sent nothing new this block — a Position that just arrived is
604 // already positioned at the block start.
605 if (inst->has_transport) {
606 if (!transport_received) {
607 advance_time_position(inst->transport, n_samples, inst->sample_rate);
608 }
609 boundary::apply_host_transport(proc_ctx, to_host_transport(inst->transport),
610 inst->playhead_prev);
611 } else {
612 // No transport ever seen: still run the diff so the snapshot tracks
613 // block size and the validity mask stays honestly empty.
615 inst->playhead_prev);
616 }
617
618 inst->processor->process(process_buffers, inst->midi_in, inst->midi_out,
619 proc_ctx);
620
621 // Serialize outgoing MIDI back to the LV2 atom output port.
623 static_cast<LV2_Atom_Sequence*>(inst->midi_out_atom),
624 inst->urid_atom_sequence,
625 inst->urid_midi_event,
626 inst->midi_out);
627 }
628}
629
630inline void deactivate(LV2_Handle) {
631 // Nothing needed
632}
633
634// ── state:interface ──────────────────────────────────────────────────────
635//
636// One property, keyed by lv2_adapter::kStateBlobUri, holding the same
637// versioned envelope plugin_state_io::serialize() produces for every other
638// format: the StateStore payload plus whatever the Processor returns from
639// serialize_plugin_state(). Control-port values are the host's to save and
640// restore, so the envelope's parameter half is redundant under LV2 and is
641// simply overwritten by the port values on the next run(); the plugin-owned
642// half — sampler buffers, file references, blobs — has no other way home.
643
644inline LV2_State_Status save_state(LV2_Handle instance, LV2_State_Store_Function store,
645 LV2_State_Handle handle, uint32_t, const LV2_Feature* const*) {
646 auto* inst = static_cast<lv2_adapter::PulpLv2Instance*>(instance);
647 if (!inst || !inst->processor || !store)
648 return LV2_STATE_ERR_UNKNOWN;
649 if (inst->urid_state_blob == 0 || inst->urid_atom_chunk == 0) {
650 return LV2_STATE_ERR_NO_FEATURE;
651 }
652
653 const auto data = plugin_state_io::serialize(inst->store, *inst->processor);
654 // The spec requires size > 0 for a stored property. Nothing to save is a
655 // success, not an error — restore() falls back to defaults.
656 if (data.empty())
657 return LV2_STATE_SUCCESS;
658
659 return store(handle, inst->urid_state_blob, data.data(), data.size(), inst->urid_atom_chunk,
660 LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE);
661}
662
663inline LV2_State_Status restore_state(LV2_Handle instance, LV2_State_Retrieve_Function retrieve,
664 LV2_State_Handle handle, uint32_t,
665 const LV2_Feature* const*) {
666 auto* inst = static_cast<lv2_adapter::PulpLv2Instance*>(instance);
667 if (!inst || !inst->processor || !retrieve)
668 return LV2_STATE_ERR_UNKNOWN;
669 if (inst->urid_state_blob == 0)
670 return LV2_STATE_ERR_NO_FEATURE;
671
672 std::size_t size = 0;
673 uint32_t type = 0;
674 uint32_t flags = 0;
675 const void* value = retrieve(handle, inst->urid_state_blob, &size, &type, &flags);
676 // A host may legitimately restore an empty map to reset the plugin. The
677 // spec requires falling back to defaults rather than failing.
678 if (!value || size == 0)
679 return LV2_STATE_SUCCESS;
680 if (inst->urid_atom_chunk != 0 && type != 0 && type != inst->urid_atom_chunk) {
681 return LV2_STATE_ERR_BAD_TYPE;
682 }
683
684 const auto* bytes = static_cast<const uint8_t*>(value);
685 // restore() is in LV2's Instantiation threading class, so no run() call is
686 // in flight and Processor::deserialize_plugin_state() gets the
687 // non-concurrent context it documents.
688 const bool ok = plugin_state_io::deserialize(std::span<const uint8_t>(bytes, size), inst->store,
689 *inst->processor);
690 if (!ok)
691 return LV2_STATE_ERR_UNKNOWN;
692
693 // A restored state can name a different derived source than the live one
694 // (a different impulse response, a different sample set). Reconcile it here
695 // or a worker-less processor renders the old state for the rest of the
696 // session.
697 inst->processor->on_non_realtime_tick();
698 return LV2_STATE_SUCCESS;
699}
700
701inline const LV2_State_Interface g_state_interface = {save_state, restore_state};
702
703inline const void* extension_data(const char* uri) {
704 if (uri && std::strcmp(uri, LV2_STATE__interface) == 0) {
705 return &g_state_interface;
706 }
707 return nullptr;
708}
709
710inline void cleanup(LV2_Handle handle) {
711 auto* inst = static_cast<lv2_adapter::PulpLv2Instance*>(handle);
712 if (inst) {
713 inst->processor->release();
714 delete[] inst->control_in_ports;
715 delete[] inst->control_out_ports;
716 delete inst;
717 }
718}
719
720// ── Offline bundle description ──────────────────────────────────────────
721//
722// A host discovers a plugin by reading `manifest.ttl` from the bundle, so a
723// bundle that carries only the shared object is not a plugin: nothing finds it
724// and the failure is silent. The generators for both files already live in
725// `lv2_adapter.cpp`, and describing the plugin needs the plugin — the port
726// layout comes from its descriptor and the control ports from its parameters —
727// so the module describes ITSELF rather than a build script guessing.
728//
729// Exported so one driver can dlopen any Pulp LV2 module and ask it. Offline
730// only: called by the build, never by a host, never on the audio thread.
731inline int write_bundle_ttl(const char* bundle_dir, const char* binary_name) noexcept {
732 if (bundle_dir == nullptr || binary_name == nullptr || g_factory == nullptr || g_uri == nullptr)
733 return 1;
734
735 // The store is declared before the Processor so it is destroyed AFTER it,
736 // the same ordering PulpLv2Instance states and for the same reason:
737 // Processor::state() dereferences a pointer to this store, and a Processor
738 // may read it from its destructor or from a worker thread that destructor
739 // is about to join. Locals are destroyed in reverse declaration order, so
740 // swapping these two hands that thread a freed store.
741 state::StateStore store;
742
743 auto processor = g_factory();
744 if (!processor)
745 return 2;
746
747 // Same order instantiate() uses: the descriptor decides the port layout and
748 // define_parameters() decides the control ports, so both must run before
749 // either file is emitted or the manifest describes a different plugin from
750 // the one the host will load.
751 processor->set_state_store(&store);
752 processor->define_parameters(store);
753 const auto descriptor = processor->descriptor();
754
755 const std::string dir(bundle_dir);
756 const std::string binary(binary_name);
757 const auto stem = binary.substr(0, binary.rfind('.'));
758
759 std::ofstream manifest(dir + "/manifest.ttl", std::ios::binary | std::ios::trunc);
760 if (!manifest)
761 return 3;
762 manifest << lv2_adapter::generate_manifest_ttl(g_uri, binary);
763 if (!manifest.flush())
764 return 3;
765
766 // `generate_manifest_ttl` points rdfs:seeAlso at <stem>.ttl, so the
767 // description has to land on exactly that name or the host reads the
768 // manifest, follows the pointer, and finds nothing.
769 std::ofstream plugin(dir + "/" + stem + ".ttl", std::ios::binary | std::ios::trunc);
770 if (!plugin)
771 return 4;
772 plugin << lv2_adapter::generate_plugin_ttl(descriptor, store, g_uri);
773 if (!plugin.flush())
774 return 4;
775 return 0;
776}
777
778// The LV2 descriptor
779inline LV2_Descriptor g_lv2_descriptor = {nullptr, // URI — set at static init
782
783} // namespace pulp::format::lv2_generic
784
785// ── PULP_LV2_PLUGIN Macro ───────────────────────────────────────────────
786
787#define PULP_LV2_PLUGIN(factory_fn, plugin_uri) \
788 namespace { \
789 struct PulpLv2Init { \
790 PulpLv2Init() { \
791 pulp::format::lv2_generic::g_factory = factory_fn; \
792 pulp::format::lv2_generic::g_uri = plugin_uri; \
793 pulp::format::lv2_generic::g_lv2_descriptor.URI = plugin_uri; \
794 } \
795 } s_lv2_init; \
796 } \
797 \
798 extern "C" { \
799 LV2_SYMBOL_EXPORT \
800 const LV2_Descriptor* lv2_descriptor(uint32_t index) { \
801 return (index == 0) ? &pulp::format::lv2_generic::g_lv2_descriptor : nullptr; \
802 } \
803 LV2_SYMBOL_EXPORT \
804 int pulp_lv2_write_bundle_ttl(const char* bundle_dir, const char* binary) { \
805 return pulp::format::lv2_generic::write_bundle_ttl(bundle_dir, binary); \
806 } \
807 }
The shared adapter-boundary core.
Non-owning view over multi-channel audio sample data.
Definition buffer.hpp:30
Non-owning span of richer-surface process buses for one direction.
Definition process_block.hpp:259
constexpr void set(TransportField field, bool valid=true) noexcept
Definition transport_fields.hpp:37
Collection of timestamped MIDI events within a single audio buffer period.
Definition buffer.hpp:31
RAII guard that marks the current thread as "must not allocate".
Definition scoped_no_alloc.hpp:27
Definition scoped_flush_denormals.hpp:106
static constexpr std::size_t kCapacity
Definition parameter_event_queue.hpp:84
Centralized parameter storage with lock-free value access.
Definition store.hpp:164
The one max-block-overrun contract shared by every format adapter.
void apply_host_transport(ProcessContext &ctx, const HostTransport &transport, detail::PlayheadSnapshot &snapshot) noexcept
Write transport into ctx's transport fields, derive the bar when the host did not supply one,...
Definition adapter_boundary.hpp:163
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
@ 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)
Definition lv2_entry.hpp:38
const LV2_State_Interface g_state_interface
Definition lv2_entry.hpp:701
LV2_State_Status save_state(LV2_Handle instance, LV2_State_Store_Function store, LV2_State_Handle handle, uint32_t, const LV2_Feature *const *)
Definition lv2_entry.hpp:644
bool read_numeric_atom(const LV2_Atom *atom, const lv2_adapter::Lv2TimeUrids &urids, double &out) noexcept
Read a numeric atom body as a double, whatever integer or float type the host chose for it.
Definition lv2_entry.hpp:63
void deactivate(LV2_Handle)
Definition lv2_entry.hpp:630
void advance_time_position(lv2_adapter::Lv2TimePosition &pos, uint32_t n_samples, double sample_rate) noexcept
Advance a latched transport by one block of n_samples while it is rolling, so the position keeps movi...
Definition lv2_entry.hpp:148
LV2_Descriptor g_lv2_descriptor
Definition lv2_entry.hpp:779
const void * extension_data(const char *uri)
Definition lv2_entry.hpp:703
constexpr std::size_t kRealtimeMidiEventCapacity
Definition lv2_entry.hpp:47
ProcessorFactory g_factory
Definition lv2_entry.hpp:41
void write_midi_out_to_sequence(LV2_Atom_Sequence *out_seq, LV2_URID urid_atom_sequence, LV2_URID urid_midi_event, const midi::MidiBuffer &midi_out)
Definition lv2_entry.hpp:399
int write_bundle_ttl(const char *bundle_dir, const char *binary_name) noexcept
Definition lv2_entry.hpp:731
void run(LV2_Handle handle, uint32_t n_samples)
Definition lv2_entry.hpp:430
void activate(LV2_Handle)
Definition lv2_entry.hpp:388
LV2_Handle instantiate(const LV2_Descriptor *, double sample_rate, const char *, const LV2_Feature *const *features)
Definition lv2_entry.hpp:209
LV2_State_Status restore_state(LV2_Handle instance, LV2_State_Retrieve_Function retrieve, LV2_State_Handle handle, uint32_t, const LV2_Feature *const *)
Definition lv2_entry.hpp:663
void cleanup(LV2_Handle handle)
Definition lv2_entry.hpp:710
void connect_port(LV2_Handle handle, uint32_t port, void *data)
Definition lv2_entry.hpp:342
bool decode_time_position(const LV2_Atom_Object *obj, const lv2_adapter::Lv2TimeUrids &urids, lv2_adapter::Lv2TimePosition &out) noexcept
Decode a time:Position object into out, overwriting only the properties the host actually sent.
Definition lv2_entry.hpp:106
boundary::HostTransport to_host_transport(const lv2_adapter::Lv2TimePosition &pos) noexcept
Project a decoded time:Position onto the neutral transport every adapter hands the shared ProcessCont...
Definition lv2_entry.hpp:172
const char * g_uri
Definition lv2_entry.hpp:42
bool deserialize(std::span< const uint8_t > bytes, state::StateStore &store, Processor &processor)
Restore a host-facing plugin state blob.
std::vector< uint8_t > serialize(const state::StateStore &store, const Processor &processor)
Serialize the host-facing plugin state blob for a Processor.
@ Realtime
Definition process_context.hpp:42
std::unique_ptr< Processor >(*)() ProcessorFactory
Factory function type — plugins provide this to create processor instances.
Definition processor.hpp:1416
constexpr int clamp_block_to_prepared_max(int requested, int prepared_max) noexcept
Clamp a requested per-block frame count to the prepared maximum.
Definition max_block_contract.hpp:48
@ Output
Definition process_block.hpp:114
@ Input
Definition process_block.hpp:113
@ Realtime
Definition process_block.hpp:30
@ ok
Definition offline_sample_bounce.hpp:20
@ BeatPosition
Definition transport_fields.hpp:14
@ TimeSignature
Definition transport_fields.hpp:16
@ Playing
Definition transport_fields.hpp:10
@ Tempo
Definition transport_fields.hpp:13
@ Bar
Definition transport_fields.hpp:18
@ SamplePosition
Definition transport_fields.hpp:15
@ Main
Definition bus_role.hpp:10
@ Sidechain
Definition bus_role.hpp:12
ProcessBuffersT< float > ProcessBuffers
Definition process_block.hpp:422
@ plugin
Definition editor_host_kind.hpp:15
void log_warn(std::format_string< Args... > fmt, Args &&... args)
Definition log.hpp:190
Hardware flush-to-zero RAII guard for the audio callback boundary.
Definition prepare_resources.hpp:102
double sample_rate
Definition prepare_resources.hpp:103
int input_channels
Definition prepare_resources.hpp:105
int max_buffer_size
Definition prepare_resources.hpp:104
int output_channels
Definition prepare_resources.hpp:106
Non-owning view of one richer-surface process bus.
Definition process_block.hpp:231
Process context — passed every audio callback with transport state.
Definition process_context.hpp:89
int num_samples
Definition process_context.hpp:91
RenderSpeedHint render_speed_hint
Advisory render-speed category for hosts that know whether the current pass is live,...
Definition process_context.hpp:121
double sample_rate
Definition process_context.hpp:90
ProcessMode process_mode
Runtime mode for this block.
Definition process_context.hpp:96
Format-neutral transport snapshot, already decoded from a host playhead into canonical units (quarter...
Definition adapter_boundary.hpp:126
TransportValidity validity
Definition adapter_boundary.hpp:127
std::int64_t position_samples
Definition adapter_boundary.hpp:137
std::int64_t host_bar
A host that publishes a precomputed bar sets TransportField::Bar; otherwise the mapper derives it fro...
Definition adapter_boundary.hpp:147
double tempo_bpm
Definition adapter_boundary.hpp:133
double position_beats
quarter notes
Definition adapter_boundary.hpp:135
int time_sig_denominator
Definition adapter_boundary.hpp:140
int time_sig_numerator
Definition adapter_boundary.hpp:139
bool is_playing
Definition adapter_boundary.hpp:129
The single definition of a Pulp LV2 plugin's port ordering.
Definition lv2_adapter.hpp:79
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
constexpr Lv2PortKind kind_of(int port) const noexcept
Classify a port number.
Definition lv2_adapter.hpp:111
A time:Position object decoded into LV2's own units and conventions.
Definition lv2_adapter.hpp:175
URIDs the time:Position decode needs, resolved once in instantiate().
Definition lv2_adapter.hpp:149
Definition lv2_adapter.hpp:199
Timestamped MIDI event combining a choc::midi::ShortMessage with timing info.
Definition message.hpp:32
choc::midi::ShortMessage message
Definition message.hpp:33
int32_t sample_offset
Sample position within the current buffer block.
Definition message.hpp:34