score-plugin-vst/Vst/Node.hpp
1 #pragma once
2 #include <Process/Dataflow/TimeSignature.hpp>
3 
4 #include <Vst/EffectModel.hpp>
5 
6 #include <ossia/dataflow/fx_node.hpp>
7 #include <ossia/dataflow/graph_node.hpp>
8 #include <ossia/dataflow/port.hpp>
9 #include <ossia/detail/pod_vector.hpp>
10 #include <ossia/editor/scenario/time_signature.hpp>
11 namespace vst
12 {
13 
14 class vst_node_base : public ossia::graph_node
15 {
16 public:
17  std::shared_ptr<AEffectWrapper> fx{};
18 
19 protected:
20  explicit vst_node_base(std::shared_ptr<AEffectWrapper>&& ptr)
21  : fx{std::move(ptr)}
22  {
23  m_inlets.reserve(10);
24  controls.reserve(10);
25  }
26 
27  struct vst_control
28  {
29  int idx{};
30  float value{};
31  ossia::value_port* port{};
32  };
33 
34  inline void dispatch(
35  int32_t opcode, int32_t index = 0, intptr_t value = 0, void* ptr = nullptr,
36  float opt = 0.0f)
37  {
38  fx->dispatch(opcode, index, value, ptr, opt);
39  }
40 
41 public:
42  ossia::small_vector<vst_control, 16> controls;
43 
44  void setControls()
45  {
46  for(vst_control& p : controls)
47  {
48  const auto& vec = p.port->get_data();
49  if(vec.empty())
50  continue;
51  auto t = ossia::convert<float>(last(vec));
52  {
53  p.value = ossia::clamp<float>(t, 0.f, 1.f);
54  fx->fx->setParameter(fx->fx, p.idx, p.value);
55  }
56  }
57  }
58 
59  auto& prepareInput(int64_t offset, int64_t samples)
60  {
61  const auto bs = offset + samples;
62  auto& p = *m_inlets[0]->template target<ossia::audio_port>();
63  switch(p.channels())
64  {
65  case 0: {
66  p.set_channels(2);
67  p.channel(0).resize(bs);
68  p.channel(1).resize(bs);
69  break;
70  }
71  case 1: {
72  p.set_channels(2);
73  p.channel(0).resize(bs);
74  p.channel(1).assign(p.channel(0).begin(), p.channel(0).end());
75  break;
76  }
77  default: {
78  for(auto& i : p)
79  i.resize(bs);
80  break;
81  }
82  }
83 
84  return p.get();
85  }
86 
87  auto& prepareOutput(int64_t offset, int64_t samples)
88  {
89  const auto bs = offset + samples;
90  auto& p = *m_outlets[0]->template target<ossia::audio_port>();
91  p.set_channels(2);
92  for(auto& chan : p)
93  chan.resize(bs, boost::container::default_init);
94  return p.get();
95  }
96 
97  void setupTimeInfo(const ossia::token_request& tk, ossia::exec_state_facade st)
98  {
99  static const constexpr double ppq_reference = 960.;
100 
101  auto& time_info = fx->info;
102  time_info.samplePos = this->m_processed_frames;
103  time_info.sampleRate = st.sampleRate();
104  time_info.nanoSeconds = st.currentDate() - st.startDate();
105  time_info.ppqPos = tk.musical_start_position; // * ppq_reference;
106  time_info.tempo = tk.tempo;
107  time_info.barStartPos = tk.musical_start_last_bar; // * ppq_reference;
108  time_info.cycleStartPos = 0.;
109  time_info.cycleEndPos = 0.;
110  time_info.timeSigNumerator = tk.signature.upper;
111  time_info.timeSigDenominator = tk.signature.lower;
112  time_info.smpteOffset = 0;
113  time_info.smpteFrameRate = 0;
114  time_info.samplesToNextClock = 0;
115  time_info.flags = kVstTransportPlaying | kVstNanosValid | kVstPpqPosValid
116  | kVstTempoValid | kVstBarsValid | kVstTimeSigValid
117  | kVstClockValid;
118  }
119 };
120 
121 template <bool UseDouble, bool IsSynth>
122 class vst_node final : public vst_node_base
123 {
124 public:
125  static constexpr bool synth = IsSynth;
126  VstSpeakerArrangement i_arr{};
127  VstSpeakerArrangement o_arr{};
128  int m_bs{};
129 
130  vst_node(std::shared_ptr<AEffectWrapper> dat, int sampleRate, int bs)
131  : vst_node_base{std::move(dat)}
132  , m_bs{bs}
133  {
134  // Midi or audio input
135  m_inlets.push_back(new ossia::audio_inlet);
136  if constexpr(IsSynth)
137  m_inlets.push_back(new ossia::midi_inlet);
138 
139  // audio output
140  m_outlets.push_back(new ossia::audio_outlet);
141 
142  {
143  memset(&i_arr, 0, sizeof(i_arr));
144  memset(&o_arr, 0, sizeof(o_arr));
145  i_arr.type = kSpeakerArrStereo;
146  i_arr.numChannels = 2;
147  i_arr.speakers[0].type = kSpeakerL;
148  i_arr.speakers[1].type = kSpeakerR;
149 
150  o_arr.type = kSpeakerArrStereo;
151  o_arr.numChannels = 2;
152  o_arr.speakers[0].type = kSpeakerL;
153  o_arr.speakers[1].type = kSpeakerR;
154  dispatch(effSetSpeakerArrangement, 0, (intptr_t)&i_arr, (void*)&o_arr, 0);
155  }
156 
157  dispatch(effSetSampleRate, 0, sampleRate, nullptr, sampleRate);
158  dispatch(effSetBlockSize, 0, bs, nullptr, bs); // Generalize what's in pd
159  dispatch(
160  effSetProcessPrecision, 0,
161  UseDouble ? kVstProcessPrecision64 : kVstProcessPrecision32);
162  dispatch(effMainsChanged, 0, 1);
163  dispatch(effStartProcess);
164 
165  fx->fx->resvd2 = reinterpret_cast<intptr_t>(this);
166  }
167 
168  ~vst_node()
169  {
170  fx->fx->resvd2 = 0;
171  dispatch(effStopProcess);
172  dispatch(effMainsChanged, 0, 0);
173  }
174 
175  std::string label() const noexcept override { return ""; }
176 
177  void all_notes_off() noexcept override
178  {
179  if constexpr(IsSynth)
180  {
181  // copy midi data
182  // should be 32 but some VSTs read a bit out of bounds apparently ! so we allocate a bit more memory
183  constexpr auto sz = sizeof(VstEvents) + sizeof(void*) * 32 * 2;
184  VstEvents* events = (VstEvents*)alloca(sz);
185  std::memset(events, 0, sz);
186 
187  events->numEvents = 32;
188 
189  VstMidiEvent ev[64] = {};
190  memset(&ev, 0, sizeof(ev));
191 
192  // All notes off
193  for(int i = 0; i < 16; i++)
194  {
195  auto& e = ev[i];
196  e.type = kVstMidiType;
197  e.flags = kVstMidiEventIsRealtime;
198  e.byteSize = sizeof(VstMidiEvent);
199 
200  e.midiData[0] = (char)(uint8_t)176 + i;
201  e.midiData[1] = (char)(uint8_t)123;
202  e.midiData[2] = 0;
203  e.midiData[3] = 0;
204 
205  events->events[i] = reinterpret_cast<VstEvent*>(&e);
206  }
207 
208  // All sound off
209  for(int i = 0; i < 16; i++)
210  {
211  auto& e = ev[16 + i];
212  e.type = kVstMidiType;
213  e.flags = kVstMidiEventIsRealtime;
214  e.byteSize = sizeof(VstMidiEvent);
215 
216  e.midiData[0] = (char)(uint8_t)176 + i;
217  e.midiData[1] = (char)(uint8_t)121;
218  e.midiData[2] = 0;
219  e.midiData[3] = 0;
220 
221  events->events[16 + i] = reinterpret_cast<VstEvent*>(&e);
222  }
223 
224  dispatch(effProcessEvents, 0, 0, events, 0.f);
225 
226  if constexpr(!UseDouble)
227  {
228  float* dummy = (float*)alloca(sizeof(float) * m_bs);
229  std::fill_n(dummy, m_bs, 0.f);
230 
231  float** input
232  = (float**)alloca(sizeof(float*) * std::max(2, this->fx->fx->numInputs));
233  for(int i = 0; i < this->fx->fx->numInputs; i++)
234  input[i] = dummy;
235 
236  float** output
237  = (float**)alloca(sizeof(float*) * std::max(2, this->fx->fx->numOutputs));
238  for(int i = 0; i < this->fx->fx->numOutputs; i++)
239  output[i] = dummy;
240 
241  fx->fx->processReplacing(fx->fx, input, output, m_bs);
242  }
243  else
244  {
245  double* dummy = (double*)alloca(sizeof(double) * m_bs);
246  std::fill_n(dummy, m_bs, 0.);
247 
248  double** input
249  = (double**)alloca(sizeof(double*) * std::max(2, this->fx->fx->numInputs));
250  for(int i = 0; i < this->fx->fx->numInputs; i++)
251  input[i] = dummy;
252 
253  double** output
254  = (double**)alloca(sizeof(double*) * std::max(2, this->fx->fx->numOutputs));
255  for(int i = 0; i < this->fx->fx->numOutputs; i++)
256  output[i] = dummy;
257 
258  fx->fx->processDoubleReplacing(fx->fx, input, output, m_bs);
259  }
260  }
261  }
262 
263  // Note: the function that does the actual tick is passed as an argument
264  // since some plug-ins only store pointers to VstEvents struct,
265  // which would go out of scope if this function was just called like this.
266  template <typename Fun>
267  void dispatchMidi(int64_t offset, Fun&& f)
268  {
269  // copy midi data
270  auto& ip = static_cast<ossia::midi_inlet*>(m_inlets[1])->data.messages;
271  const auto n_mess = ip.size();
272  if(n_mess == 0)
273  {
274  f();
275  return;
276  }
277 
278  // -2 since two are already available ?
279  const auto sz = sizeof(VstEvents) + sizeof(void*) * n_mess * 2;
280  VstEvents* events = (VstEvents*)alloca(sz);
281  std::memset(events, 0, sz);
282  events->numEvents = n_mess;
283 
284  ossia::small_vector<VstMidiEvent, 16> vec;
285  vec.resize(n_mess);
286  std::size_t i = 0;
287  for(libremidi::message& mess : ip)
288  {
289  VstMidiEvent& e = vec[i];
290  std::memset(&e, 0, sizeof(VstMidiEvent));
291 
292  e.type = kVstMidiType;
293  e.byteSize = sizeof(VstMidiEvent);
294  e.deltaFrames = mess.timestamp - offset;
295  e.flags = kVstMidiEventIsRealtime;
296 
297  std::memcpy(
298  e.midiData, mess.bytes.data(), std::min(mess.bytes.size(), (std::size_t)4));
299 
300  events->events[i] = reinterpret_cast<VstEvent*>(&e);
301  i++;
302  }
303  dispatch(effProcessEvents, 0, 0, events, 0.f);
304  f();
305  }
306 
307  void run(const ossia::token_request& tk, ossia::exec_state_facade st) noexcept override
308  {
309  if(!muted() && tk.date > tk.prev_date)
310  {
311  const auto timings = st.timings(tk);
312  this->setControls();
313  this->setupTimeInfo(tk, st);
314 
315  if constexpr(UseDouble)
316  {
317  if constexpr(IsSynth)
318  {
319  dispatchMidi(timings.start_sample, [this, timings] {
320  processDouble(timings.start_sample, timings.length);
321  });
322  }
323  else
324  {
325  processDouble(timings.start_sample, timings.length);
326  }
327  }
328  else
329  {
330  if constexpr(IsSynth)
331  {
332  dispatchMidi(timings.start_sample, [this, timings] {
333  processFloat(timings.start_sample, timings.length);
334  });
335  }
336  else
337  {
338  processFloat(timings.start_sample, timings.length);
339  }
340  }
341 
342  // upmix mono VSTs to stereo
343  if(this->fx->fx->numOutputs == 1)
344  {
345  auto& op = m_outlets[0]->template target<ossia::audio_port>()->get();
346  op[1].assign(op[0].begin(), op[0].end());
347  }
348  }
349  }
350 
351  void processFloat(int64_t offset, int64_t samples)
352  {
353  if constexpr(!UseDouble)
354  {
355  if(samples <= 0)
356  return;
357 
358  SCORE_ASSERT(m_bs >= offset + samples);
359 
360  const auto max_i = std::max(2, this->fx->fx->numInputs);
361  const auto max_o = std::max(2, this->fx->fx->numOutputs);
362  const auto max_io = std::max(max_i, max_o);
363 
364  const auto max_samples = std::max(samples, (int64_t)m_bs); // * 16;
365 
366  //qDebug() << samples << m_bs << max_samples << offset << " ::: " << max_i << max_o << max_io;
367 
368  // prepare ossia::graph_node buffers
369  auto& ip = prepareInput(offset, samples);
370  SCORE_ASSERT(ip.size() >= 2);
371 
372  auto& op = prepareOutput(offset, samples);
373  SCORE_ASSERT(op.size() >= 2);
374 
375  // copy io
376  float_v[0].resize(max_samples);
377  float_v[1].resize(max_samples);
378 
379  std::copy_n(ip[0].data() + offset, samples, float_v[0].data());
380  std::copy_n(ip[1].data() + offset, samples, float_v[1].data());
381 
382  float** io = (float**)alloca(sizeof(float*) * max_io);
383  io[0] = float_v[0].data();
384  io[1] = float_v[1].data();
385 
386  if(max_io > 2)
387  {
388  // Note that alloca has *function* scope, not block scope so it is
389  // freed at the end
390  float* dummy = (float*)alloca(sizeof(float) * max_samples);
391  std::fill_n(dummy, max_samples, 0.f);
392 
393  for(int i = 2; i < max_io; i++)
394  io[i] = dummy;
395  }
396 
397  fx->fx->processReplacing(fx->fx, io, io, samples);
398 
399  std::copy_n(float_v[0].data(), samples, op[0].data() + offset);
400  std::copy_n(float_v[1].data(), samples, op[1].data() + offset);
401 
402  float_v[0].clear();
403  float_v[1].clear();
404  }
405  }
406 
407  void processDouble(int64_t offset, int64_t samples)
408  {
409  if constexpr(UseDouble)
410  {
411  if(samples <= 0)
412  return;
413 
414  SCORE_ASSERT(m_bs >= offset + samples);
415 
416  const auto max_i = std::max(2, this->fx->fx->numInputs);
417  const auto max_o = std::max(2, this->fx->fx->numOutputs);
418  const auto max_io = std::max(max_i, max_o);
419 
420  // copy audio data
421  auto& ip = prepareInput(offset, samples);
422  SCORE_ASSERT(ip.size() >= 2);
423 
424  auto& op = prepareOutput(offset, samples);
425  SCORE_ASSERT(op.size() >= 2);
426 
427  double** input = (double**)alloca(sizeof(double*) * max_i);
428  input[0] = ip[0].data() + offset;
429  input[1] = ip[1].data() + offset;
430 
431  double** output = (double**)alloca(sizeof(double*) * max_o);
432  output[0] = op[0].data() + offset;
433  output[1] = op[1].data() + offset;
434 
435  if(max_io > 2)
436  {
437  // Note that alloca has *function* scope, not block scope so it is
438  // freed at the end
439  double* dummy = (double*)alloca(sizeof(double) * m_bs);
440  std::fill_n(dummy, m_bs, 0.);
441  for(int i = 2; i < this->fx->fx->numInputs; i++)
442  input[i] = dummy;
443  for(int i = 2; i < this->fx->fx->numOutputs; i++)
444  output[i] = dummy;
445  }
446 
447  fx->fx->processDoubleReplacing(fx->fx, input, output, samples);
448  }
449  }
450 
451  struct dummy_t
452  {
453  };
454  std::conditional_t<!UseDouble, std::array<ossia::float_vector, 2>, dummy_t> float_v;
455 };
456 
457 template <bool b1, bool b2, typename... Args>
458 auto make_vst_fx(Args&... args)
459 {
460  return ossia::make_node<vst_node<b1, b2>>(args...);
461 }
462 }
Definition: score-plugin-vst/Vst/Node.hpp:15
Definition: score-plugin-vst/Vst/Node.hpp:123
Definition: score-plugin-vst/Vst/Node.hpp:452
Definition: score-plugin-vst/Vst/Node.hpp:28