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