Loading...
Searching...
No Matches
score-plugin-lv2/LV2/Node.hpp
1#pragma once
2#include <LV2/Context.hpp>
3#include <LV2/lv2_atom_helpers.hpp>
4
5#include <ossia/dataflow/fx_node.hpp>
6#include <ossia/dataflow/port.hpp>
7#include <ossia/detail/fmt.hpp>
8#include <ossia/detail/lockfree_queue.hpp>
9#include <ossia/detail/pod_vector.hpp>
10#include <ossia/detail/small_vector.hpp>
11
12#include <libremidi/detail/conversion.hpp>
13
14#include <QCoreApplication>
15
16#include <lilv/lilv.h>
17
18#include <algorithm>
19#include <memory>
20#include <optional>
21#include <type_traits>
22#include <vector>
23
24namespace LV2
25{
26
27// One LilvInstance + per-voice scratch / atom buffers. Pinned (TimePort holds pointers).
28struct voice
29{
30 SharedInstance instance_holder;
31 LilvInstance* instance{}; // cached: instance_holder->instance
32
33 ossia::float_vector fInControls;
34 ossia::float_vector fOutControls;
35 ossia::float_vector fOtherControls;
36 std::vector<ossia::float_vector> fCVs;
37 std::vector<ossia::float_vector> fCVOuts;
38
39 std::vector<ossia::float_vector> audio_in_scratch;
40 std::vector<ossia::float_vector> audio_out_scratch;
41
42 std::vector<AtomBuffer> midi_atom_ins;
43 std::vector<AtomBuffer> midi_atom_outs;
46 std::vector<libremidi::midi2_to_midi1_assembler<>> midi_2to1;
47 std::vector<libremidi::midi1_to_midi2> midi_1to2;
48 std::vector<ossia::small_vector<Message, 2>> message_for_midi_atom_ins;
49
50 std::vector<AtomBuffer> atom_ins;
51 std::vector<AtomBuffer> atom_outs;
52 std::vector<ossia::small_vector<Message, 2>> message_for_atom_ins;
54 std::vector<AtomBuffer> atom_others;
55
56 struct TimePort
57 {
58 int port;
59 AtomBuffer* buffer{};
60 };
61 std::vector<TimePort> atom_timePosition_midi;
62 std::vector<AtomBuffer> atom_timePosition_owned_buffers;
63 std::vector<TimePort> atom_timePosition_owned;
64
65 const LV2_Worker_Interface* worker{};
66 ossia::mpmc_queue<std::vector<char>> worker_datas;
67
70 uint32_t port_count{};
71
72 voice() = default;
73 voice(const voice&) = delete;
74 voice& operator=(const voice&) = delete;
75 voice(voice&&) = delete;
76 voice& operator=(voice&&) = delete;
77
81 ~voice() noexcept
82 {
83 if(!instance_holder || !instance || instance_holder->connected_to != this)
84 return;
85 if(instance_holder->activated)
86 {
87 lilv_instance_deactivate(instance);
88 instance_holder->activated = false;
89 }
90 park_ports();
91 }
92
100 void park_ports() noexcept
101 {
102 if(!instance_holder || !instance || instance_holder.use_count() < 2
103 || instance_holder->connected_to != this)
104 return;
105 alignas(64) static float parking[2 * 4096 + 64]{};
106 for(uint32_t p = 0; p < port_count; p++)
107 lilv_instance_connect_port(instance, p, parking);
108 instance_holder->connected_to = nullptr;
109 }
110};
111
112enum class voice_routing : std::uint8_t
113{
114 single,
115 // N voices, channel c -> voice c (mono plug-in on multichannel bus)
116 per_channel
117};
118
120{
121 voice_routing routing{voice_routing::single};
122 std::size_t voice_count{1};
123};
124
125// 1-in/1-out plug-ins get N replicated voices for multichannel host buses
126inline voice_strategy
127choose_voice_strategy(const LV2Data& data, std::size_t replicate_count = 8) noexcept
128{
129 const auto ains = data.audio_in_ports.size();
130 const auto aouts = data.audio_out_ports.size();
131 if(ains == 1 && aouts == 1 && replicate_count > 1)
132 return {voice_routing::per_channel, replicate_count};
133 return {voice_routing::single, 1};
134}
135
136template <typename OnExecStart, typename OnExecFinished>
137struct lv2_node final : public ossia::graph_node
138{
139 LV2Data data;
140 std::vector<std::unique_ptr<voice>> voices;
141 voice_routing routing{voice_routing::single};
142
143 ossia::float_vector fParamMin, fParamMax, fParamInit;
146 ossia::float_vector fInScale, fOutScale;
147 std::unique_ptr<uint8_t[]> timePositionBuffer{};
149 std::unique_ptr<uint8_t[]> stoppedPositionBuffer{};
153 bool m_sendStopped{true};
156 bool mustStop{};
159
160 // Matches LV2_BUF_SIZE__maxBlockLength advertised in Context.cpp
161 std::size_t max_block_size{4096};
162
163 // Audio-thread high-water mark; monotonic; main-thread grower reads via in_exec
164 std::atomic<std::size_t> requested_voices{0};
165
166 OnExecStart on_start;
167 OnExecFinished on_finished;
168
169 // Avoids RT-unfriendly realloc in append_voice for common channel counts
170 static constexpr std::size_t kVoicePoolReserve = 256;
171
172 lv2_node(
173 const LV2Data& dat, int sampleRate, voice_strategy strat, OnExecStart os,
174 OnExecFinished of)
175 : data{dat}
176 , routing{strat.routing}
177 , on_start{os}
178 , on_finished{of}
179 {
180 this->set_not_fp_safe();
181
182 if(strat.voice_count < 1)
183 strat.voice_count = 1;
184
185 const auto audio_in_size = data.audio_in_ports.size();
186 const auto audio_out_size = data.audio_out_ports.size();
187 const auto cv_size = data.cv_ports.size();
188 const auto midi_in_size = data.midi_in_ports.size();
189 const auto midi_out_size = data.midi_out_ports.size();
190 const auto atom_in_size = data.atom_in_ports.size();
191 const auto atom_out_size = data.atom_out_ports.size();
192 const auto control_in_size = data.control_in_ports.size();
193 const auto control_out_size = data.control_out_ports.size();
194
195 if(audio_in_size > 0)
196 m_inlets.push_back(new ossia::audio_inlet);
197 if(audio_out_size > 0)
198 m_outlets.push_back(new ossia::audio_outlet);
199 for(std::size_t i = 0; i < cv_size; i++)
200 m_inlets.push_back(new ossia::audio_inlet);
201 for(std::size_t i = 0; i < midi_in_size; i++)
202 m_inlets.push_back(new ossia::midi_inlet);
203 for(std::size_t i = 0; i < midi_out_size; i++)
204 m_outlets.push_back(new ossia::midi_outlet);
205 for(std::size_t i = 0; i < atom_in_size; i++)
206 m_inlets.push_back(new ossia::value_inlet);
207 for(std::size_t i = 0; i < atom_out_size; i++)
208 m_outlets.push_back(new ossia::value_outlet);
209 for(std::size_t i = 0; i < control_in_size; i++)
210 m_inlets.push_back(new ossia::value_inlet);
211 for(std::size_t i = 0; i < control_out_size; i++)
212 m_outlets.push_back(new ossia::value_outlet);
213 for(std::size_t i = 0; i < data.cv_out_ports.size(); i++)
214 m_outlets.push_back(new ossia::audio_outlet);
215
216 data.portRanges(fParamMin, fParamMax, fParamInit);
217 fInScale.resize(control_in_size);
218 for(std::size_t i = 0; i < control_in_size; i++)
219 fInScale[i] = data.isSampleRatePort(data.control_in_ports[i]) ? sampleRate : 1.f;
220 fOutScale.resize(control_out_size);
221 for(std::size_t i = 0; i < control_out_size; i++)
222 fOutScale[i] = data.isSampleRatePort(data.control_out_ports[i]) ? sampleRate : 1.f;
223
224 if(!data.effect.instance_holder)
225 throw std::runtime_error("Error while creating a LV2 plug-in");
226
227 voices.reserve(
228 routing == voice_routing::per_channel ? kVoicePoolReserve : strat.voice_count);
229
230 // A plug-in without activate() has no way to start over (x42's MIDI
231 // generator keeps playing from where it stopped): a play starts on a
232 // fresh instance, with the state of the previous one. Not while its UI is
233 // open, which talks to the current instance.
234 if(auto* desc = lilv_instance_get_descriptor(data.effect.instance);
235 desc && !desc->activate && !data.effect.ui_instance)
236 renew_primary_instance(sampleRate);
237
238 // Voice 0 reuses the Model's primary instance across play->stop->play
239 voices.push_back(build_voice(data.effect.instance_holder));
240
241 // Snapshot so extras inherit loaded samples/models; ports seeded separately below
242 LilvState* primary_state = (strat.voice_count > 1) ? snapshot_state_raw() : nullptr;
243
244 // lv2_node is constructed on the main thread; LV2EffectComponent grows past this
245 for(std::size_t i = 1; i < strat.voice_count; ++i)
246 {
247 auto handle = instantiate_handle(sampleRate);
248 if(!handle)
249 break;
250 if(primary_state)
251 lilv_state_restore(
252 primary_state, handle->instance, nullptr, nullptr,
253 LV2_STATE_IS_PORTABLE, nullptr);
254 voices.push_back(build_voice(std::move(handle)));
255 }
256
257 if(primary_state)
258 lilv_state_free(primary_state);
259
260 if(!data.time_Position_ports.empty())
261 {
262 timePositionBuffer = std::make_unique<uint8_t[]>(256);
263 stoppedPositionBuffer = std::make_unique<uint8_t[]>(256);
264 }
265
266 requested_voices.store(voices.size(), std::memory_order_relaxed);
267 }
268
269 ~lv2_node() noexcept override
270 {
271 // May run on audio thread; voice cleanup is main-thread-only per LV2 spec
272 if(voices.empty())
273 return;
274 QMetaObject::invokeMethod(
275 QCoreApplication::instance(),
276 [pending = std::move(voices)]() mutable { pending.clear(); });
277 }
278
279 // [main thread]
280 std::unique_ptr<voice> build_voice(SharedInstance handle)
281 {
282 auto v = std::make_unique<voice>();
283 v->instance_holder = std::move(handle);
284 v->instance = v->instance_holder->instance;
285 v->port_count = data.effect.plugin.get_num_ports();
286 allocate_voice_buffers(*v);
288 v->instance_holder->connected_to = v.get();
289 activate_voice(*v);
290 return v;
291 }
292
293 // [main thread]
294 SharedInstance instantiate_handle(int sampleRate)
295 {
296 auto* inst = lilv_plugin_instantiate(
297 data.effect.plugin.me, sampleRate,
298 data.host.global ? data.host.global->features() : data.host.features);
299 if(!inst)
300 return {};
301 return std::make_shared<InstanceHandle>(inst);
302 }
303
304 // [main thread] Replaces the Model's instance by a new one in the same state.
305 void renew_primary_instance(int sampleRate)
306 {
307 auto handle = instantiate_handle(sampleRate);
308 if(!handle)
309 return;
310 if(data.host.global)
311 {
312 if(LilvState* state = lilv_state_new_from_instance(
313 data.effect.plugin.me, data.effect.instance, &data.host.global->map,
314 nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
315 LV2_STATE_IS_PORTABLE, nullptr))
316 {
317 lilv_state_restore(
318 state, handle->instance, nullptr, nullptr, LV2_STATE_IS_PORTABLE, nullptr);
319 lilv_state_free(state);
320 }
321 }
322 data.effect.instance = handle->instance;
323 data.effect.instance_holder = std::move(handle);
324 }
325
326 // [main thread] null on failure; grower retries next tick
327 std::unique_ptr<voice>
328 make_voice_for_pool(int sampleRate, LilvState* state)
329 {
330 auto handle = instantiate_handle(sampleRate);
331 if(!handle)
332 return nullptr;
333
334 if(state)
335 lilv_state_restore(
336 state, handle->instance, nullptr, nullptr, LV2_STATE_IS_PORTABLE,
337 nullptr);
338
339 return build_voice(std::move(handle));
340 }
341
342 // [main thread] Caller frees with lilv_state_free
343 LilvState* snapshot_state_raw() const noexcept
344 {
345 if(voices.empty() || !voices[0]->instance || !data.host.global)
346 return nullptr;
347 return lilv_state_new_from_instance(
348 data.effect.plugin.me, voices[0]->instance, &data.host.global->map,
349 nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
350 LV2_STATE_IS_PORTABLE, nullptr);
351 }
352
353 // [audio thread] from main-thread grower via in_exec
354 void append_voice(std::unique_ptr<voice> v) noexcept
355 {
356 voices.push_back(std::move(v));
357 }
358
359 // [audio thread] Monotonic high-water mark
360 void request_voice_count(std::size_t n) noexcept
361 {
362 auto prev = requested_voices.load(std::memory_order_relaxed);
363 while(n > prev
364 && !requested_voices.compare_exchange_weak(
365 prev, n, std::memory_order_release, std::memory_order_relaxed))
366 ;
367 }
368
369 void allocate_voice_buffers(voice& v)
370 {
371 const auto control_in_size = data.control_in_ports.size();
372 const auto control_out_size = data.control_out_ports.size();
373 const auto cv_size = data.cv_ports.size();
374 const auto other_size = data.control_other_ports.size();
375 const auto midi_in_size = data.midi_in_ports.size();
376 const auto midi_out_size = data.midi_out_ports.size();
377 const auto atom_in_size = data.atom_in_ports.size();
378 const auto atom_out_size = data.atom_out_ports.size();
379 const auto audio_in_size = data.audio_in_ports.size();
380 const auto audio_out_size = data.audio_out_ports.size();
381
382 v.fInControls.resize(control_in_size);
383 v.fOutControls.resize(control_out_size);
384 v.fOtherControls.resize(other_size);
385
386 v.fCVs.resize(cv_size);
387 for(auto& cv : v.fCVs)
388 cv.resize(max_block_size);
389 v.fCVOuts.resize(data.cv_out_ports.size());
390 for(auto& cv : v.fCVOuts)
391 cv.resize(max_block_size);
392
393 v.audio_in_scratch.resize(audio_in_size);
394 for(auto& a : v.audio_in_scratch)
395 a.resize(max_block_size);
396
397 v.audio_out_scratch.resize(audio_out_size);
398 for(auto& a : v.audio_out_scratch)
399 a.resize(max_block_size);
400
401 v.midi_atom_ins.reserve(midi_in_size);
402 v.midi_2to1.resize(midi_in_size);
403 v.message_for_midi_atom_ins.resize(midi_in_size);
404 for(std::size_t i = 0; i < midi_in_size; i++)
405 v.midi_atom_ins.emplace_back(
406 data.atomBufferSize(data.midi_in_ports[i]), data.host.atom_chunk_id,
407 data.host.atom_sequence_id, true);
408
409 v.midi_atom_outs.reserve(midi_out_size);
410 v.midi_1to2.resize(midi_out_size);
411 for(std::size_t i = 0; i < midi_out_size; i++)
412 v.midi_atom_outs.emplace_back(
413 data.atomBufferSize(data.midi_out_ports[i]), data.host.atom_chunk_id,
414 data.host.atom_sequence_id, false);
415
416 v.atom_ins.reserve(atom_in_size);
417 v.message_for_atom_ins.resize(atom_in_size);
418 for(std::size_t i = 0; i < atom_in_size; i++)
419 v.atom_ins.emplace_back(
420 data.atomBufferSize(data.atom_in_ports[i]), data.host.atom_chunk_id,
421 data.host.atom_sequence_id, true);
422
423 v.atom_outs.reserve(atom_out_size);
424 for(std::size_t i = 0; i < atom_out_size; i++)
425 v.atom_outs.emplace_back(
426 data.atomBufferSize(data.atom_out_ports[i]), data.host.atom_chunk_id,
427 data.host.atom_sequence_id, false);
428
429 v.atom_others.reserve(data.atom_other_ports.size());
430 for(int port : data.atom_other_ports)
431 v.atom_others.emplace_back(
432 data.atomBufferSize(port), data.host.atom_chunk_id,
433 data.host.atom_sequence_id, true);
434
435 // time:Position may share a MIDI port (atom:supports midi+time) or have its own atom port
436 v.atom_timePosition_owned_buffers.reserve(data.time_Position_ports.size());
437 for(int port_index : data.time_Position_ports)
438 {
439 bool shared = false;
440 for(std::size_t k = 0; k < data.midi_in_ports.size(); ++k)
441 {
442 if(data.midi_in_ports[k] == port_index)
443 {
444 v.atom_timePosition_midi.push_back({port_index, &v.midi_atom_ins[k]});
445 shared = true;
446 break;
447 }
448 }
449 if(!shared)
450 {
451 v.atom_timePosition_owned_buffers.emplace_back(
452 256, data.host.atom_chunk_id, data.host.atom_sequence_id, true);
453 v.atom_timePosition_owned.push_back(
454 {port_index, &v.atom_timePosition_owned_buffers.back()});
455 }
456 }
457
458 if(lilv_plugin_has_feature(data.effect.plugin.me, data.host.work_schedule)
459 && lilv_plugin_has_extension_data(
460 data.effect.plugin.me, data.host.work_interface))
461 {
462 v.worker = static_cast<const LV2_Worker_Interface*>(
463 lilv_instance_get_extension_data(v.instance, LV2_WORKER__interface));
464 }
465
466 for(std::size_t i = 0; i < control_in_size; i++)
467 v.fInControls[i] = fParamInit[data.control_in_ports[i]] * fInScale[i];
468
469 (void)control_out_size;
470 (void)audio_in_size;
471 (void)audio_out_size;
472 }
473
477 void connect_voice_ports(voice& v) noexcept
478 {
479 auto* inst = v.instance;
480 for(std::size_t i = 0; i < data.audio_in_ports.size(); i++)
481 lilv_instance_connect_port(inst, data.audio_in_ports[i], v.audio_in_scratch[i].data());
482 for(std::size_t i = 0; i < data.audio_out_ports.size(); i++)
483 lilv_instance_connect_port(
484 inst, data.audio_out_ports[i], v.audio_out_scratch[i].data());
485 for(std::size_t i = 0; i < data.control_in_ports.size(); i++)
486 lilv_instance_connect_port(inst, data.control_in_ports[i], &v.fInControls[i]);
487 for(std::size_t i = 0; i < data.control_out_ports.size(); i++)
488 lilv_instance_connect_port(inst, data.control_out_ports[i], &v.fOutControls[i]);
489 for(std::size_t i = 0; i < data.cv_ports.size(); i++)
490 lilv_instance_connect_port(inst, data.cv_ports[i], v.fCVs[i].data());
491 for(std::size_t i = 0; i < data.cv_out_ports.size(); i++)
492 lilv_instance_connect_port(inst, data.cv_out_ports[i], v.fCVOuts[i].data());
493 for(std::size_t i = 0; i < data.control_other_ports.size(); i++)
494 lilv_instance_connect_port(
495 inst, data.control_other_ports[i], &v.fOtherControls[i]);
496 for(std::size_t i = 0; i < v.midi_atom_ins.size(); i++)
497 lilv_instance_connect_port(
498 inst, data.midi_in_ports[i], &v.midi_atom_ins[i].buf->atoms);
499 for(std::size_t i = 0; i < v.midi_atom_outs.size(); i++)
500 lilv_instance_connect_port(
501 inst, data.midi_out_ports[i], &v.midi_atom_outs[i].buf->atoms);
502 for(std::size_t i = 0; i < v.atom_ins.size(); i++)
503 lilv_instance_connect_port(
504 inst, data.atom_in_ports[i], &v.atom_ins[i].buf->atoms);
505 for(std::size_t i = 0; i < v.atom_outs.size(); i++)
506 lilv_instance_connect_port(
507 inst, data.atom_out_ports[i], &v.atom_outs[i].buf->atoms);
508 for(auto& [port, buf] : v.atom_timePosition_owned)
509 lilv_instance_connect_port(inst, port, &buf->buf->atoms);
510 for(std::size_t i = 0; i < v.atom_others.size(); i++)
511 lilv_instance_connect_port(
512 inst, data.atom_other_ports[i], &v.atom_others[i].buf->atoms);
513 }
514
515 void activate_voice(voice& v) noexcept
516 {
517 if(v.instance_holder && v.instance && !v.instance_holder->activated)
518 {
519 lilv_instance_activate(v.instance);
520 v.instance_holder->activated = true;
521 }
522 }
523
524 void all_notes_off() noexcept override
525 {
526 // CC#120 (All Sound Off) + CC#123 (All Notes Off) on every channel of every voice
527 if(data.midi_in_ports.empty())
528 return;
529 const int midi_port_index = data.midi_in_ports[0];
530 for(auto& v : voices)
531 {
532 if(v->message_for_midi_atom_ins.empty())
533 continue;
534 auto& msg_queue = v->message_for_midi_atom_ins[0];
535 for(uint8_t ch = 0; ch < 16; ++ch)
536 {
537 for(uint8_t cc : {uint8_t(120), uint8_t(123)})
538 {
539 Message m;
540 m.index = uint32_t(midi_port_index);
541 m.protocol = data.host.atom_eventTransfer;
542 m.body.resize(sizeof(LV2_Atom) + 3);
543 auto* atom = reinterpret_cast<LV2_Atom*>(m.body.data());
544 atom->type = data.host.midi_event_id;
545 atom->size = 3;
546 uint8_t* d = m.body.data() + sizeof(LV2_Atom);
547 d[0] = uint8_t(0xB0 | ch);
548 d[1] = cc;
549 d[2] = 0;
550 msg_queue.push_back(std::move(m));
551 }
552 }
553 }
554 }
555
556 [[nodiscard]] std::string label() const noexcept override
557 {
558 return fmt::format("lv2 ({})", data.effect.plugin.get_name().as_string());
559 }
560
561 void updateTime(const ossia::token_request& tk, ossia::exec_state_facade st)
562 {
563 forgeTime(timePositionBuffer.get(), tk, st, tk.speed);
564 forgeTime(stoppedPositionBuffer.get(), tk, st, 0.f);
565 m_timeForged = true;
566 }
567
568 void forgeTime(
569 uint8_t* buffer, const ossia::token_request& tk, ossia::exec_state_facade st,
570 float speed)
571 {
572 LV2::HostContext& host = data.host;
573 auto& forge = host.forge;
574 lv2_atom_forge_set_buffer(&forge, buffer, 256);
575 LV2_Atom_Forge_Frame frame;
576 lv2_atom_forge_object(&forge, &frame, 0, host.time_Position_id);
577
578 // The playhead at the first sample of this run. m_transport_frames is
579 // only updated once a node has run: it is that of the previous tick.
580 lv2_atom_forge_key(&forge, host.time_frame_id);
581 lv2_atom_forge_long(
582 &forge, tk.speed != 0. ? tk.start_date_to_physical(st.modelToSamples())
583 : this->m_transport_frames);
584
585 lv2_atom_forge_key(&forge, host.time_framesPerSecond_id);
586 lv2_atom_forge_long(&forge, st.sampleRate());
587
588 lv2_atom_forge_key(&forge, host.time_speed_id);
589 lv2_atom_forge_float(&forge, speed);
590
591 lv2_atom_forge_key(&forge, host.time_bar_id);
592 lv2_atom_forge_long(&forge, tk.musical_start_last_bar / 4.);
593
594 lv2_atom_forge_key(&forge, host.time_beat_id);
595 lv2_atom_forge_double(&forge, tk.musical_start_position);
596
597 auto barBeat = float(tk.musical_start_position - tk.musical_start_last_bar);
598 lv2_atom_forge_key(&forge, host.time_barBeat_id);
599 lv2_atom_forge_float(&forge, barBeat);
600
601 lv2_atom_forge_key(&forge, host.time_beatUnit_id);
602 lv2_atom_forge_int(&forge, 4);
603
604 lv2_atom_forge_key(&forge, host.time_beatsPerBar_id);
605 lv2_atom_forge_float(
606 &forge, 4 * double(tk.signature.upper) / double(tk.signature.lower));
607
608 lv2_atom_forge_key(&forge, host.time_beatsPerMinute_id);
609 lv2_atom_forge_float(&forge, tk.tempo);
610
611 lv2_atom_forge_pop(&forge, &frame);
612 }
613
614 // Vec slices by voice (clamped); scalars broadcast. Mirrors faust_mono_fx::set_control.
615 static std::optional<float>
616 voice_control_value(const ossia::value& val, std::size_t voice_idx) noexcept
617 {
618 return ossia::apply_nonnull(
619 [voice_idx](const auto& v) -> std::optional<float> {
620 using T = std::decay_t<decltype(v)>;
621 if constexpr(std::is_same_v<T, std::vector<ossia::value>>)
622 {
623 if(v.empty())
624 return std::nullopt;
625 const auto& elem = (voice_idx < v.size()) ? v[voice_idx] : v.back();
626 return ossia::convert<float>(elem);
627 }
628 else if constexpr(std::is_same_v<T, ossia::vec2f>)
629 return v[std::min<std::size_t>(voice_idx, 1)];
630 else if constexpr(std::is_same_v<T, ossia::vec3f>)
631 return v[std::min<std::size_t>(voice_idx, 2)];
632 else if constexpr(std::is_same_v<T, ossia::vec4f>)
633 return v[std::min<std::size_t>(voice_idx, 3)];
634 else if constexpr(
635 std::is_same_v<T, float> || std::is_same_v<T, int>
636 || std::is_same_v<T, bool>)
637 return float(v);
638 else
639 return std::nullopt;
640 },
641 val.v);
642 }
643
644 void preProcessVoice(voice& v, std::size_t voice_idx)
645 {
646 const std::size_t audio_in_size = data.audio_in_ports.size();
647 const std::size_t cv_size = data.cv_ports.size();
648 const std::size_t midi_in_size = data.midi_in_ports.size();
649 const std::size_t atom_in_size = data.atom_in_ports.size();
650 const std::size_t control_in_size = data.control_in_ports.size();
651
652 int first_midi_idx = (audio_in_size > 0 ? 1 : 0) + cv_size;
653 for(std::size_t i = 0; i < v.midi_atom_ins.size(); i++)
654 {
655 auto& ossia_port
656 = this->m_inlets[i + first_midi_idx]->template cast<ossia::midi_port>();
657 auto& lv2_port = v.midi_atom_ins[i];
658 Iterator it{lv2_port.buf};
659
660 // The position first: the sequence is in time order, and the events of
661 // this block are at this position.
662 if(ossia::any_of(v.atom_timePosition_midi, [&](const auto& t) {
663 return t.buffer == &lv2_port;
664 }))
665 writeTime(it);
666
667 for(const Message& msg : v.message_for_midi_atom_ins[i])
668 {
669 auto* atom = (LV2_Atom*)msg.body.data();
670 auto* atom_data = (const uint8_t*)LV2_ATOM_BODY(atom);
671 it.write(0, 0, atom->type, atom->size, atom_data);
672 }
673
674 // Port timestamps are relative to the buffer, the plug-in's to the tick:
675 // as the VST3 host does, what falls outside it goes to its nearest end.
676 auto& conv = v.midi_2to1[i];
677 for(const libremidi::ump& msg : ossia_port.messages)
678 {
679 const int64_t frame = std::clamp<int64_t>(
680 msg.timestamp - m_span.start, 0,
681 m_span.samples > 0 ? m_span.samples - 1 : 0);
682 conv.convert(
683 msg.data, msg.size(), frame,
684 [&](const unsigned char* midi1, std::size_t bytes, int64_t) {
685 if(bytes > 0)
686 it.write(frame, 0, data.host.midi_event_id, bytes, midi1);
687 return stdx::error{};
688 });
689 }
690 }
691
692 for(auto& [_, buf] : v.atom_timePosition_owned)
693 {
694 Iterator it{buf->buf};
695 writeTime(it);
696 }
697
698 const auto control_start
699 = (audio_in_size > 0 ? 1 : 0) + midi_in_size + atom_in_size + cv_size;
700 for(std::size_t i = 0; i < control_in_size; i++)
701 {
702 auto& in = m_inlets[control_start + i]
703 ->template cast<ossia::value_port>()
704 .get_data();
705 if(in.empty())
706 continue;
707 if(auto new_val = voice_control_value(in.back().value, voice_idx))
708 v.fInControls[i] = *new_val * fInScale[i];
709 }
710
711 (void)atom_in_size;
712 }
713
718 {
719 int64_t start{};
720 int64_t samples{};
721 int64_t size{};
722 } m_span;
723
724 template <typename Channel>
725 static void readChannel(const Channel& ch, float* dst, const buffer_span& s) noexcept
726 {
727 const int64_t n = std::clamp<int64_t>(int64_t(ch.size()) - s.start, 0, s.samples);
728 for(int64_t j = 0; j < n; j++)
729 dst[j] = float(ch[s.start + j]);
730 std::fill(dst + n, dst + s.samples, 0.f);
731 }
732
733 template <typename Channel>
734 static void writeChannel(Channel& ch, const float* src, const buffer_span& s)
735 {
736 if(int64_t(ch.size()) < s.size)
737 ch.resize(s.size);
738 std::copy_n(src, s.samples, ch.begin() + s.start);
739 }
740
743 void feedCVInputs(voice& v, std::size_t voice_idx) noexcept
744 {
745 const std::size_t first_cv = data.audio_in_ports.size() > 0 ? 1 : 0;
746 for(std::size_t i = 0; i < v.fCVs.size(); i++)
747 {
748 auto* buf = v.fCVs[i].data();
749 const auto& in = m_inlets[first_cv + i]->template cast<ossia::audio_port>();
750 if(const auto chans = in.channels(); chans > 0)
751 readChannel(in.channel(voice_idx < chans ? voice_idx : 0), buf, m_span);
752 else
753 std::fill_n(buf, m_span.samples, 0.f);
754 }
755 }
756
758 void writeCVOutputs(voice& v) noexcept
759 {
760 const std::size_t first_cv = (data.audio_out_ports.size() > 0 ? 1 : 0)
761 + data.midi_out_ports.size()
762 + data.atom_out_ports.size()
763 + data.control_out_ports.size();
764 for(std::size_t i = 0; i < v.fCVOuts.size(); i++)
765 {
766 auto& out = static_cast<ossia::audio_outlet*>(m_outlets[first_cv + i])->data;
767 out.set_channels(1);
768 writeChannel(out.channel(0), v.fCVOuts[i].data(), m_span);
769 }
770 }
771
774 void writeTime(Iterator& it) noexcept
775 {
776 auto write = [&](const uint8_t* buf) {
777 const LV2_Atom* atom = (const LV2_Atom*)buf;
778 it.write(0, 0, atom->type, atom->size, (const uint8_t*)LV2_ATOM_BODY(atom));
779 };
780 if(!timePositionBuffer || !m_timeForged)
781 return;
782 if(m_sendStopped || mustStop)
783 write(stoppedPositionBuffer.get());
784 if(!mustStop)
785 write(timePositionBuffer.get());
786 }
787
788 void postProcessVoice(voice& v) noexcept
789 {
790 if(v.worker && v.worker->work_response)
791 {
792 std::vector<char> vec;
793 while(v.worker_datas.try_dequeue(vec))
794 v.worker->work_response(v.instance->lv2_handle, vec.size(), vec.data());
795 }
796 if(v.worker && v.worker->end_run)
797 v.worker->end_run(v.instance->lv2_handle);
798
799 for(auto& port : v.midi_atom_ins)
800 port.buf->reset(true);
801 for(auto& port : v.atom_ins)
802 port.buf->reset(true);
803 for(auto& [_, buf] : v.atom_timePosition_owned)
804 buf->buf->reset(true);
805 for(auto& port : v.midi_atom_outs)
806 port.buf->reset(false);
807 for(auto& port : v.atom_outs)
808 port.buf->reset(false);
809 for(auto& port : v.atom_others)
810 port.buf->reset(true);
811 for(auto& mqueue : v.message_for_midi_atom_ins)
812 mqueue.clear();
813 }
814
815 // per_channel: inputs broadcast -> voice 0 outputs are canonical
816 void surfaceVoiceZeroOutputs(int64_t offset)
817 {
818 auto& v0 = *voices[0];
819 const auto audio_out_size = data.audio_out_ports.size();
820 const auto midi_out_size = data.midi_out_ports.size();
821 const auto control_out_size = data.control_out_ports.size();
822
823 int first_midi_idx = (audio_out_size > 0 ? 1 : 0);
824 for(std::size_t i = 0; i < v0.midi_atom_outs.size(); i++)
825 {
826 auto& ossia_port
827 = this->m_outlets[i + first_midi_idx]->template cast<ossia::midi_port>();
828 auto& lv2_port = v0.midi_atom_outs[i];
829 auto& conv = v0.midi_1to2[i];
830
831 lv2_port.buf->for_each_output_event([&](const LV2_Atom_Event& ev) {
832 if(ev.body.type == data.host.midi_event_id)
833 {
834 auto bytes = (const uint8_t*)LV2_ATOM_BODY_CONST(&ev.body);
835 conv.convert(
836 bytes, ev.body.size, ev.time.frames,
837 [&](const uint32_t* ump, int count, int64_t ts) {
838 libremidi::ump u;
839 std::copy_n(ump, std::min(count, 4), u.data);
840 u.timestamp = offset + ts;
841 ossia_port.messages.push_back(u);
842 return stdx::error{};
843 });
844 }
845 // Other events on a MIDI port are for the plug-in's UI (on_finish
846 // forwards them); FIXME also forward them (patch:Set...) to a value
847 // outlet.
848 });
849 }
850
851 const auto control_start
852 = (audio_out_size > 0 ? 1 : 0) + midi_out_size + data.atom_out_ports.size();
853 for(std::size_t i = 0; i < control_out_size; i++)
854 {
855 auto& out
856 = m_outlets[control_start + i]->template cast<ossia::value_port>();
857 out.write_value(v0.fOutControls[i] / fOutScale[i], offset);
858 }
859 }
860
863 uint32_t stop_run_samples() const noexcept
864 {
865 constexpr int64_t fallback = 64;
866 const int64_t n = m_span.samples > 0 ? m_span.samples : fallback;
867 return uint32_t(std::min<int64_t>(n, int64_t(max_block_size)));
868 }
869
872 void stop_voices() noexcept
873 {
874 data.host.current = &data.effect;
875 for(auto& vp : voices)
876 {
877 auto& v = *vp;
878 for(std::size_t i = 0; i < v.midi_atom_ins.size(); i++)
879 {
880 auto& lv2_port = v.midi_atom_ins[i];
881 Iterator it{lv2_port.buf};
882 if(ossia::any_of(v.atom_timePosition_midi, [&](const auto& t) {
883 return t.buffer == &lv2_port;
884 }))
885 writeTime(it);
886 for(const Message& msg : v.message_for_midi_atom_ins[i])
887 {
888 auto* atom = (LV2_Atom*)msg.body.data();
889 it.write(0, 0, atom->type, atom->size, (const uint8_t*)LV2_ATOM_BODY(atom));
890 }
891 }
892 for(auto& [_, buf] : v.atom_timePosition_owned)
893 {
894 Iterator it{buf->buf};
895 writeTime(it);
896 }
897
898 for(std::size_t i = 0; i < v.audio_in_scratch.size(); i++)
899 {
900 auto& scratch = v.audio_in_scratch[i];
901 std::fill(scratch.begin(), scratch.end(), 0.f);
902 lilv_instance_connect_port(v.instance, data.audio_in_ports[i], scratch.data());
903 }
904 for(std::size_t i = 0; i < v.audio_out_scratch.size(); i++)
905 lilv_instance_connect_port(
906 v.instance, data.audio_out_ports[i], v.audio_out_scratch[i].data());
907
908 // As long as the last tick, not a single frame: plug-ins dividing by
909 // (sample_count - 1) to ramp a parameter (SWH's Constant Signal
910 // Generator) turn their state to NaN on a run of one frame
911 {
912 worker_routing_scope ws{data.host, v};
913 lilv_instance_run(v.instance, stop_run_samples());
914 }
915 postProcessVoice(v);
916 }
917 }
918
919 void run(const ossia::token_request& tk, ossia::exec_state_facade st) noexcept override
920 {
921 // Before the paused check: the tick requested by stop() is empty.
922 if(mustStop)
923 {
924 stop_voices();
925 mustStop = false;
926 m_sendStopped = true;
927 return;
928 }
929 if(tk.paused())
930 return;
931
932 data.host.current = &data.effect;
933 if(!data.time_Position_ports.empty())
934 updateTime(tk, st);
935
936 const auto [tick_start, samples] = st.timings(tk);
937 const bool samples_valid
938 = samples > 0 && std::size_t(samples) <= max_block_size && tick_start >= 0;
939 m_span = {
940 std::max<int64_t>(tick_start, 0), samples_valid ? samples : 0,
941 std::max<int64_t>(st.bufferSize(), tick_start + samples)};
942
943 on_start();
944 for(std::size_t c = 0; c < voices.size(); ++c)
945 preProcessVoice(*voices[c], c);
946
947 if(samples_valid)
948 {
949 for(std::size_t c = 0; c < voices.size(); ++c)
950 feedCVInputs(*voices[c], c);
951 switch(routing)
952 {
953 case voice_routing::single:
954 runSingle(samples);
955 break;
956 case voice_routing::per_channel:
957 runPerChannel(samples);
958 break;
959 }
960 }
961
962 surfaceVoiceZeroOutputs(tk.physical_start(st.modelToSamples()));
963 on_finished();
964
965 for(auto& v : voices)
966 postProcessVoice(*v);
967 m_sendStopped = false;
968 }
969
971 {
972 LV2::HostContext& host;
974 : host{h}
975 {
976 host.current_worker_datas = &v.worker_datas;
977 }
978 ~worker_routing_scope() noexcept { host.current_worker_datas = nullptr; }
979 };
980
981 void runSingle(int samples) noexcept
982 {
983 auto& v = *voices[0];
984 const auto audio_ins = data.audio_in_ports.size();
985 const auto audio_outs = data.audio_out_ports.size();
986
987 if(audio_ins > 0)
988 {
989 const auto& audio_in = m_inlets[0]->template cast<ossia::audio_port>();
990 const auto in_channels = audio_in.channels();
991 for(std::size_t i = 0; i < audio_ins; i++)
992 {
993 auto& scratch = v.audio_in_scratch[i];
994 if(in_channels > i)
995 readChannel(audio_in.channel(i), scratch.data(), m_span);
996 else
997 std::fill_n(scratch.data(), samples, 0.0f);
998 lilv_instance_connect_port(
999 v.instance, data.audio_in_ports[i], scratch.data());
1000 }
1001 }
1002
1003 for(std::size_t i = 0; i < audio_outs; i++)
1004 lilv_instance_connect_port(
1005 v.instance, data.audio_out_ports[i], v.audio_out_scratch[i].data());
1006
1007 {
1008 worker_routing_scope ws{data.host, v};
1009 lilv_instance_run(v.instance, samples);
1010 }
1011 writeCVOutputs(v);
1012
1013 if(audio_outs > 0)
1014 {
1015 auto& out = static_cast<ossia::audio_outlet*>(m_outlets[0])->data;
1016 out.set_channels(audio_outs);
1017 for(std::size_t i = 0; i < audio_outs; i++)
1018 writeChannel(out.channel(i), v.audio_out_scratch[i].data(), m_span);
1019 }
1020 }
1021
1022 // voice c <-> channel c; MIDI/controls broadcast in preProcessVoice
1023 void runPerChannel(int samples) noexcept
1024 {
1025 if(voices.empty())
1026 return;
1027
1028 const auto* audio_in_inlet
1029 = m_inlets.empty() ? nullptr : m_inlets[0]->template target<ossia::audio_port>();
1030 const auto in_channels = audio_in_inlet ? audio_in_inlet->channels() : 0;
1031
1032 // Pure synths drive all voices; otherwise follow input channel count
1033 const auto requested
1034 = (audio_in_inlet ? std::max<std::size_t>(in_channels, 1) : voices.size());
1035 request_voice_count(requested);
1036
1037 const auto active = std::min<std::size_t>(voices.size(), requested);
1038
1039 auto& out = static_cast<ossia::audio_outlet*>(m_outlets[0])->data;
1040 out.set_channels(active);
1041
1042 for(std::size_t c = 0; c < active; ++c)
1043 {
1044 auto& v = *voices[c];
1045
1046 if(audio_in_inlet)
1047 {
1048 auto& in_scratch = v.audio_in_scratch[0];
1049 if(in_channels > c)
1050 readChannel(audio_in_inlet->channel(c), in_scratch.data(), m_span);
1051 else
1052 std::fill_n(in_scratch.data(), samples, 0.0f);
1053 lilv_instance_connect_port(
1054 v.instance, data.audio_in_ports[0], in_scratch.data());
1055 }
1056
1057 auto& out_scratch = v.audio_out_scratch[0];
1058 lilv_instance_connect_port(
1059 v.instance, data.audio_out_ports[0], out_scratch.data());
1060
1061 {
1062 worker_routing_scope ws{data.host, v};
1063 lilv_instance_run(v.instance, samples);
1064 }
1065
1066 writeChannel(out.channel(c), out_scratch.data(), m_span);
1067 if(c == 0)
1068 writeCVOutputs(v);
1069 }
1070 }
1071};
1072
1073} // namespace LV2
Definition lv2_atom_helpers.hpp:228
Definition lv2_atom_helpers.hpp:132
Definition Context.hpp:61
Definition Context.hpp:286
Definition Context.hpp:330
Definition score-plugin-lv2/LV2/Node.hpp:718
Definition score-plugin-lv2/LV2/Node.hpp:971
Definition score-plugin-lv2/LV2/Node.hpp:138
uint32_t stop_run_samples() const noexcept
Definition score-plugin-lv2/LV2/Node.hpp:863
void connect_voice_ports(voice &v) noexcept
Definition score-plugin-lv2/LV2/Node.hpp:477
void writeCVOutputs(voice &v) noexcept
The CV outputs of voice 0, after the control outlets.
Definition score-plugin-lv2/LV2/Node.hpp:758
void feedCVInputs(voice &v, std::size_t voice_idx) noexcept
Definition score-plugin-lv2/LV2/Node.hpp:743
void writeTime(Iterator &it) noexcept
Definition score-plugin-lv2/LV2/Node.hpp:774
bool m_timeForged
The position buffers hold a position: a run happened.
Definition score-plugin-lv2/LV2/Node.hpp:158
void stop_voices() noexcept
Definition score-plugin-lv2/LV2/Node.hpp:872
bool m_sendStopped
Definition score-plugin-lv2/LV2/Node.hpp:153
std::unique_ptr< uint8_t[]> stoppedPositionBuffer
The same position with a speed of 0: the transport is stopped.
Definition score-plugin-lv2/LV2/Node.hpp:149
bool mustStop
Definition score-plugin-lv2/LV2/Node.hpp:156
ossia::float_vector fInScale
Definition score-plugin-lv2/LV2/Node.hpp:146
Definition score-plugin-lv2/LV2/Node.hpp:57
Definition score-plugin-lv2/LV2/Node.hpp:120
Definition score-plugin-lv2/LV2/Node.hpp:29
uint32_t port_count
Definition score-plugin-lv2/LV2/Node.hpp:70
std::vector< AtomBuffer > atom_others
The atom ports with no direction: an empty sequence, every run.
Definition score-plugin-lv2/LV2/Node.hpp:54
void park_ports() noexcept
Definition score-plugin-lv2/LV2/Node.hpp:100
std::vector< libremidi::midi2_to_midi1_assembler<> > midi_2to1
Definition score-plugin-lv2/LV2/Node.hpp:46
~voice() noexcept
Definition score-plugin-lv2/LV2/Node.hpp:81