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