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