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
38 std::vector<ossia::float_vector> audio_in_scratch;
39 std::vector<ossia::float_vector> audio_out_scratch;
40
41 std::vector<AtomBuffer> midi_atom_ins;
42 std::vector<AtomBuffer> midi_atom_outs;
43 std::vector<libremidi::midi2_to_midi1> midi_2to1;
44 std::vector<libremidi::midi1_to_midi2> midi_1to2;
45 std::vector<ossia::small_vector<Message, 2>> message_for_midi_atom_ins;
46
47 std::vector<AtomBuffer> atom_ins;
48 std::vector<AtomBuffer> atom_outs;
49 std::vector<ossia::small_vector<Message, 2>> message_for_atom_ins;
50
51 struct TimePort
52 {
53 int port;
54 AtomBuffer* buffer{};
55 };
56 std::vector<TimePort> atom_timePosition_midi;
57 std::vector<AtomBuffer> atom_timePosition_owned_buffers;
58 std::vector<TimePort> atom_timePosition_owned;
59
60 const LV2_Worker_Interface* worker{};
61 ossia::mpmc_queue<std::vector<char>> worker_datas;
62
63 voice() = default;
64 voice(const voice&) = delete;
65 voice& operator=(const voice&) = delete;
66 voice(voice&&) = delete;
67 voice& operator=(voice&&) = delete;
68
69 // Voice 0's instance survives via Model's shared ref and is re-activated next play
70 ~voice() noexcept
71 {
72 if(instance_holder && instance && instance_holder->activated)
73 {
74 lilv_instance_deactivate(instance);
75 instance_holder->activated = false;
76 }
77 }
78};
79
80enum class voice_routing : std::uint8_t
81{
82 single,
83 // N voices, channel c -> voice c (mono plug-in on multichannel bus)
84 per_channel
85};
86
88{
89 voice_routing routing{voice_routing::single};
90 std::size_t voice_count{1};
91};
92
93// 1-in/1-out plug-ins get N replicated voices for multichannel host buses
94inline voice_strategy
95choose_voice_strategy(const LV2Data& data, std::size_t replicate_count = 8) noexcept
96{
97 const auto ains = data.audio_in_ports.size();
98 const auto aouts = data.audio_out_ports.size();
99 if(ains == 1 && aouts == 1 && replicate_count > 1)
100 return {voice_routing::per_channel, replicate_count};
101 return {voice_routing::single, 1};
102}
103
104template <typename OnExecStart, typename OnExecFinished>
105struct lv2_node final : public ossia::graph_node
106{
107 LV2Data data;
108 std::vector<std::unique_ptr<voice>> voices;
109 voice_routing routing{voice_routing::single};
110
111 ossia::float_vector fParamMin, fParamMax, fParamInit;
112 std::unique_ptr<uint8_t[]> timePositionBuffer{};
113
114 // Matches LV2_BUF_SIZE__maxBlockLength advertised in Context.cpp
115 std::size_t max_block_size{4096};
116
117 // Audio-thread high-water mark; monotonic; main-thread grower reads via in_exec
118 std::atomic<std::size_t> requested_voices{0};
119
120 OnExecStart on_start;
121 OnExecFinished on_finished;
122
123 // Avoids RT-unfriendly realloc in append_voice for common channel counts
124 static constexpr std::size_t kVoicePoolReserve = 256;
125
126 lv2_node(
127 const LV2Data& dat, int sampleRate, voice_strategy strat, OnExecStart os,
128 OnExecFinished of)
129 : data{dat}
130 , routing{strat.routing}
131 , on_start{os}
132 , on_finished{of}
133 {
134 this->set_not_fp_safe();
135
136 if(strat.voice_count < 1)
137 strat.voice_count = 1;
138
139 const auto audio_in_size = data.audio_in_ports.size();
140 const auto audio_out_size = data.audio_out_ports.size();
141 const auto cv_size = data.cv_ports.size();
142 const auto midi_in_size = data.midi_in_ports.size();
143 const auto midi_out_size = data.midi_out_ports.size();
144 const auto atom_in_size = data.atom_in_ports.size();
145 const auto atom_out_size = data.atom_out_ports.size();
146 const auto control_in_size = data.control_in_ports.size();
147 const auto control_out_size = data.control_out_ports.size();
148
149 if(audio_in_size > 0)
150 m_inlets.push_back(new ossia::audio_inlet);
151 if(audio_out_size > 0)
152 m_outlets.push_back(new ossia::audio_outlet);
153 for(std::size_t i = 0; i < cv_size; i++)
154 m_inlets.push_back(new ossia::audio_inlet);
155 for(std::size_t i = 0; i < midi_in_size; i++)
156 m_inlets.push_back(new ossia::midi_inlet);
157 for(std::size_t i = 0; i < midi_out_size; i++)
158 m_outlets.push_back(new ossia::midi_outlet);
159 for(std::size_t i = 0; i < atom_in_size; i++)
160 m_inlets.push_back(new ossia::value_inlet);
161 for(std::size_t i = 0; i < atom_out_size; i++)
162 m_outlets.push_back(new ossia::value_outlet);
163 for(std::size_t i = 0; i < control_in_size; i++)
164 m_inlets.push_back(new ossia::value_inlet);
165 for(std::size_t i = 0; i < control_out_size; i++)
166 m_outlets.push_back(new ossia::value_outlet);
167
168 const auto num_ports = data.effect.plugin.get_num_ports();
169 fParamMin.resize(num_ports);
170 fParamMax.resize(num_ports);
171 fParamInit.resize(num_ports);
172 data.effect.plugin.get_port_ranges_float(
173 fParamMin.data(), fParamMax.data(), fParamInit.data());
174
175 if(!data.effect.instance_holder)
176 throw std::runtime_error("Error while creating a LV2 plug-in");
177
178 voices.reserve(
179 routing == voice_routing::per_channel ? kVoicePoolReserve : strat.voice_count);
180
181 // Voice 0 reuses the Model's primary instance across play->stop->play
182 voices.push_back(build_voice(data.effect.instance_holder));
183
184 // Snapshot so extras inherit loaded samples/models; ports seeded separately below
185 LilvState* primary_state = (strat.voice_count > 1) ? snapshot_state_raw() : nullptr;
186
187 // lv2_node is constructed on the main thread; LV2EffectComponent grows past this
188 for(std::size_t i = 1; i < strat.voice_count; ++i)
189 {
190 auto handle = instantiate_handle(sampleRate);
191 if(!handle)
192 break;
193 if(primary_state)
194 lilv_state_restore(
195 primary_state, handle->instance, nullptr, nullptr,
196 LV2_STATE_IS_PORTABLE, nullptr);
197 voices.push_back(build_voice(std::move(handle)));
198 }
199
200 if(primary_state)
201 lilv_state_free(primary_state);
202
203 if(!data.time_Position_ports.empty())
204 timePositionBuffer = std::make_unique<uint8_t[]>(256);
205
206 requested_voices.store(voices.size(), std::memory_order_relaxed);
207 }
208
209 ~lv2_node() noexcept override
210 {
211 // May run on audio thread; voice cleanup is main-thread-only per LV2 spec
212 if(voices.empty())
213 return;
214 QMetaObject::invokeMethod(
215 QCoreApplication::instance(),
216 [pending = std::move(voices)]() mutable { pending.clear(); });
217 }
218
219 // [main thread]
220 std::unique_ptr<voice> build_voice(SharedInstance handle)
221 {
222 auto v = std::make_unique<voice>();
223 v->instance_holder = std::move(handle);
224 v->instance = v->instance_holder->instance;
225 allocate_voice_buffers(*v);
226 connect_voice_ports(*v);
227 activate_voice(*v);
228 return v;
229 }
230
231 // [main thread]
232 SharedInstance instantiate_handle(int sampleRate)
233 {
234 auto* inst = lilv_plugin_instantiate(
235 data.effect.plugin.me, sampleRate,
236 data.host.global ? data.host.global->features() : data.host.features);
237 if(!inst)
238 return {};
239 return std::make_shared<InstanceHandle>(inst);
240 }
241
242 // [main thread] null on failure; grower retries next tick
243 std::unique_ptr<voice>
244 make_voice_for_pool(int sampleRate, LilvState* state)
245 {
246 auto handle = instantiate_handle(sampleRate);
247 if(!handle)
248 return nullptr;
249
250 if(state)
251 lilv_state_restore(
252 state, handle->instance, nullptr, nullptr, LV2_STATE_IS_PORTABLE,
253 nullptr);
254
255 return build_voice(std::move(handle));
256 }
257
258 // [main thread] Caller frees with lilv_state_free
259 LilvState* snapshot_state_raw() const noexcept
260 {
261 if(voices.empty() || !voices[0]->instance || !data.host.global)
262 return nullptr;
263 return lilv_state_new_from_instance(
264 data.effect.plugin.me, voices[0]->instance, &data.host.global->map,
265 nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
266 LV2_STATE_IS_PORTABLE, nullptr);
267 }
268
269 // [audio thread] from main-thread grower via in_exec
270 void append_voice(std::unique_ptr<voice> v) noexcept
271 {
272 voices.push_back(std::move(v));
273 }
274
275 // [audio thread] Monotonic high-water mark
276 void request_voice_count(std::size_t n) noexcept
277 {
278 auto prev = requested_voices.load(std::memory_order_relaxed);
279 while(n > prev
280 && !requested_voices.compare_exchange_weak(
281 prev, n, std::memory_order_release, std::memory_order_relaxed))
282 ;
283 }
284
285 void allocate_voice_buffers(voice& v)
286 {
287 const auto control_in_size = data.control_in_ports.size();
288 const auto control_out_size = data.control_out_ports.size();
289 const auto cv_size = data.cv_ports.size();
290 const auto other_size = data.control_other_ports.size();
291 const auto midi_in_size = data.midi_in_ports.size();
292 const auto midi_out_size = data.midi_out_ports.size();
293 const auto atom_in_size = data.atom_in_ports.size();
294 const auto atom_out_size = data.atom_out_ports.size();
295 const auto audio_in_size = data.audio_in_ports.size();
296 const auto audio_out_size = data.audio_out_ports.size();
297
298 v.fInControls.resize(control_in_size);
299 v.fOutControls.resize(control_out_size);
300 v.fOtherControls.resize(other_size);
301
302 v.fCVs.resize(cv_size);
303 for(auto& cv : v.fCVs)
304 cv.resize(max_block_size);
305
306 v.audio_in_scratch.resize(audio_in_size);
307 for(auto& a : v.audio_in_scratch)
308 a.resize(max_block_size);
309
310 v.audio_out_scratch.resize(audio_out_size);
311 for(auto& a : v.audio_out_scratch)
312 a.resize(max_block_size);
313
314 v.midi_atom_ins.reserve(midi_in_size);
315 v.midi_2to1.resize(midi_in_size);
316 v.message_for_midi_atom_ins.resize(midi_in_size);
317 for(std::size_t i = 0; i < midi_in_size; i++)
318 v.midi_atom_ins.emplace_back(
319 2048, data.host.atom_chunk_id, data.host.midi_event_id, true);
320
321 v.midi_atom_outs.reserve(midi_out_size);
322 v.midi_1to2.resize(midi_out_size);
323 for(std::size_t i = 0; i < midi_out_size; i++)
324 v.midi_atom_outs.emplace_back(
325 2048, data.host.atom_chunk_id, data.host.midi_event_id, false);
326
327 v.atom_ins.reserve(atom_in_size);
328 v.message_for_atom_ins.resize(atom_in_size);
329 for(std::size_t i = 0; i < atom_in_size; i++)
330 v.atom_ins.emplace_back(
331 2048, data.host.atom_chunk_id, data.host.atom_object_id, true);
332
333 v.atom_outs.reserve(atom_out_size);
334 for(std::size_t i = 0; i < atom_out_size; i++)
335 v.atom_outs.emplace_back(
336 2048, data.host.atom_chunk_id, data.host.atom_object_id, false);
337
338 // time:Position may share a MIDI port (atom:supports midi+time) or have its own atom port
339 v.atom_timePosition_owned_buffers.reserve(data.time_Position_ports.size());
340 for(int port_index : data.time_Position_ports)
341 {
342 bool shared = false;
343 for(std::size_t k = 0; k < data.midi_in_ports.size(); ++k)
344 {
345 if(data.midi_in_ports[k] == port_index)
346 {
347 v.atom_timePosition_midi.push_back({port_index, &v.midi_atom_ins[k]});
348 shared = true;
349 break;
350 }
351 }
352 if(!shared)
353 {
354 v.atom_timePosition_owned_buffers.emplace_back(
355 256, data.host.atom_chunk_id, data.host.time_Position_id, true);
356 v.atom_timePosition_owned.push_back(
357 {port_index, &v.atom_timePosition_owned_buffers.back()});
358 }
359 }
360
361 if(lilv_plugin_has_feature(data.effect.plugin.me, data.host.work_schedule)
362 && lilv_plugin_has_extension_data(
363 data.effect.plugin.me, data.host.work_interface))
364 {
365 v.worker = static_cast<const LV2_Worker_Interface*>(
366 lilv_instance_get_extension_data(v.instance, LV2_WORKER__interface));
367 }
368
369 for(std::size_t i = 0; i < control_in_size; i++)
370 v.fInControls[i] = fParamInit[data.control_in_ports[i]];
371
372 (void)control_out_size;
373 (void)audio_in_size;
374 (void)audio_out_size;
375 }
376
377 void connect_voice_ports(voice& v) noexcept
378 {
379 auto* inst = v.instance;
380 for(std::size_t i = 0; i < data.control_in_ports.size(); i++)
381 lilv_instance_connect_port(inst, data.control_in_ports[i], &v.fInControls[i]);
382 for(std::size_t i = 0; i < data.control_out_ports.size(); i++)
383 lilv_instance_connect_port(inst, data.control_out_ports[i], &v.fOutControls[i]);
384 for(std::size_t i = 0; i < data.cv_ports.size(); i++)
385 lilv_instance_connect_port(inst, data.cv_ports[i], v.fCVs[i].data());
386 for(std::size_t i = 0; i < data.control_other_ports.size(); i++)
387 lilv_instance_connect_port(
388 inst, data.control_other_ports[i], &v.fOtherControls[i]);
389 for(std::size_t i = 0; i < v.midi_atom_ins.size(); i++)
390 lilv_instance_connect_port(
391 inst, data.midi_in_ports[i], &v.midi_atom_ins[i].buf->atoms);
392 for(std::size_t i = 0; i < v.midi_atom_outs.size(); i++)
393 lilv_instance_connect_port(
394 inst, data.midi_out_ports[i], &v.midi_atom_outs[i].buf->atoms);
395 for(std::size_t i = 0; i < v.atom_ins.size(); i++)
396 lilv_instance_connect_port(
397 inst, data.atom_in_ports[i], &v.atom_ins[i].buf->atoms);
398 for(std::size_t i = 0; i < v.atom_outs.size(); i++)
399 lilv_instance_connect_port(
400 inst, data.atom_out_ports[i], &v.atom_outs[i].buf->atoms);
401 for(auto& [port, buf] : v.atom_timePosition_owned)
402 lilv_instance_connect_port(inst, port, &buf->buf->atoms);
403 }
404
405 void activate_voice(voice& v) noexcept
406 {
407 if(v.instance_holder && v.instance && !v.instance_holder->activated)
408 {
409 lilv_instance_activate(v.instance);
410 v.instance_holder->activated = true;
411 }
412 }
413
414 void all_notes_off() noexcept override
415 {
416 // CC#120 (All Sound Off) + CC#123 (All Notes Off) on every channel of every voice
417 if(data.midi_in_ports.empty())
418 return;
419 const int midi_port_index = data.midi_in_ports[0];
420 for(auto& v : voices)
421 {
422 if(v->message_for_midi_atom_ins.empty())
423 continue;
424 auto& msg_queue = v->message_for_midi_atom_ins[0];
425 for(uint8_t ch = 0; ch < 16; ++ch)
426 {
427 for(uint8_t cc : {uint8_t(120), uint8_t(123)})
428 {
429 Message m;
430 m.index = uint32_t(midi_port_index);
431 m.protocol = data.host.atom_eventTransfer;
432 m.body.resize(sizeof(LV2_Atom) + 3);
433 auto* atom = reinterpret_cast<LV2_Atom*>(m.body.data());
434 atom->type = data.host.midi_event_id;
435 atom->size = 3;
436 uint8_t* d = m.body.data() + sizeof(LV2_Atom);
437 d[0] = uint8_t(0xB0 | ch);
438 d[1] = cc;
439 d[2] = 0;
440 msg_queue.push_back(std::move(m));
441 }
442 }
443 }
444 }
445
446 [[nodiscard]] std::string label() const noexcept override
447 {
448 return fmt::format("lv2 ({})", data.effect.plugin.get_name().as_string());
449 }
450
451 void updateTime(const ossia::token_request& tk, ossia::exec_state_facade st)
452 {
453 LV2::HostContext& host = data.host;
454 auto& forge = host.forge;
455 uint8_t* buffer = timePositionBuffer.get();
456 lv2_atom_forge_set_buffer(&forge, buffer, 256);
457 LV2_Atom_Forge_Frame frame;
458 lv2_atom_forge_object(&forge, &frame, 0, host.time_Position_id);
459
460 lv2_atom_forge_key(&forge, host.time_frame_id);
461 lv2_atom_forge_long(&forge, this->m_transport_frames);
462
463 lv2_atom_forge_key(&forge, host.time_framesPerSecond_id);
464 lv2_atom_forge_long(&forge, st.sampleRate());
465
466 lv2_atom_forge_key(&forge, host.time_speed_id);
467 lv2_atom_forge_float(&forge, tk.speed);
468
469 lv2_atom_forge_key(&forge, host.time_bar_id);
470 lv2_atom_forge_long(&forge, tk.musical_start_last_bar / 4.);
471
472 lv2_atom_forge_key(&forge, host.time_beat_id);
473 lv2_atom_forge_double(&forge, tk.musical_start_position);
474
475 auto barBeat = float(tk.musical_start_position - tk.musical_start_last_bar);
476 lv2_atom_forge_key(&forge, host.time_barBeat_id);
477 lv2_atom_forge_float(&forge, barBeat);
478
479 lv2_atom_forge_key(&forge, host.time_beatUnit_id);
480 lv2_atom_forge_int(&forge, 4);
481
482 lv2_atom_forge_key(&forge, host.time_beatsPerBar_id);
483 lv2_atom_forge_float(
484 &forge, 4 * double(tk.signature.upper) / double(tk.signature.lower));
485
486 lv2_atom_forge_key(&forge, host.time_beatsPerMinute_id);
487 lv2_atom_forge_float(&forge, tk.tempo);
488
489 lv2_atom_forge_pop(&forge, &frame);
490 }
491
492 // Vec slices by voice (clamped); scalars broadcast. Mirrors faust_mono_fx::set_control.
493 static std::optional<float>
494 voice_control_value(const ossia::value& val, std::size_t voice_idx) noexcept
495 {
496 return ossia::apply_nonnull(
497 [voice_idx](const auto& v) -> std::optional<float> {
498 using T = std::decay_t<decltype(v)>;
499 if constexpr(std::is_same_v<T, std::vector<ossia::value>>)
500 {
501 if(v.empty())
502 return std::nullopt;
503 const auto& elem = (voice_idx < v.size()) ? v[voice_idx] : v.back();
504 return ossia::convert<float>(elem);
505 }
506 else if constexpr(std::is_same_v<T, ossia::vec2f>)
507 return v[std::min<std::size_t>(voice_idx, 1)];
508 else if constexpr(std::is_same_v<T, ossia::vec3f>)
509 return v[std::min<std::size_t>(voice_idx, 2)];
510 else if constexpr(std::is_same_v<T, ossia::vec4f>)
511 return v[std::min<std::size_t>(voice_idx, 3)];
512 else if constexpr(
513 std::is_same_v<T, float> || std::is_same_v<T, int>
514 || std::is_same_v<T, bool>)
515 return float(v);
516 else
517 return std::nullopt;
518 },
519 val.v);
520 }
521
522 void preProcessVoice(voice& v, std::size_t voice_idx)
523 {
524 const std::size_t audio_in_size = data.audio_in_ports.size();
525 const std::size_t cv_size = data.cv_ports.size();
526 const std::size_t midi_in_size = data.midi_in_ports.size();
527 const std::size_t atom_in_size = data.atom_in_ports.size();
528 const std::size_t control_in_size = data.control_in_ports.size();
529
530 int first_midi_idx = (audio_in_size > 0 ? 1 : 0) + cv_size;
531 for(std::size_t i = 0; i < v.midi_atom_ins.size(); i++)
532 {
533 auto& ossia_port
534 = this->m_inlets[i + first_midi_idx]->template cast<ossia::midi_port>();
535 auto& lv2_port = v.midi_atom_ins[i];
536 Iterator it{lv2_port.buf};
537
538 for(const Message& msg : v.message_for_midi_atom_ins[i])
539 {
540 auto* atom = (LV2_Atom*)msg.body.data();
541 auto* atom_data = (const uint8_t*)LV2_ATOM_BODY(atom);
542 it.write(0, 0, atom->type, atom->size, atom_data);
543 }
544
545 auto& conv = v.midi_2to1[i];
546 for(const libremidi::ump& msg : ossia_port.messages)
547 {
548 conv.convert(
549 msg.data, msg.size(), msg.timestamp,
550 [&](unsigned char* midi1, int bytes, int64_t) {
551 if(bytes > 0)
552 it.write(msg.timestamp, 0, data.host.midi_event_id, bytes, midi1);
553 return stdx::error{};
554 });
555 }
556
557 if(!v.atom_timePosition_midi.empty())
558 {
559 const LV2_Atom* atom = (const LV2_Atom*)timePositionBuffer.get();
560 it.write(0, 0, atom->type, atom->size, (const uint8_t*)LV2_ATOM_BODY(atom));
561 }
562 }
563
564 for(auto& [_, buf] : v.atom_timePosition_owned)
565 {
566 Iterator it{buf->buf};
567 const LV2_Atom* atom = (const LV2_Atom*)timePositionBuffer.get();
568 it.write(0, 0, atom->type, atom->size, (const uint8_t*)LV2_ATOM_BODY(atom));
569 }
570
571 const auto control_start
572 = (audio_in_size > 0 ? 1 : 0) + midi_in_size + atom_in_size + cv_size;
573 for(std::size_t i = 0; i < control_in_size; i++)
574 {
575 auto& in = m_inlets[control_start + i]
576 ->template cast<ossia::value_port>()
577 .get_data();
578 if(in.empty())
579 continue;
580 if(auto new_val = voice_control_value(in.back().value, voice_idx))
581 v.fInControls[i] = *new_val;
582 }
583
584 (void)atom_in_size;
585 }
586
587 void postProcessVoice(voice& v) noexcept
588 {
589 if(v.worker && v.worker->work_response)
590 {
591 std::vector<char> vec;
592 while(v.worker_datas.try_dequeue(vec))
593 v.worker->work_response(v.instance->lv2_handle, vec.size(), vec.data());
594 }
595 if(v.worker && v.worker->end_run)
596 v.worker->end_run(v.instance->lv2_handle);
597
598 for(auto& port : v.midi_atom_ins)
599 port.buf->reset(true);
600 for(auto& port : v.atom_ins)
601 port.buf->reset(true);
602 for(auto& [_, buf] : v.atom_timePosition_owned)
603 buf->buf->reset(true);
604 for(auto& port : v.midi_atom_outs)
605 port.buf->reset(false);
606 for(auto& port : v.atom_outs)
607 port.buf->reset(false);
608 for(auto& mqueue : v.message_for_midi_atom_ins)
609 mqueue.clear();
610 }
611
612 // per_channel: inputs broadcast -> voice 0 outputs are canonical
613 void surfaceVoiceZeroOutputs(int64_t offset)
614 {
615 auto& v0 = *voices[0];
616 const auto audio_out_size = data.audio_out_ports.size();
617 const auto midi_out_size = data.midi_out_ports.size();
618 const auto control_out_size = data.control_out_ports.size();
619
620 int first_midi_idx = (audio_out_size > 0 ? 1 : 0);
621 for(std::size_t i = 0; i < v0.midi_atom_outs.size(); i++)
622 {
623 auto& ossia_port
624 = this->m_outlets[i + first_midi_idx]->template cast<ossia::midi_port>();
625 auto& lv2_port = v0.midi_atom_outs[i];
626 auto& conv = v0.midi_1to2[i];
627
628 LV2_ATOM_SEQUENCE_FOREACH(&lv2_port.buf->atoms, ev)
629 {
630 if(ev->body.type == data.host.midi_event_id)
631 {
632 auto bytes = (uint8_t*)LV2_ATOM_BODY_CONST(&ev->body);
633 conv.convert(
634 bytes, ev->body.size, ev->time.frames,
635 [&](const uint32_t* ump, int count, int64_t ts) {
636 libremidi::ump u;
637 std::copy_n(ump, std::min(count, 4), u.data);
638 u.timestamp = ts;
639 ossia_port.messages.push_back(u);
640 return stdx::error{};
641 });
642 }
643 else
644 {
645 // FIXME forward non-MIDI atom outputs (patch:Set etc.) to a value outlet
646 qDebug() << "Unhandled LV2 event type: " << ev->body.type;
647 }
648 }
649 }
650
651 const auto control_start = (audio_out_size > 0 ? 1 : 0) + midi_out_size;
652 for(std::size_t i = 0; i < control_out_size; i++)
653 {
654 auto& out
655 = m_outlets[control_start + i]->template cast<ossia::value_port>();
656 out.write_value(v0.fOutControls[i], offset);
657 }
658 }
659
660 void run(const ossia::token_request& tk, ossia::exec_state_facade st) noexcept override
661 {
662 if(tk.paused())
663 return;
664
665 data.host.current = &data.effect;
666 if(!data.time_Position_ports.empty())
667 updateTime(tk, st);
668
669 on_start();
670 for(std::size_t c = 0; c < voices.size(); ++c)
671 preProcessVoice(*voices[c], c);
672
673 const auto [tick_start, samples] = st.timings(tk);
674 const bool samples_valid
675 = samples > 0 && std::size_t(samples) <= max_block_size;
676
677 if(samples_valid)
678 {
679 switch(routing)
680 {
681 case voice_routing::single:
682 runSingle(samples);
683 break;
684 case voice_routing::per_channel:
685 runPerChannel(samples);
686 break;
687 }
688 }
689
690 surfaceVoiceZeroOutputs(tk.physical_start(st.modelToSamples()));
691 on_finished();
692
693 for(auto& v : voices)
694 postProcessVoice(*v);
695 }
696
698 {
699 LV2::HostContext& host;
701 : host{h}
702 {
703 host.current_worker_datas = &v.worker_datas;
704 }
705 ~worker_routing_scope() noexcept { host.current_worker_datas = nullptr; }
706 };
707
708 void runSingle(int samples) noexcept
709 {
710 auto& v = *voices[0];
711 const auto audio_ins = data.audio_in_ports.size();
712 const auto audio_outs = data.audio_out_ports.size();
713
714 if(audio_ins > 0)
715 {
716 const auto& audio_in = m_inlets[0]->template cast<ossia::audio_port>();
717 const auto in_channels = audio_in.channels();
718 for(std::size_t i = 0; i < audio_ins; i++)
719 {
720 auto& scratch = v.audio_in_scratch[i];
721 std::fill_n(scratch.data(), samples, 0.0f);
722 if(in_channels > i)
723 {
724 const auto& ch = audio_in.channel(i);
725 const auto n = std::min<std::size_t>(samples, ch.size());
726 for(std::size_t j = 0; j < n; j++)
727 scratch[j] = float(ch[j]);
728 }
729 lilv_instance_connect_port(
730 v.instance, data.audio_in_ports[i], scratch.data());
731 }
732 }
733
734 for(std::size_t i = 0; i < audio_outs; i++)
735 lilv_instance_connect_port(
736 v.instance, data.audio_out_ports[i], v.audio_out_scratch[i].data());
737
738 {
739 worker_routing_scope ws{data.host, v};
740 lilv_instance_run(v.instance, samples);
741 }
742
743 if(audio_outs > 0)
744 {
745 auto& out = static_cast<ossia::audio_outlet*>(m_outlets[0])->data;
746 out.set_channels(audio_outs);
747 for(std::size_t i = 0; i < audio_outs; i++)
748 {
749 auto& ch = out.channel(i);
750 ch.assign(
751 v.audio_out_scratch[i].begin(),
752 v.audio_out_scratch[i].begin() + samples);
753 }
754 }
755 }
756
757 // voice c <-> channel c; MIDI/controls broadcast in preProcessVoice
758 void runPerChannel(int samples) noexcept
759 {
760 if(voices.empty())
761 return;
762
763 const auto* audio_in_inlet
764 = m_inlets.empty() ? nullptr : m_inlets[0]->template target<ossia::audio_port>();
765 const auto in_channels = audio_in_inlet ? audio_in_inlet->channels() : 0;
766
767 // Pure synths drive all voices; otherwise follow input channel count
768 const auto requested
769 = (audio_in_inlet ? std::max<std::size_t>(in_channels, 1) : voices.size());
770 request_voice_count(requested);
771
772 const auto active = std::min<std::size_t>(voices.size(), requested);
773
774 auto& out = static_cast<ossia::audio_outlet*>(m_outlets[0])->data;
775 out.set_channels(active);
776
777 for(std::size_t c = 0; c < active; ++c)
778 {
779 auto& v = *voices[c];
780
781 if(audio_in_inlet)
782 {
783 auto& in_scratch = v.audio_in_scratch[0];
784 std::fill_n(in_scratch.data(), samples, 0.0f);
785 if(in_channels > c)
786 {
787 const auto& src = audio_in_inlet->channel(c);
788 const auto n = std::min<std::size_t>(samples, src.size());
789 for(std::size_t j = 0; j < n; j++)
790 in_scratch[j] = float(src[j]);
791 }
792 lilv_instance_connect_port(
793 v.instance, data.audio_in_ports[0], in_scratch.data());
794 }
795
796 auto& out_scratch = v.audio_out_scratch[0];
797 lilv_instance_connect_port(
798 v.instance, data.audio_out_ports[0], out_scratch.data());
799
800 {
801 worker_routing_scope ws{data.host, v};
802 lilv_instance_run(v.instance, samples);
803 }
804
805 auto& dst = out.channel(c);
806 dst.assign(out_scratch.begin(), out_scratch.begin() + samples);
807 }
808 }
809};
810
811} // namespace LV2
Definition lv2_atom_helpers.hpp:197
Definition lv2_atom_helpers.hpp:101
Definition Context.hpp:60
Definition Context.hpp:265
Definition Context.hpp:283
Definition score-plugin-lv2/LV2/Node.hpp:698
Definition score-plugin-lv2/LV2/Node.hpp:106
Definition score-plugin-lv2/LV2/EffectModel.cpp:998
Definition score-plugin-lv2/LV2/Node.hpp:52
Definition score-plugin-lv2/LV2/Node.hpp:88
Definition score-plugin-lv2/LV2/Node.hpp:29