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 namespace LV2
11 {
12 template <typename OnExecStart, typename OnExecFinished>
13 struct 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{};
27  struct MatchedPort
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:681