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/pod_vector.hpp>
9
10#include <libremidi/detail/conversion.hpp>
11
12namespace LV2
13{
14template <typename OnExecStart, typename OnExecFinished>
15struct lv2_node final : public ossia::graph_node
16{
17 LV2Data data;
18 ossia::float_vector fInControls, fOutControls, fParamMin, fParamMax, fParamInit,
19 fOtherControls;
20 std::vector<ossia::float_vector> fCVs;
21 std::vector<AtomBuffer> m_midi_atom_ins, m_midi_atom_outs;
22 std::vector<libremidi::midi2_to_midi1> m_midi_2to1;
23 std::vector<libremidi::midi1_to_midi2> m_midi_1to2;
24 std::vector<ossia::small_vector<Message, 2>> m_message_for_midi_atom_ins;
25
26 std::vector<AtomBuffer> m_atom_ins, m_atom_outs;
27 std::vector<ossia::small_vector<Message, 2>> m_message_for_atom_ins;
28
29 LilvInstance* fInstance{};
30 std::unique_ptr<uint8_t[]> timePositionBuffer{};
32 {
33 int port;
34 AtomBuffer* buffer{};
35 };
36 std::vector<MatchedPort> m_atom_timePosition_midi;
37 std::vector<MatchedPort> m_atom_timePosition_owned;
38
39 OnExecStart on_start;
40 OnExecFinished on_finished;
41 lv2_node(LV2Data dat, int sampleRate, OnExecStart os, OnExecFinished of)
42 : data{dat}
43 , on_start{os}
44 , on_finished{of}
45 {
46 this->set_not_fp_safe();
47
48 const std::size_t audio_in_size = data.audio_in_ports.size();
49 const std::size_t audio_out_size = data.audio_out_ports.size();
50 const std::size_t control_in_size = data.control_in_ports.size();
51 const std::size_t control_out_size = data.control_out_ports.size();
52 const std::size_t midi_in_size = data.midi_in_ports.size();
53 const std::size_t midi_out_size = data.midi_out_ports.size();
54 const std::size_t atom_in_size = data.atom_in_ports.size();
55 const std::size_t atom_out_size = data.atom_out_ports.size();
56 const std::size_t time_in_size = data.time_Position_ports.size();
57 const std::size_t cv_size = data.cv_ports.size();
58 const std::size_t other_size = data.control_other_ports.size();
59 const std::size_t num_ports = data.effect.plugin.get_num_ports();
60
61 if(audio_in_size > 0)
62 {
63 m_inlets.push_back(new ossia::audio_inlet);
64 }
65 if(audio_out_size > 0)
66 {
67 m_outlets.push_back(new ossia::audio_outlet);
68 }
69
70 for(std::size_t i = 0; i < cv_size; i++)
71 {
72 m_inlets.push_back(new ossia::audio_inlet);
73 }
74
75 for(std::size_t i = 0; i < midi_in_size; i++)
76 {
77 m_inlets.push_back(new ossia::midi_inlet);
78 }
79 for(std::size_t i = 0; i < midi_out_size; i++)
80 {
81 m_outlets.push_back(new ossia::midi_outlet);
82 }
83
84 for(std::size_t i = 0; i < atom_in_size; i++)
85 {
86 m_inlets.push_back(new ossia::value_inlet);
87 }
88 for(std::size_t i = 0; i < atom_out_size; i++)
89 {
90 m_outlets.push_back(new ossia::value_outlet);
91 }
92
93 for(std::size_t i = 0; i < control_in_size; i++)
94 {
95 m_inlets.push_back(new ossia::value_inlet);
96 }
97 for(std::size_t i = 0; i < control_out_size; i++)
98 {
99 m_outlets.push_back(new ossia::value_outlet);
100 }
101
102 fInControls.resize(control_in_size);
103 fOutControls.resize(control_out_size);
104 fOtherControls.resize(other_size);
105 fCVs.resize(cv_size);
106 for(std::size_t i = 0; i < cv_size; i++)
107 {
108 fCVs[i].resize(4096);
109 }
110
111 fParamMin.resize(num_ports);
112 fParamMax.resize(num_ports);
113 fParamInit.resize(num_ports);
114
115 data.effect.plugin.get_port_ranges_float(
116 fParamMin.data(), fParamMax.data(), fParamInit.data());
117
118 fInstance = data.effect.instance;
119 data.effect.instance = fInstance;
120
121 if(!fInstance)
122 throw std::runtime_error("Error while creating a LV2 plug-in");
123
124 // MIDI
125 m_midi_atom_ins.reserve(midi_in_size);
126 m_midi_2to1.resize(midi_in_size);
127 m_message_for_midi_atom_ins.resize(midi_in_size);
128 for(std::size_t i = 0; i < midi_in_size; i++)
129 {
130 m_midi_atom_ins.emplace_back(
131 2048, data.host.atom_chunk_id, data.host.midi_event_id, true);
132 }
133
134 m_midi_atom_outs.reserve(midi_out_size);
135 m_midi_1to2.resize(midi_out_size);
136 for(std::size_t i = 0; i < midi_out_size; i++)
137 {
138 m_midi_atom_outs.emplace_back(
139 2048, data.host.atom_chunk_id, data.host.midi_event_id, false);
140 }
141
142 // Atom
143 m_atom_ins.reserve(atom_in_size);
144 m_message_for_atom_ins.resize(atom_in_size);
145 for(std::size_t i = 0; i < atom_in_size; i++)
146 {
147 m_atom_ins.emplace_back(
148 2048, data.host.atom_chunk_id, data.host.atom_object_id, true); // FIXME
149 }
150
151 m_atom_outs.reserve(atom_out_size);
152 for(std::size_t i = 0; i < atom_out_size; i++)
153 {
154 m_atom_outs.emplace_back(
155 2048, data.host.atom_chunk_id, data.host.atom_object_id, false);
156 }
157
158 // Timing
159 // Note: some plug-ins have the timing port shared with the midi port, others have it separate
160 for(std::size_t i = 0; i < time_in_size; i++)
161 {
162 auto port_index = data.time_Position_ports[i];
163
164 bool is_midi = false;
165 for(std::size_t midi_port_k = 0; midi_port_k < data.midi_in_ports.size();
166 midi_port_k++)
167 {
168 int midi_port_index = data.midi_in_ports[midi_port_k];
169 if(midi_port_index == port_index)
170 {
171 m_atom_timePosition_midi.push_back(
172 MatchedPort{port_index, &m_midi_atom_ins[midi_port_k]});
173 is_midi = true;
174 break;
175 }
176 }
177
178 if(!is_midi)
179 {
180 // Allocate a new port
181 auto abuf = new AtomBuffer(
182 256, data.host.atom_chunk_id, data.host.time_Position_id, true);
183 m_atom_timePosition_owned.push_back(MatchedPort{port_index, abuf});
184 }
185 }
186
187 // Worker
188 if(lilv_plugin_has_feature(data.effect.plugin.me, data.host.work_schedule)
189 && lilv_plugin_has_extension_data(
190 data.effect.plugin.me, data.host.work_interface))
191 {
192 data.effect.worker = static_cast<const LV2_Worker_Interface*>(
193 lilv_instance_get_extension_data(fInstance, LV2_WORKER__interface));
194 }
195
196 for(std::size_t i = 0; i < control_in_size; i++)
197 {
198 auto port_i = data.control_in_ports[i];
199 fInControls[i] = fParamInit[port_i];
200 }
201
202 if(!m_atom_timePosition_midi.empty() || !m_atom_timePosition_owned.empty())
203 {
204 // inspired from QTractor code, (c) RNCBC
205 timePositionBuffer = std::make_unique<uint8_t[]>(256);
206 }
207
208 lilv_instance_activate(fInstance);
209 }
210
211 void connect_all_ports()
212 {
213 const std::size_t control_in_size = data.control_in_ports.size();
214 const std::size_t control_out_size = data.control_out_ports.size();
215 const std::size_t midi_in_size = data.midi_in_ports.size();
216 const std::size_t midi_out_size = data.midi_out_ports.size();
217 const std::size_t atom_in_size = data.atom_in_ports.size();
218 const std::size_t atom_out_size = data.atom_out_ports.size();
219 const std::size_t cv_size = data.cv_ports.size();
220 const std::size_t other_size = data.control_other_ports.size();
221
222 for(std::size_t i = 0; i < control_in_size; i++)
223 {
224 lilv_instance_connect_port(fInstance, data.control_in_ports[i], &fInControls[i]);
225 }
226
227 for(std::size_t i = 0; i < control_out_size; i++)
228 {
229 lilv_instance_connect_port(fInstance, data.control_out_ports[i], &fOutControls[i]);
230 }
231
232 for(std::size_t i = 0; i < cv_size; i++)
233 {
234 lilv_instance_connect_port(fInstance, data.cv_ports[i], fCVs[i].data());
235 }
236
237 for(std::size_t i = 0; i < other_size; i++)
238 {
239 lilv_instance_connect_port(
240 fInstance, data.control_other_ports[i], &fOtherControls[i]);
241 }
242
243 for(std::size_t i = 0; i < midi_in_size; i++)
244 {
245 lilv_instance_connect_port(
246 fInstance, data.midi_in_ports[i], &m_midi_atom_ins[i].buf->atoms);
247 }
248
249 for(std::size_t i = 0; i < midi_out_size; i++)
250 {
251 auto& out_p = data.midi_out_ports[i];
252 auto atoms = &m_midi_atom_outs[i].buf->atoms;
253 lilv_instance_connect_port(fInstance, out_p, atoms);
254 }
255
256 for(std::size_t i = 0; i < atom_in_size; i++)
257 {
258 lilv_instance_connect_port(
259 fInstance, data.atom_in_ports[i], &m_atom_ins[i].buf->atoms);
260 }
261
262 for(std::size_t i = 0; i < atom_out_size; i++)
263 {
264 auto& out_p = data.atom_out_ports[i];
265 auto atoms = &m_atom_outs[i].buf->atoms;
266 lilv_instance_connect_port(fInstance, out_p, atoms);
267 }
268
269 for(auto& [index, port] : m_atom_timePosition_owned)
270 {
271 lilv_instance_connect_port(fInstance, index, &port->buf->atoms);
272 }
273 }
274
275 void all_notes_off() noexcept override
276 {
277 // TODO
278 }
279
280 [[nodiscard]] std::string label() const noexcept override
281 {
282 return fmt::format("lv2 ({})", data.effect.plugin.get_name().as_string());
283 }
284 void preProcess()
285 {
286 const std::size_t audio_in_size = data.audio_in_ports.size();
287 const std::size_t cv_size = data.cv_ports.size();
288 const std::size_t midi_in_size = data.midi_in_ports.size();
289 const std::size_t atom_in_size = data.atom_in_ports.size();
290 const std::size_t control_in_size = data.control_in_ports.size();
291
292 // Callback from UI
293 on_start();
294
295 // Copy midi
296 int first_midi_idx = (audio_in_size > 0 ? 1 : 0) + cv_size;
297 for(std::size_t i = 0; i < m_midi_atom_ins.size(); i++)
298 {
299 ossia::midi_port& ossia_port
300 = this->m_inlets[i + first_midi_idx]->template cast<ossia::midi_port>();
301 auto& lv2_port = m_midi_atom_ins[i];
302 Iterator it{lv2_port.buf};
303
304 // Message from the UI
305 for(const Message& msg : this->m_message_for_midi_atom_ins[i])
306 {
307 auto atom = (LV2_Atom*)msg.body.data();
308 auto atom_data = (const uint8_t*)LV2_ATOM_BODY(atom);
309 it.write(0, 0, atom->type, atom->size, atom_data);
310 }
311
312 // MIDI input
313 auto& conv = m_midi_2to1[i];
314 for(const libremidi::ump& msg : ossia_port.messages)
315 {
316 conv.convert(
317 msg.data, msg.size(), msg.timestamp,
318 [&](unsigned char* midi1, int bytes, int64_t ts) {
319 if(bytes > 0)
320 {
321 it.write(msg.timestamp, 0, data.host.midi_event_id, bytes, midi1);
322 }
323 return stdx::error{};
324 });
325 }
326
327 // Copy timing for MIDI ports
328 if(this->m_atom_timePosition_midi.size() != 0)
329 {
330 const LV2_Atom* atom = (const LV2_Atom*)timePositionBuffer.get();
331
332 // Time position
333 it.write(0, 0, atom->type, atom->size, (const uint8_t*)LV2_ATOM_BODY(atom));
334 }
335 }
336
337 // Copy timing for timing-only ports
338 for(auto& [port, atoms] : m_atom_timePosition_owned)
339 {
340 auto& lv2_port = atoms;
341 Iterator it{lv2_port->buf};
342 {
343 const LV2_Atom* atom = (const LV2_Atom*)timePositionBuffer.get();
344
345 // Time position
346 it.write(0, 0, atom->type, atom->size, (const uint8_t*)LV2_ATOM_BODY(atom));
347 }
348 }
349
350 // FIXME atom_in ports
351
352 // Copy controls
353 auto control_start
354 = (audio_in_size > 0 ? 1 : 0) + midi_in_size + atom_in_size + cv_size;
355 for(std::size_t i = control_start; i < control_in_size; i++)
356 {
357 auto& in = m_inlets[i]->template cast<ossia::value_port>().get_data();
358
359 if(!in.empty())
360 {
361 if(auto f = in.back().value.template target<float>())
362 {
363 fInControls[i - control_start] = *f;
364 }
365 }
366 }
367 }
368
369 void updateTime(const ossia::token_request& tk, ossia::exec_state_facade st)
370 {
371 LV2::HostContext& host = this->data.host;
372 auto& forge = host.forge;
373 uint8_t* buffer = timePositionBuffer.get();
374 lv2_atom_forge_set_buffer(&forge, buffer, 256);
375 LV2_Atom_Forge_Frame frame;
376 lv2_atom_forge_object(&forge, &frame, 0, host.time_Position_id);
377
378 lv2_atom_forge_key(&forge, host.time_frame_id);
379 lv2_atom_forge_long(&forge, this->m_processed_frames);
380
381 lv2_atom_forge_key(&forge, host.time_framesPerSecond_id);
382 lv2_atom_forge_long(&forge, st.sampleRate());
383
384 lv2_atom_forge_key(&forge, host.time_speed_id);
385 lv2_atom_forge_float(&forge, tk.speed);
386
387 lv2_atom_forge_key(&forge, host.time_bar_id);
388 lv2_atom_forge_long(&forge, tk.musical_start_last_bar / 4.);
389
390 lv2_atom_forge_key(&forge, host.time_beat_id);
391 lv2_atom_forge_double(&forge, tk.musical_start_position);
392
393 auto barBeat = float(tk.musical_start_position - tk.musical_start_last_bar);
394 lv2_atom_forge_key(&forge, host.time_barBeat_id);
395 lv2_atom_forge_float(&forge, barBeat);
396
397 lv2_atom_forge_key(&forge, host.time_beatUnit_id);
398 lv2_atom_forge_int(&forge, 4);
399
400 lv2_atom_forge_key(&forge, host.time_beatsPerBar_id);
401 lv2_atom_forge_float(
402 &forge, 4 * double(tk.signature.upper) / double(tk.signature.lower));
403
404 lv2_atom_forge_key(&forge, host.time_beatsPerMinute_id);
405 lv2_atom_forge_float(&forge, tk.tempo);
406
407 lv2_atom_forge_pop(&forge, &frame);
408 }
409
410 void postProcess(int64_t offset)
411 {
412 if(data.effect.worker && data.effect.worker->work_response)
413 {
414 std::vector<char> vec;
415 while(data.effect.worker_datas.try_dequeue(vec))
416 {
417 data.effect.worker->work_response(
418 data.effect.instance->lv2_handle, vec.size(), vec.data());
419 }
420 }
421
422 if(data.effect.worker && data.effect.worker->end_run)
423 {
424 data.effect.worker->end_run(data.effect.instance->lv2_handle);
425 }
426
427 const std::size_t audio_out_size = data.audio_out_ports.size();
428 const std::size_t midi_out_size = data.midi_out_ports.size();
429 const std::size_t control_out_size = data.control_out_ports.size();
430
431 // Copy midi
432 // FIXME the line below definitely does not look right
433 int first_midi_idx = (audio_out_size > 0 ? 1 : 0);
434 for(std::size_t i = 0; i < m_midi_atom_outs.size(); i++)
435 {
436 ossia::midi_port& ossia_port
437 = this->m_outlets[i + first_midi_idx]->template cast<ossia::midi_port>();
438 AtomBuffer& lv2_port = m_midi_atom_outs[i];
439 auto& conv = m_midi_1to2[i];
440
441 const LV2::HostContext& host = this->data.host;
442
443 LV2_ATOM_SEQUENCE_FOREACH(&lv2_port.buf->atoms, ev)
444 {
445 if(ev->body.type == host.midi_event_id)
446 {
447 libremidi::ump msg;
448 msg.timestamp = ev->time.frames;
449
450 auto bytes = (uint8_t*)LV2_ATOM_BODY_CONST(&ev->body);
451 conv.convert(
452 bytes, ev->body.size, ev->time.frames,
453 [&](const uint32_t* ump, int count, int64_t ts) {
454 libremidi::ump u;
455 std::copy_n(ump, std::min(count, 4), u.data);
456 u.timestamp = ts;
457 ossia_port.messages.push_back(u);
458 return stdx::error{};
459 });
460 }
461 else
462 {
463 // FIXME
464 qDebug() << "Unhandled LV2 event type: " << ev->body.type;
465 }
466 }
467 }
468
469 // Copy controls
470 auto control_start = (audio_out_size > 0 ? 1 : 0) + midi_out_size;
471 for(std::size_t i = control_start; i < control_out_size; i++)
472 {
473 auto& out = m_outlets[i]->template cast<ossia::value_port>();
474
475 out.write_value(fOutControls[i - control_start], offset);
476 }
477
478 // Callback to UI
479 on_finished();
480
481 for(AtomBuffer& port : m_midi_atom_ins)
482 {
483 port.buf->reset(true);
484 }
485 for(AtomBuffer& port : m_atom_ins)
486 {
487 port.buf->reset(true);
488 }
489 for(auto& [port, atoms] : m_atom_timePosition_owned)
490 {
491 atoms->buf->reset(true);
492 }
493 for(AtomBuffer& port : m_midi_atom_outs)
494 {
495 port.buf->reset(false);
496 }
497 for(AtomBuffer& port : m_atom_outs)
498 {
499 port.buf->reset(false);
500 }
501
502 for(auto& mqueue : m_message_for_midi_atom_ins)
503 mqueue.clear();
504 }
505
506 ~lv2_node() override
507 {
508 lilv_instance_deactivate(fInstance);
509 if(!m_atom_timePosition_owned.empty())
510 for(auto [port, atoms] : m_atom_timePosition_owned)
511 delete atoms;
512 }
513
514 void run(const ossia::token_request& tk, ossia::exec_state_facade st) noexcept override
515 {
516 if(tk.date > tk.prev_date)
517 {
518 data.host.current = &data.effect;
519 if(!data.time_Position_ports.empty())
520 updateTime(tk, st);
521
522 preProcess();
523
524 const auto [tick_start, samples] = st.timings(tk);
525 const auto audio_ins = data.audio_in_ports.size();
526 const auto audio_outs = data.audio_out_ports.size();
527 ossia::small_vector<ossia::float_vector, 2> in_vec;
528 in_vec.resize(audio_ins);
529 ossia::small_vector<ossia::float_vector, 2> out_vec;
530 out_vec.resize(audio_outs);
531
532 connect_all_ports();
533 if(audio_ins > 0)
534 {
535 const auto& audio_in = m_inlets[0]->template cast<ossia::audio_port>();
536 for(std::size_t i = 0; i < audio_ins; i++)
537 {
538 in_vec[i].resize(samples);
539 if(audio_in.channels() > i)
540 {
541 for(std::size_t j = 0;
542 j < std::min(std::size_t(samples), audio_in.channel(i).size()); j++)
543 {
544 in_vec[i][j] = (float)audio_in.channel(i)[j];
545 }
546 }
547 lilv_instance_connect_port(
548 fInstance, data.audio_in_ports[i], in_vec[i].data());
549 }
550 }
551
552 if(audio_outs > 0)
553 {
554 for(std::size_t i = 0; i < audio_outs; i++)
555 {
556 out_vec[i].resize(samples);
557 lilv_instance_connect_port(
558 fInstance, data.audio_out_ports[i], out_vec[i].data());
559 }
560 }
561
562 lilv_instance_run(fInstance, samples);
563
564 if(audio_outs > 0)
565 {
566 auto& audio_out = static_cast<ossia::audio_outlet*>(m_outlets[0])->data;
567 audio_out.set_channels(audio_outs);
568 for(std::size_t i = 0; i < audio_outs; i++)
569 {
570 audio_out.channel(i).clear();
571 audio_out.channel(i).reserve(samples);
572 for(int64_t j = 0; j < samples; j++)
573 {
574 audio_out.channel(i).push_back((double)out_vec[i][j]);
575 }
576 }
577 }
578
579 postProcess(tk.physical_start(st.modelToSamples()));
580 }
581 }
582};
583}
Definition lv2_atom_helpers.hpp:195
Definition lv2_atom_helpers.hpp:99
Definition Context.hpp:58
Definition Context.hpp:216
Definition score-plugin-lv2/LV2/Node.hpp:32
Definition score-plugin-lv2/LV2/Node.hpp:16
Definition score-plugin-lv2/LV2/EffectModel.cpp:688