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