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#include <Vst/MidiEvents.hpp>
6
7#include <ossia/dataflow/fx_node.hpp>
8#include <ossia/dataflow/graph_node.hpp>
9#include <ossia/dataflow/port.hpp>
10#include <ossia/detail/algorithms.hpp>
11#include <ossia/detail/pod_vector.hpp>
12#include <ossia/editor/scenario/time_signature.hpp>
13
14#include <libremidi/detail/conversion.hpp>
15
16#include <algorithm>
17#include <atomic>
18#include <cmath>
19#include <thread>
20namespace vst
21{
22
23class vst_node_base : public ossia::graph_node
24{
25public:
26 std::shared_ptr<AEffectWrapper> fx{};
27
28protected:
29 explicit vst_node_base(std::shared_ptr<AEffectWrapper>&& ptr)
30 : fx{std::move(ptr)}
31 {
32 this->set_not_fp_safe();
33 m_inlets.reserve(10);
34 controls.reserve(10);
35 }
36
38 {
39 int idx{};
40 float value{};
41 ossia::value_port* port{};
42 };
43
44 inline void dispatch(
45 int32_t opcode, int32_t index = 0, intptr_t value = 0, void* ptr = nullptr,
46 float opt = 0.0f)
47 {
48 fx->dispatch(opcode, index, value, ptr, opt);
49 }
50
51public:
52 ossia::small_vector<vst_control, 16> controls;
53
55 ossia::midi_outlet* midi_out{};
56
60 void receiveEvents(const VstEvents* events) noexcept
61 {
62 if(!midi_out || !events
63 || m_processThread.load(std::memory_order_relaxed) != std::this_thread::get_id())
64 return;
65 auto& out = midi_out->data.messages;
66 const auto push = [&](const libremidi::ump& u) {
67 if(out.size() < 4096)
68 out.push_back(u);
69 };
70 for(int i = 0; i < events->numEvents; i++)
71 {
72 const VstEvent* e = events->events[i];
73 if(!e)
74 continue;
75 const int64_t date = m_blockStart + std::max(0, e->deltaFrames);
76 if(e->type == kVstMidiType)
77 {
78 const auto* m = reinterpret_cast<const VstMidiEvent*>(e);
79 const auto* bytes = reinterpret_cast<const unsigned char*>(m->midiData);
80 m_toUmp.convert_packets(bytes, midiMessageSize(bytes[0]), date, push);
81 }
82 else if(e->type == kVstSysExType)
83 {
84 const auto* s = reinterpret_cast<const VstMidiSysexEvent*>(e);
85 if(s->sysexDump && s->dumpBytes > 0)
86 m_toUmp.convert_packets(
87 reinterpret_cast<const unsigned char*>(s->sysexDump),
88 std::size_t(s->dumpBytes), date, push);
89 }
90 }
91 }
92
93 static std::size_t midiMessageSize(unsigned char status) noexcept
94 {
95 switch(status & 0xF0)
96 {
97 case 0xC0:
98 case 0xD0:
99 return 2;
100 case 0xF0:
101 switch(status)
102 {
103 case 0xF1:
104 case 0xF3:
105 return 2;
106 case 0xF2:
107 return 3;
108 default:
109 return 1;
110 }
111 default:
112 return 3;
113 }
114 }
115
116protected:
119 std::atomic<std::thread::id> m_processThread{};
120 int64_t m_blockStart{};
121 libremidi::midi1_to_midi2 m_toUmp;
122
123public:
124
125 void setControls()
126 {
127 for(vst_control& p : controls)
128 {
129 const auto& vec = p.port->get_data();
130 if(vec.empty())
131 continue;
132 auto t = ossia::convert<float>(last(vec));
133 {
134 p.value = ossia::clamp<float>(t, 0.f, 1.f);
135 fx->fx->setParameter(fx->fx, p.idx, p.value);
136 }
137 }
138 }
139
140 auto& prepareInput(int64_t offset, int64_t samples)
141 {
142 const auto bs = offset + samples;
143 auto& p = *m_inlets[0]->template target<ossia::audio_port>();
144 switch(p.channels())
145 {
146 case 0: {
147 p.set_channels(2);
148 p.channel(0).resize(bs);
149 p.channel(1).resize(bs);
150 break;
151 }
152 case 1: {
153 p.set_channels(2);
154 p.channel(0).resize(bs);
155 p.channel(1).assign(p.channel(0).begin(), p.channel(0).end());
156 break;
157 }
158 default: {
159 for(auto& i : p)
160 i.resize(bs);
161 break;
162 }
163 }
164
165 return p.get();
166 }
167
168 auto& prepareOutput(int64_t offset, int64_t samples)
169 {
170 const auto bs = offset + samples;
171 auto& p = *m_outlets[0]->template target<ossia::audio_port>();
172 p.set_channels(2);
173 for(auto& chan : p)
174 chan.resize(bs, boost::container::default_init);
175 return p.get();
176 }
177
180
181 void setupTimeInfo(const ossia::token_request& tk, ossia::exec_state_facade st)
182 {
183 auto& time_info = fx->info;
184 // The playhead at the first sample of this block. m_transport_frames is
185 // only updated once a node has run: it is that of the previous tick.
186 time_info.samplePos = tk.speed != 0. ? tk.start_date_to_physical(st.modelToSamples())
187 : this->m_transport_frames;
188 m_backwards = tk.speed < 0.;
189 time_info.sampleRate = st.sampleRate();
190 time_info.nanoSeconds = st.currentDate() - st.startDate();
191 time_info.ppqPos = tk.musical_start_position; // * ppq_reference;
192 time_info.tempo = tk.tempo;
193 time_info.barStartPos = tk.musical_start_last_bar; // * ppq_reference;
194 time_info.cycleStartPos = 0.;
195 time_info.cycleEndPos = 0.;
196 time_info.timeSigNumerator = tk.signature.upper;
197 time_info.timeSigDenominator = tk.signature.lower;
198 time_info.smpteOffset = 0;
199 time_info.smpteFrameRate = 0;
200 time_info.samplesToNextClock = 0;
201 time_info.flags = kVstTransportPlaying | kVstNanosValid | kVstPpqPosValid
202 | kVstTempoValid | kVstBarsValid | kVstTimeSigValid
203 | kVstClockValid;
204 }
205};
206
207template <bool UseDouble, bool IsSynth>
208class vst_node final : public vst_node_base
209{
210public:
211 static constexpr bool synth = IsSynth;
212 VstSpeakerArrangement i_arr{};
213 VstSpeakerArrangement o_arr{};
214 int m_bs{};
215
216 vst_node(
217 std::shared_ptr<AEffectWrapper> dat, int sampleRate, int bs, bool midiOutput = false)
218 : vst_node_base{std::move(dat)}
219 , m_bs{bs}
220 {
221 // Midi or audio input
222 m_inlets.push_back(new ossia::audio_inlet);
223 if constexpr(IsSynth)
224 m_inlets.push_back(new ossia::midi_inlet);
225
226 // audio output
227 m_outlets.push_back(new ossia::audio_outlet);
228 if(midiOutput)
229 {
230 midi_out = new ossia::midi_outlet;
231 m_outlets.push_back(midi_out);
232 }
233
234 {
235 memset(&i_arr, 0, sizeof(i_arr));
236 memset(&o_arr, 0, sizeof(o_arr));
237 i_arr.type = kSpeakerArrStereo;
238 i_arr.numChannels = 2;
239 i_arr.speakers[0].type = kSpeakerL;
240 i_arr.speakers[1].type = kSpeakerR;
241
242 o_arr.type = kSpeakerArrStereo;
243 o_arr.numChannels = 2;
244 o_arr.speakers[0].type = kSpeakerL;
245 o_arr.speakers[1].type = kSpeakerR;
246 dispatch(effSetSpeakerArrangement, 0, (intptr_t)&i_arr, (void*)&o_arr, 0);
247 }
248
249 dispatch(effSetSampleRate, 0, sampleRate, nullptr, sampleRate);
250 dispatch(effSetBlockSize, 0, bs, nullptr, bs); // Generalize what's in pd
251 dispatch(
252 effSetProcessPrecision, 0,
253 UseDouble ? kVstProcessPrecision64 : kVstProcessPrecision32);
254 dispatch(effMainsChanged, 0, 1);
255 dispatch(effStartProcess);
256
257 fx->fx->resvd2 = reinterpret_cast<intptr_t>(static_cast<vst_node_base*>(this));
258 }
259
260 ~vst_node()
261 {
262 fx->fx->resvd2 = 0;
263 fx->info.flags = (fx->info.flags & ~kVstTransportPlaying) | kVstTransportChanged;
264 dispatch(effStopProcess);
265 dispatch(effMainsChanged, 0, 0);
266 }
267
268 std::string label() const noexcept override
269 {
270 char paramName[512] = {0};
271 fx->fx->dispatcher(fx->fx, effGetProductString, 0, 0, paramName, 0.0f);
272 return paramName;
273 }
274
275 void all_notes_off() noexcept override
276 {
277 if constexpr(IsSynth)
278 {
279 // copy midi data
280 // should be 32 but some VSTs read a bit out of bounds apparently ! so we allocate a bit more memory
281 constexpr auto sz = sizeof(VstEvents) + sizeof(void*) * 32 * 2;
282 VstEvents* events = (VstEvents*)alloca(sz);
283 std::memset(events, 0, sz);
284
285 events->numEvents = 32;
286
287 VstMidiEvent ev[64] = {};
288 memset(&ev, 0, sizeof(ev));
289
290 // All notes off
291 for(int i = 0; i < 16; i++)
292 {
293 auto& e = ev[i];
294 e.type = kVstMidiType;
295 e.flags = kVstMidiEventIsRealtime;
296 e.byteSize = sizeof(VstMidiEvent);
297
298 e.midiData[0] = (char)(uint8_t)176 + i;
299 e.midiData[1] = (char)(uint8_t)123;
300 e.midiData[2] = 0;
301 e.midiData[3] = 0;
302
303 events->events[i] = reinterpret_cast<VstEvent*>(&e);
304 }
305
306 // All sound off
307 for(int i = 0; i < 16; i++)
308 {
309 auto& e = ev[16 + i];
310 e.type = kVstMidiType;
311 e.flags = kVstMidiEventIsRealtime;
312 e.byteSize = sizeof(VstMidiEvent);
313
314 e.midiData[0] = (char)(uint8_t)176 + i;
315 e.midiData[1] = (char)(uint8_t)121;
316 e.midiData[2] = 0;
317 e.midiData[3] = 0;
318
319 events->events[16 + i] = reinterpret_cast<VstEvent*>(&e);
320 }
321
322 dispatch(effProcessEvents, 0, 0, events, 0.f);
323
324 if constexpr(!UseDouble)
325 {
326 float* dummy = (float*)alloca(sizeof(float) * m_bs);
327 std::fill_n(dummy, m_bs, 0.f);
328
329 float** input
330 = (float**)alloca(sizeof(float*) * std::max(2, this->fx->fx->numInputs));
331 for(int i = 0; i < this->fx->fx->numInputs; i++)
332 input[i] = dummy;
333
334 float** output
335 = (float**)alloca(sizeof(float*) * std::max(2, this->fx->fx->numOutputs));
336 for(int i = 0; i < this->fx->fx->numOutputs; i++)
337 output[i] = dummy;
338
339 fx->fx->processReplacing(fx->fx, input, output, m_bs);
340 }
341 else
342 {
343 double* dummy = (double*)alloca(sizeof(double) * m_bs);
344 std::fill_n(dummy, m_bs, 0.);
345
346 double** input
347 = (double**)alloca(sizeof(double*) * std::max(2, this->fx->fx->numInputs));
348 for(int i = 0; i < this->fx->fx->numInputs; i++)
349 input[i] = dummy;
350
351 double** output
352 = (double**)alloca(sizeof(double*) * std::max(2, this->fx->fx->numOutputs));
353 for(int i = 0; i < this->fx->fx->numOutputs; i++)
354 output[i] = dummy;
355
356 fx->fx->processDoubleReplacing(fx->fx, input, output, m_bs);
357 }
358 }
359 }
360
361 // Note: the function that does the actual tick is passed as an argument
362 // since some plug-ins only store pointers to VstEvents struct,
363 // which would go out of scope if this function was just called like this.
368 {
369 for(const libremidi::ump& mess :
370 static_cast<ossia::midi_inlet*>(m_inlets[1])->data.messages)
371 {
372 if(m_deferred.size() >= 1024)
373 return;
374 m_deferred.push_back(mess);
375 }
376 }
377
382 template <typename Fun>
383 void dispatchMidi(int64_t offset, int64_t samples, bool first, bool last, Fun&& f)
384 {
385 auto& ip = static_cast<ossia::midi_inlet*>(m_inlets[1])->data.messages;
386 const auto in_block = [=](const libremidi::ump& mess) {
387 return (first || mess.timestamp >= offset)
388 && (last || mess.timestamp < offset + samples);
389 };
390 const auto n_mess
391 = (first ? m_deferred.size() : 0) + ossia::count_if(ip, in_block);
392 if(n_mess == 0)
393 {
394 f();
395 return;
396 }
397
398 // -2 since two are already available ?
399 const auto sz = sizeof(VstEvents) + sizeof(void*) * n_mess * 2;
400 VstEvents* events = (VstEvents*)alloca(sz);
401 std::memset(events, 0, sz);
402
403 ossia::small_vector<std::pair<const libremidi::ump*, int64_t>, 16> to_send;
404 if(first)
405 for(const libremidi::ump& mess : m_deferred)
406 to_send.push_back({&mess, 0});
407 for(const libremidi::ump& mess : ip)
408 if(in_block(mess))
409 to_send.push_back(
410 {&mess, std::clamp<int64_t>(
411 mess.timestamp - offset, 0, samples > 0 ? samples - 1 : 0)});
412
413 // The plug-in may read the events, sysex bytes included, until f() has
414 // returned.
415 ossia::small_vector<VstMidiEvent, 16> vec;
416 vec.resize(n_mess);
417 m_sysexArena.clear();
418 std::size_t sysex_count = 0;
419 std::size_t i = 0;
420 for(const auto& [mess_ptr, delta] : to_send)
421 {
422 VstMidiSysexEvent* sysex
423 = sysex_count < m_sysexEvents.size() ? &m_sysexEvents[sysex_count] : nullptr;
424 VstEvent* e = toVstEvent(
425 *mess_ptr, int32_t(delta), vec[i], sysex, m_sysexArena, m_sysexStream);
426 if(!e)
427 continue;
428 if(e == reinterpret_cast<VstEvent*>(sysex))
429 sysex_count++;
430 events->events[i++] = e;
431 }
432 events->numEvents = int32_t(i);
433 if(first)
434 m_deferred.clear();
435 dispatch(effProcessEvents, 0, 0, events, 0.f);
436 f();
437 }
438
439 void run(const ossia::token_request& tk, ossia::exec_state_facade st) noexcept override
440 {
441 if(!muted() && !tk.paused())
442 {
443 const auto timings = st.timings(tk);
444 if(timings.length <= 0)
445 {
446 // Never call a plug-in with an empty block, but do not lose what the
447 // tick carried: it goes out at the top of the next real one.
448 if constexpr(IsSynth)
449 this->stashMidi();
450 return;
451 }
452
453 this->setControls();
454 this->setupTimeInfo(tk, st);
455
456 if constexpr(UseDouble)
457 {
458 if constexpr(IsSynth)
459 {
460 const int64_t end = timings.start_sample + timings.length;
461 forEachBlock(timings.start_sample, timings.length, [&](int64_t o, int64_t n) {
462 dispatchMidi(o, n, o == timings.start_sample, o + n == end, [&] {
463 processDoubleBlock(o, n);
464 });
465 });
466 }
467 else
468 {
469 processDouble(timings.start_sample, timings.length);
470 }
471 }
472 else
473 {
474 if constexpr(IsSynth)
475 {
476 const int64_t end = timings.start_sample + timings.length;
477 forEachBlock(timings.start_sample, timings.length, [&](int64_t o, int64_t n) {
478 dispatchMidi(o, n, o == timings.start_sample, o + n == end, [&] {
479 processFloatBlock(o, n);
480 });
481 });
482 }
483 else
484 {
485 processFloat(timings.start_sample, timings.length);
486 }
487 }
488
489 // upmix mono VSTs to stereo
490 if(this->fx->fx->numOutputs == 1)
491 {
492 auto& op = m_outlets[0]->template target<ossia::audio_port>()->get();
493 op[1].assign(op[0].begin(), op[0].end());
494 }
495 }
496 }
497
501 template <typename F>
502 void forEachBlock(int64_t offset, int64_t samples, F&& f)
503 {
504 auto& time_info = fx->info;
505 const double ppq_per_frame = time_info.sampleRate > 0 && time_info.tempo > 0
506 ? time_info.tempo / (60. * time_info.sampleRate)
507 : 0.;
508 const int64_t block = std::max(m_bs, 1);
509 m_processThread.store(std::this_thread::get_id(), std::memory_order_relaxed);
510 for(int64_t done = 0; done < samples;)
511 {
512 const int64_t n = std::min(block, samples - done);
513 m_blockStart = offset + done;
514 f(offset + done, n);
515 done += n;
516 if(done < samples)
517 {
518 // Played backwards, the timeline runs from the end of each block to
519 // its start: the next block starts earlier
520 const double step = m_backwards ? -double(n) : double(n);
521 time_info.samplePos += step;
522 if(ppq_per_frame > 0.)
523 {
524 time_info.ppqPos += step * ppq_per_frame;
525 // The bar the block starts in
526 const double bar = time_info.timeSigDenominator > 0
527 ? 4. * time_info.timeSigNumerator
528 / time_info.timeSigDenominator
529 : 0.;
530 if(bar > 0.)
531 time_info.barStartPos
532 += std::floor((time_info.ppqPos - time_info.barStartPos) / bar) * bar;
533 }
534 }
535 }
536 m_processThread.store(std::thread::id{}, std::memory_order_relaxed);
537 }
538
539 void processFloat(int64_t offset, int64_t samples)
540 {
541 forEachBlock(offset, samples, [this](int64_t o, int64_t n) { processFloatBlock(o, n); });
542 }
543
544 void processDouble(int64_t offset, int64_t samples)
545 {
546 forEachBlock(offset, samples, [this](int64_t o, int64_t n) { processDoubleBlock(o, n); });
547 }
548
549 void processFloatBlock(int64_t offset, int64_t samples)
550 {
551 if constexpr(!UseDouble)
552 {
553 if(samples <= 0)
554 return;
555
556 const auto max_i = std::max(2, this->fx->fx->numInputs);
557 const auto max_o = std::max(2, this->fx->fx->numOutputs);
558 const auto max_io = std::max(max_i, max_o);
559
560 const auto max_samples = std::max(samples, (int64_t)m_bs); // * 16;
561
562 //qDebug() << samples << m_bs << max_samples << offset << " ::: " << max_i << max_o << max_io;
563
564 // prepare ossia::graph_node buffers
565 auto& ip = prepareInput(offset, samples);
566 SCORE_ASSERT(ip.size() >= 2);
567
568 auto& op = prepareOutput(offset, samples);
569 SCORE_ASSERT(op.size() >= 2);
570
571 // copy io
572 float_v[0].resize(max_samples);
573 float_v[1].resize(max_samples);
574
575 std::copy_n(ip[0].data() + offset, samples, float_v[0].data());
576 std::copy_n(ip[1].data() + offset, samples, float_v[1].data());
577
578 float** io = (float**)alloca(sizeof(float*) * max_io);
579 io[0] = float_v[0].data();
580 io[1] = float_v[1].data();
581
582 if(max_io > 2)
583 {
584 // Note that alloca has *function* scope, not block scope so it is
585 // freed at the end
586 float* dummy = (float*)alloca(sizeof(float) * max_samples);
587 std::fill_n(dummy, max_samples, 0.f);
588
589 for(int i = 2; i < max_io; i++)
590 io[i] = dummy;
591 }
592
593 fx->fx->processReplacing(fx->fx, io, io, samples);
594
595 std::copy_n(float_v[0].data(), samples, op[0].data() + offset);
596 std::copy_n(float_v[1].data(), samples, op[1].data() + offset);
597
598 float_v[0].clear();
599 float_v[1].clear();
600 }
601 }
602
603 void processDoubleBlock(int64_t offset, int64_t samples)
604 {
605 if constexpr(UseDouble)
606 {
607 if(samples <= 0)
608 return;
609
610 const auto max_i = std::max(2, this->fx->fx->numInputs);
611 const auto max_o = std::max(2, this->fx->fx->numOutputs);
612 const auto max_io = std::max(max_i, max_o);
613
614 // copy audio data
615 auto& ip = prepareInput(offset, samples);
616 SCORE_ASSERT(ip.size() >= 2);
617
618 auto& op = prepareOutput(offset, samples);
619 SCORE_ASSERT(op.size() >= 2);
620
621 double** input = (double**)alloca(sizeof(double*) * max_i);
622 input[0] = ip[0].data() + offset;
623 input[1] = ip[1].data() + offset;
624
625 double** output = (double**)alloca(sizeof(double*) * max_o);
626 output[0] = op[0].data() + offset;
627 output[1] = op[1].data() + offset;
628
629 if(max_io > 2)
630 {
631 // Note that alloca has *function* scope, not block scope so it is
632 // freed at the end
633 double* dummy = (double*)alloca(sizeof(double) * m_bs);
634 std::fill_n(dummy, m_bs, 0.);
635 for(int i = 2; i < this->fx->fx->numInputs; i++)
636 input[i] = dummy;
637 for(int i = 2; i < this->fx->fx->numOutputs; i++)
638 output[i] = dummy;
639 }
640
641 fx->fx->processDoubleReplacing(fx->fx, input, output, samples);
642 }
643 }
644
645 struct dummy_t
646 {
647 };
648 std::conditional_t<!UseDouble, std::array<ossia::float_vector, 2>, dummy_t> float_v;
649
652 ossia::small_vector<libremidi::ump, 8> m_deferred;
653
656 Process::MidiSysexArena::stream m_sysexStream;
657 Process::MidiSysexArena m_sysexArena;
658 std::array<VstMidiSysexEvent, 128> m_sysexEvents{};
659};
660
661template <bool b1, bool b2, typename... Args>
662auto make_vst_fx(Args&... args)
663{
664 return ossia::make_node<vst_node<b1, b2>>(args...);
665}
666}
Definition MidiSysexArena.hpp:30
Definition score-plugin-vst/Vst/Node.hpp:24
std::atomic< std::thread::id > m_processThread
Definition score-plugin-vst/Vst/Node.hpp:119
bool m_backwards
The tick being processed plays the timeline backwards.
Definition score-plugin-vst/Vst/Node.hpp:179
ossia::midi_outlet * midi_out
The MIDI output of the process, when it has one.
Definition score-plugin-vst/Vst/Node.hpp:55
void receiveEvents(const VstEvents *events) noexcept
Definition score-plugin-vst/Vst/Node.hpp:60
Definition score-plugin-vst/Vst/Node.hpp:209
void forEachBlock(int64_t offset, int64_t samples, F &&f)
Definition score-plugin-vst/Vst/Node.hpp:502
Process::MidiSysexArena::stream m_sysexStream
Definition score-plugin-vst/Vst/Node.hpp:656
ossia::small_vector< libremidi::ump, 8 > m_deferred
Definition score-plugin-vst/Vst/Node.hpp:652
void dispatchMidi(int64_t offset, int64_t samples, bool first, bool last, Fun &&f)
Definition score-plugin-vst/Vst/Node.hpp:383
void stashMidi()
Definition score-plugin-vst/Vst/Node.hpp:367
Definition score-plugin-vst/Vst/Node.hpp:646
Definition score-plugin-vst/Vst/Node.hpp:38