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