Loading...
Searching...
No Matches
score-plugin-vst3/Vst3/Node.hpp
1#pragma once
2#include <Process/Dataflow/TimeSignature.hpp>
3
4#include <Vst3/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/logger.hpp>
10#include <ossia/detail/math.hpp>
11#include <ossia/detail/pod_vector.hpp>
12#include <ossia/detail/ssize.hpp>
13#include <ossia/editor/scenario/time_signature.hpp>
14
15#include <libremidi/ump_events.hpp>
16#include <pluginterfaces/vst/ivstmidicontrollers.h>
17
18#include <public.sdk/source/vst/hosting/eventlist.h>
19#include <public.sdk/source/vst/hosting/parameterchanges.h>
20
21#include <algorithm>
22namespace vst3
23{
24
25class param_queue final : public Steinberg::Vst::IParamValueQueue
26{
27public:
28 explicit param_queue(Steinberg::Vst::ParamID id)
29 : id{id}
30 {
31 }
32 ~param_queue() { }
33
34 Steinberg::Vst::ParamID id{};
35 ossia::small_vector<std::pair<int32_t, Steinberg::Vst::ParamValue>, 1> data;
36 Steinberg::Vst::ParamValue lastValue{};
37
38 Steinberg::tresult PLUGIN_API queryInterface(const Steinberg::TUID _iid, void** obj) override
39 {
40 return Steinberg::kResultOk;
41 }
42 Steinberg::uint32 PLUGIN_API addRef() override { return 1; }
43 Steinberg::uint32 PLUGIN_API release() override { return 1; }
44
45 Steinberg::Vst::ParamID PLUGIN_API getParameterId() override { return id; }
46 Steinberg::int32 PLUGIN_API getPointCount() override { return data.size(); }
47 Steinberg::tresult PLUGIN_API getPoint(
48 Steinberg::int32 index, Steinberg::int32& sampleOffset,
49 Steinberg::Vst::ParamValue& value) override
50 {
51 if(ossia::valid_index(index, data))
52 std::tie(sampleOffset, value) = data[index];
53 else if(index == -1)
54 {
55 sampleOffset = 0;
56 value = lastValue;
57 }
58
59 return Steinberg::kResultOk;
60 }
61
62 Steinberg::tresult PLUGIN_API addPoint(
63 Steinberg::int32 sampleOffset, Steinberg::Vst::ParamValue value,
64 Steinberg::int32& index) override
65 {
66 index = data.size();
67 data.emplace_back(sampleOffset, value);
68 return Steinberg::kResultOk;
69 }
70};
71
72class param_changes final : public Steinberg::Vst::IParameterChanges
73{
74public:
75 std::vector<param_queue> queues;
76 Steinberg::tresult PLUGIN_API queryInterface(const Steinberg::TUID _iid, void** obj) override
77 {
78 return Steinberg::kResultOk;
79 }
80 Steinberg::uint32 PLUGIN_API addRef() override { return 1; }
81 Steinberg::uint32 PLUGIN_API release() override { return 1; }
82
83 Steinberg::int32 PLUGIN_API getParameterCount() override { return queues.size(); }
84
85 param_queue* PLUGIN_API getParameterData(Steinberg::int32 index) override
86 {
87 return &queues[index];
88 }
89
90 param_queue* PLUGIN_API addParameterData(
91 const Steinberg::Vst::ParamID& id, Steinberg::int32& index /*out*/) override
92 {
93 index = queues.size();
94 queues.emplace_back(id);
95 return &queues.back();
96 }
97};
98
99class vst_node_base : public ossia::graph_node
100{
101public:
103 {
104 explicit PluginHandle(const Plugin& p)
105 : component{p.component}
106 , processor{p.processor}
107 , midi_controls{p.midiControls}
108 {
109 component->addRef();
110 processor->addRef();
111 }
112
114 {
115 processor->release();
116 component->release();
117 }
118
119 PluginHandle(const PluginHandle&) = delete;
120 PluginHandle(PluginHandle&&) = delete;
121 PluginHandle& operator=(const PluginHandle&) = delete;
122 PluginHandle& operator=(PluginHandle&&) = delete;
123
124 Steinberg::Vst::IComponent* component{};
125 Steinberg::Vst::IAudioProcessor* processor{};
126 MIDIControls midi_controls;
127 };
128
129 PluginHandle fx;
130 // Each element is the amount of channels in a given in/out port
131 ossia::small_pod_vector<int, 2> m_audioInputChannels{};
132 ossia::small_pod_vector<int, 2> m_audioOutputChannels{};
133 int m_totalAudioIns{};
134 int m_totalAudioOuts{};
135 int m_totalEventIns{};
136 int m_totalEventOuts{};
137
138protected:
139 explicit vst_node_base(const Plugin& ptr)
140 : fx{std::move(ptr)}
141 {
142 this->set_not_fp_safe();
143 m_inlets.reserve(10);
144 controls.reserve(10);
145
146 struct vis
147 {
148 vst_node_base& self;
149 void audioIn(const Steinberg::Vst::BusInfo& bus, int idx)
150 {
151 self.m_inlets.push_back(new ossia::audio_inlet);
152 self.m_audioInputChannels.push_back(bus.channelCount);
153 self.m_totalAudioIns += bus.channelCount;
154 }
155 void eventIn(const Steinberg::Vst::BusInfo& bus, int idx)
156 {
157 self.m_inlets.push_back(new ossia::midi_inlet);
158 self.m_totalEventIns++;
159 }
160 void audioOut(const Steinberg::Vst::BusInfo& bus, int idx)
161 {
162 self.m_outlets.push_back(new ossia::audio_outlet);
163 self.m_audioOutputChannels.push_back(bus.channelCount);
164 self.m_totalAudioOuts += bus.channelCount;
165 }
166 void eventOut(const Steinberg::Vst::BusInfo& bus, int idx)
167 {
168 self.m_outlets.push_back(new ossia::midi_outlet);
169 self.m_totalEventOuts++;
170 }
171 };
172
173 forEachBus(vis{*this}, *fx.component);
174
175 if(auto err = fx.processor->setProcessing(true);
176 err != Steinberg::kResultOk && err != Steinberg::kNotImplemented)
177 {
178 ossia::logger().warn("Couldn't set VST3 processing: {}", err);
179 }
180
181 m_vstData.processMode = Steinberg::Vst::ProcessModes::kRealtime;
182 m_vstData.numInputs = m_audioInputChannels.size();
183 m_vstData.numOutputs = m_audioOutputChannels.size();
184 m_vstInput.resize(m_audioInputChannels.size());
185 m_vstOutput.resize(m_audioOutputChannels.size());
186 for(std::size_t i = 0; i < m_audioInputChannels.size(); i++)
187 {
188 m_vstInput[i].numChannels = m_audioInputChannels[i];
189 }
190 for(std::size_t i = 0; i < m_audioOutputChannels.size(); i++)
191 {
192 m_vstOutput[i].numChannels = m_audioOutputChannels[i];
193 }
194
195 m_vstData.inputs = m_vstInput.data();
196 m_vstData.outputs = m_vstOutput.data();
197 m_vstData.inputParameterChanges = &m_inputChanges;
198 m_vstData.outputParameterChanges = &m_outputChanges;
199 m_vstData.inputEvents = &m_inputEvents;
200 m_vstData.outputEvents = &m_outputEvents;
201 m_vstData.processContext = &m_context;
202
203 m_inputEvents.setMaxSize(17 * 128 * m_totalEventIns);
204 m_outputEvents.setMaxSize(128 * m_totalEventOuts);
205
206 // Give every MIDI-mapped parameter a queue up front.
207 //
208 // VST3 delivers CC, pitch bend and aftertouch only as parameter changes,
209 // and the parameters a plug-in publishes through IMidiMapping are as a rule
210 // not the ones exposed as score controls - a JUCE plug-in such as Surge XT
211 // answers getMidiControllerAssignment with ids that are not in its
212 // parameter list at all. Queues were only ever created by add_control, for
213 // exposed controls, so these ids had nowhere to be written and every
214 // controller was dropped at the last step even once it had been decoded.
215 //
216 // Done here rather than on demand because adding a queue allocates, and
217 // dispatchMidi runs on the audio thread.
218 for(const auto& [key, pid] : fx.midi_controls)
219 {
220 if(queue_map.find(pid) != queue_map.end())
221 continue;
222 const auto idx = m_inputChanges.queues.size();
223 m_inputChanges.queues.emplace_back(pid);
224 queue_map[pid] = idx;
225 m_midi_only_queues.push_back(idx);
226 }
227 }
228
229 ~vst_node_base()
230 {
231 if(auto err = fx.processor->setProcessing(false);
232 err != Steinberg::kResultOk && err != Steinberg::kNotImplemented)
233 {
234 ossia::logger().warn("Couldn't set VST3 processing: {}", err);
235 }
236 }
237
239 {
240 Steinberg::Vst::ParamID idx{};
241 std::size_t queue_idx{};
242 ossia::value_port* port{};
243 };
244
245 ossia::hash_map<Steinberg::Vst::ParamID, std::size_t> queue_map;
246
250 ossia::small_vector<std::size_t, 8> m_midi_only_queues;
251
252public:
253 ossia::small_vector<vst_control, 16> controls;
254
255 std::size_t add_control(ossia::value_inlet* inlet, Steinberg::Vst::ParamID id, float v)
256 {
257 (**inlet).domain = ossia::domain_base<float>{0.f, 1.f};
258 (**inlet).type = ossia::val_type::FLOAT;
259
260 // The parameter may already own a queue because it is MIDI-mapped too.
261 // Reuse it, so a controller and the automation port for the same id do not
262 // write to two queues the plug-in then sees as conflicting. Ownership of
263 // the per-block clearing passes to setControls().
264 if(auto it = queue_map.find(id); it != queue_map.end())
265 {
266 const auto queue_idx = it->second;
267 m_midi_only_queues.erase(
268 std::remove(m_midi_only_queues.begin(), m_midi_only_queues.end(), queue_idx),
269 m_midi_only_queues.end());
270 this->m_inputChanges.queues[queue_idx].lastValue = v;
271 controls.push_back({id, queue_idx, inlet->target<ossia::value_port>()});
272 root_inputs().push_back(std::move(inlet));
273 return queue_idx;
274 }
275
276 // FIXME this allocates a lot :[
277 auto queue_idx = this->m_inputChanges.queues.size();
278 this->m_inputChanges.queues.emplace_back(id);
279 this->m_inputChanges.queues.back().lastValue = v;
280
281 queue_map[id] = queue_idx;
282 controls.push_back({id, queue_idx, inlet->target<ossia::value_port>()});
283 root_inputs().push_back(std::move(inlet));
284 return queue_idx;
285 }
286
287 // Used when a control is changed from the ui.
288 void set_control(std::size_t queue_idx, float value)
289 {
290 auto& queue = this->m_inputChanges.queues[queue_idx];
291 queue.lastValue = value;
292 queue.data.clear();
293 queue.data.emplace_back(0, value);
294 }
295
296 void setControls()
297 {
298 for(vst_control& p : controls)
299 {
300 const auto& vec = p.port->get_data();
301 if(vec.empty())
302 continue;
303 if(auto t = last(vec).target<float>())
304 {
305 double value = ossia::clamp<double>((double)*t, 0., 1.);
306 auto& queue = m_inputChanges.queues[p.queue_idx];
307 queue.data.clear();
308 queue.data.emplace_back(0, value);
309 queue.lastValue = value;
310 }
311 }
312 }
313
314 void dispatchMidi(int64_t tick_start, int64_t samples)
315 {
316 m_inputEvents.clear();
317 m_outputEvents.clear();
318
319 for(auto idx : m_midi_only_queues)
320 m_inputChanges.queues[idx].data.clear();
321
322 int k = 0;
323 int audioBusCount = std::ssize(m_audioInputChannels);
324 for(int i = audioBusCount; i < audioBusCount + m_totalEventIns; i++)
325 {
326 dispatchMidi(
327 *m_inlets[i]->template target<ossia::midi_port>(), k++, tick_start, samples);
328 }
329 m_deferred.clear();
330 m_vstData.inputEvents
331 = (m_inputEvents.getEventCount() > 0) ? &m_inputEvents : nullptr;
332 m_vstData.outputEvents = &m_outputEvents;
333 }
334
340 {
341 const int audioBusCount = std::ssize(m_audioInputChannels);
342 int k = 0;
343 for(int i = audioBusCount; i < audioBusCount + m_totalEventIns; i++, k++)
344 {
345 for(const libremidi::ump& mess :
346 m_inlets[i]->template target<ossia::midi_port>()->messages)
347 {
348 if(m_deferred.size() >= 1024)
349 return;
350 m_deferred.push_back({k, mess});
351 }
352 }
353 }
354
355 void dispatchMidi(ossia::midi_port& port, int index, int64_t tick_start, int64_t samples)
356 {
357 // copy midi data
358 auto& ip = port.messages;
359
360 // Port timestamps are relative to the buffer; the plug-in only sees
361 // [tick_start; tick_start + samples[ of it and indexes from 0. Anything
362 // held back from an empty tick goes first, at offset 0.
363 ossia::small_vector<std::pair<const libremidi::ump*, int64_t>, 16> to_send;
364 for(const auto& [bus, mess] : m_deferred)
365 if(bus == index)
366 to_send.push_back({&mess, 0});
367 for(const libremidi::ump& mess : ip)
368 to_send.push_back(
369 {&mess, std::clamp<int64_t>(
370 mess.timestamp - tick_start, 0, samples > 0 ? samples - 1 : 0)});
371
372 if(to_send.empty())
373 return;
374
375 using VstEvent = Steinberg::Vst::Event;
376
377 // VST3 has no MIDI CC event: controllers reach the plug-in only as
378 // parameter changes, for whichever controllers it published through
379 // IMidiMapping (collected into fx.midi_controls). Everything that is not a
380 // note goes through here.
381 const auto push_midi_control = [this, index](
382 int channel, int controller, double value,
383 Steinberg::int32 offset) {
384 auto it = this->fx.midi_controls.find({index, channel, controller});
385 if(it == this->fx.midi_controls.end())
386 {
387 // A plug-in that maps a controller globally answers for channel 0
388 // only; fall back to it so single-channel plug-ins keep working when
389 // the sender uses another channel.
390 it = this->fx.midi_controls.find({index, 0, controller});
391 if(it == this->fx.midi_controls.end())
392 return;
393 }
394 const auto queue_it = this->queue_map.find(it->second);
395 if(queue_it == this->queue_map.end())
396 return;
397 auto& queue = this->m_inputChanges.queues[queue_it->second];
398 queue.data.push_back({offset, value});
399 queue.lastValue = value;
400 };
401
402 VstEvent e;
403 e.busIndex = index;
404 e.sampleOffset = 0;
405 e.ppqPosition = 0; // FIXME
406 for(const auto& [mess_ptr, sample_offset] : to_send)
407 {
408 const libremidi::ump& mess = *mess_ptr;
409 if(mess.get_type() != libremidi::midi2::message_type::MIDI_2_CHANNEL)
410 continue;
411
412 e.sampleOffset = sample_offset;
413 switch(libremidi::message_type(mess.get_status_code()))
414 {
415 case libremidi::message_type::NOTE_ON: {
416 auto [channel, note, value] = libremidi::as_01::note_off(mess);
417
418 if(value > 0)
419 {
420 e.type = VstEvent::kNoteOnEvent;
421 e.noteOn.channel = channel; // FIXME 0 or 1-based?
422 e.noteOn.pitch = note;
423 e.noteOn.velocity = value;
424 e.noteOn.noteId = -1;
425 e.noteOn.tuning = 0.f;
426 m_inputEvents.addEvent(e);
427 }
428 else
429 {
430 e.type = VstEvent::kNoteOffEvent;
431 e.noteOff.channel = mess.get_channel();
432 e.noteOff.pitch = note;
433 e.noteOff.velocity = 0;
434 e.noteOff.noteId = -1;
435 e.noteOff.tuning = 0.f;
436 m_inputEvents.addEvent(e);
437 }
438 break;
439 }
440 case libremidi::message_type::NOTE_OFF: {
441 auto [channel, note, value] = libremidi::as_01::note_off(mess);
442 e.type = VstEvent::kNoteOffEvent;
443 e.noteOff.channel = channel;
444 e.noteOff.pitch = note;
445 e.noteOff.velocity = value;
446 e.noteOff.noteId = -1;
447 e.noteOff.tuning = 0.f;
448 m_inputEvents.addEvent(e);
449 break;
450 }
451 case libremidi::message_type::POLY_PRESSURE: {
452 auto [channel, note, value] = libremidi::as_01::poly_pressure(mess);
453 e.type = VstEvent::kPolyPressureEvent;
454 e.polyPressure.channel = channel;
455 e.polyPressure.pitch = note;
456 e.polyPressure.pressure = value;
457 e.polyPressure.noteId = -1;
458 m_inputEvents.addEvent(e);
459 break;
460 }
461
462 // Control changes were never handled at all, which is what kept every
463 // CC out of the plug-ins.
464 case libremidi::message_type::CONTROL_CHANGE: {
465 const auto cc = libremidi::as_01::control_change(mess);
466 push_midi_control(cc.channel, cc.control, cc.value, e.sampleOffset);
467 break;
468 }
469
470 case libremidi::message_type::PITCH_BEND: {
471 const auto pb = libremidi::as_01::pitch_bend(mess);
472 push_midi_control(
473 pb.channel, Steinberg::Vst::kPitchBend, pb.value, e.sampleOffset);
474 break;
475 }
476
477 case libremidi::message_type::AFTERTOUCH: {
478 const auto at = libremidi::as_01::aftertouch(mess);
479 push_midi_control(
480 at.channel, Steinberg::Vst::kAfterTouch, at.value, e.sampleOffset);
481 break;
482 }
483 default:
484 break;
485 }
486 }
487 }
488
489 void readbackMidi(int64_t tick_start)
490 {
491 using VstEvent = Steinberg::Vst::Event;
492
493 const int audioBusCount = std::ssize(m_audioOutputChannels);
494 const int N = m_outputEvents.getEventCount();
495
496 for(int i = 0; i < N; i++)
497 {
498 auto event_p = m_outputEvents.getEventByIndex(i);
499 if(!event_p)
500 continue;
501 VstEvent& e = *event_p;
502
503 int bus = e.busIndex;
504 auto& port = *m_outlets[bus + audioBusCount]->template target<ossia::midi_port>();
505
506 libremidi::ump mess;
507
508 switch(e.type)
509 {
510 case VstEvent::kNoteOnEvent: {
511 if(e.noteOn.velocity > 0.f)
512 mess = libremidi::from_01::note_on(
513 e.noteOn.channel, e.noteOn.pitch, e.noteOn.velocity);
514 else
515 mess = libremidi::from_01::note_off(e.noteOn.channel, e.noteOn.pitch, 0.);
516 break;
517 }
518 case VstEvent::kNoteOffEvent: {
519 mess = libremidi::from_01::note_off(
520 e.noteOff.channel, e.noteOff.pitch, e.noteOff.velocity);
521 break;
522 }
523 case VstEvent::kPolyPressureEvent: {
524 mess = libremidi::from_01::poly_pressure(
525 e.noteOff.channel, e.polyPressure.pitch, e.polyPressure.pressure);
526 break;
527 }
528 default:
529 break;
530 }
531
532 mess.timestamp = tick_start + e.sampleOffset;
533 port.messages.push_back(std::move(mess));
534 }
535 }
536
537 auto& preparePort(ossia::audio_port& port, int numChannels, std::size_t samples)
538 {
539 port.set_channels(numChannels);
540
541 for(auto& i : port)
542 i.resize(samples);
543 return port.get();
544 }
545
546 void setupTimeInfo(const ossia::token_request& tk, ossia::exec_state_facade st)
547 {
548 using namespace Steinberg::Vst;
549 using F = ProcessContext;
550 Steinberg::Vst::ProcessContext& time_info = this->m_context;
551 time_info.sampleRate = st.sampleRate();
552
553 time_info.projectTimeSamples = this->m_transport_frames;
554
555 time_info.systemTime = st.currentDate() - st.startDate();
556 time_info.continousTimeSamples = this->m_processed_frames; // TODO
557
558 time_info.projectTimeMusic = tk.musical_start_position;
559 time_info.barPositionMusic = tk.musical_start_last_bar;
560 time_info.cycleStartMusic = 0.;
561 time_info.cycleEndMusic = 0.;
562
563 time_info.tempo = tk.tempo;
564 time_info.timeSigNumerator = tk.signature.upper;
565 time_info.timeSigDenominator = tk.signature.lower;
566
567 // time_info.chord = ....;
568
569 time_info.smpteOffsetSubframes = 0;
570 time_info.frameRate = {};
571 time_info.samplesToNextClock = 0;
572 time_info.state = F::kPlaying | F::kSystemTimeValid | F::kContTimeValid
573 | F::kProjectTimeMusicValid | F::kBarPositionValid | F::kTempoValid
574 | F::kTimeSigValid;
575 }
576
577 Steinberg::Vst::ProcessData m_vstData;
578 ossia::small_vector<Steinberg::Vst::AudioBusBuffers, 1> m_vstInput;
579 ossia::small_vector<Steinberg::Vst::AudioBusBuffers, 1> m_vstOutput;
580
581 Steinberg::Vst::ProcessContext m_context;
582 param_changes m_inputChanges;
583 param_changes m_outputChanges;
584 Steinberg::Vst::EventList m_inputEvents;
585 Steinberg::Vst::EventList m_outputEvents;
586
589 ossia::small_vector<std::pair<int, libremidi::ump>, 8> m_deferred;
590};
591
592template <bool UseDouble>
593class vst_node final : public vst_node_base
594{
595public:
596 vst_node(Plugin dat, int sampleRate)
597 : vst_node_base{std::move(dat)}
598 {
599 if constexpr(UseDouble)
600 m_vstData.symbolicSampleSize = Steinberg::Vst::kSample64;
601 else
602 m_vstData.symbolicSampleSize = Steinberg::Vst::kSample32;
603 }
604
605 ~vst_node() { }
606
607 std::string label() const noexcept override { return "VST3"; }
608
609 void all_notes_off(int bus) noexcept
610 {
611 bool ok = false;
612 // Panic goes to channel 0's mapping: it is a global request, and a
613 // per-channel sweep would write the same parameter sixteen times.
614 if(auto it
615 = this->fx.midi_controls.find({bus, 0, Steinberg::Vst::kCtrlAllNotesOff});
616 it != this->fx.midi_controls.end())
617 {
618 Steinberg::Vst::ParamID pid = it->second;
619 if(auto queue_it = this->queue_map.find(pid); queue_it != this->queue_map.end())
620 {
621 auto& queue = this->m_inputChanges.queues[queue_it->second];
622 queue.data.push_back({0, 1.});
623 queue.lastValue = 1.;
624 ok = true;
625 }
626 }
627
628 if(auto it
629 = this->fx.midi_controls.find({bus, 0, Steinberg::Vst::kCtrlAllSoundsOff});
630 it != this->fx.midi_controls.end())
631 {
632 Steinberg::Vst::ParamID pid = it->second;
633 if(auto queue_it = this->queue_map.find(pid); queue_it != this->queue_map.end())
634 {
635 auto& queue = this->m_inputChanges.queues[queue_it->second];
636 queue.data.push_back({0, 1.});
637 queue.lastValue = 1.;
638 ok = true;
639 }
640 }
641
642 if(!ok)
643 {
644 // Send manual note off events
645 for(int k = 0; k <= 16; k++)
646 for(int i = 0; i <= 127; i++)
647 {
648 using VstEvent = Steinberg::Vst::Event;
649 VstEvent e;
650 e.busIndex = bus;
651 e.sampleOffset = 0;
652 e.ppqPosition = 0; // FIXME
653 e.sampleOffset = 0;
654 e.type = VstEvent::kNoteOffEvent;
655 e.noteOff.channel = k;
656 e.noteOff.pitch = i;
657 e.noteOff.velocity = 0;
658 e.noteOff.noteId = -1;
659 e.noteOff.tuning = 0.f;
660 m_inputEvents.addEvent(e);
661 }
662 }
663 }
664
665 void all_notes_off() noexcept override
666 {
667 if(m_totalEventIns == 0)
668 return;
669
670 m_inputEvents.clear();
671
672 // Put messages into each MIDI in's event queues
673 {
674 for(int i = 0; i < m_totalEventIns; i++)
675 {
676 all_notes_off(i++);
677 }
678 }
679
680 // Run a process cycle
681 {
682 constexpr int samples = 64;
683 Steinberg::Vst::ProcessData dat;
684 memcpy(&dat, &m_vstData, sizeof(m_vstData));
685 dat.inputEvents = &m_inputEvents;
686 dat.numSamples = samples;
687
688 {
689 double** input{};
690 double** output{};
691
692 // Copy inputs
693 if(m_totalAudioIns > 0)
694 {
695 input = (double**)alloca(sizeof(double*) * m_totalAudioIns);
696
697 for(int k = 0; k < m_totalAudioIns; k++)
698 {
699 input[k] = (double*)alloca(sizeof(double) * samples);
700 memset(input[k], 0, sizeof(double) * samples);
701 }
702
703 for(std::size_t i = 0; i < m_audioInputChannels.size(); i++)
704 {
705 Steinberg::Vst::AudioBusBuffers& vst_in = dat.inputs[i];
706 vst_in.channelBuffers64 = input;
707 vst_in.silenceFlags = ~0ULL;
708 }
709 }
710
711 // Prepare outputs
712 if(m_totalAudioOuts > 0)
713 {
714 output = (double**)alloca(sizeof(double*) * m_totalAudioOuts);
715 for(int k = 0; k < m_totalAudioOuts; k++)
716 {
717 output[k] = (double*)alloca(sizeof(double) * samples);
718 memset(output[k], 0, sizeof(double) * samples);
719 }
720
721 for(std::size_t i = 0; i < m_audioOutputChannels.size(); i++)
722 {
723 Steinberg::Vst::AudioBusBuffers& vst_out = dat.outputs[i];
724 vst_out.channelBuffers64 = output;
725 vst_out.silenceFlags = ~0ULL;
726 }
727 }
728
729 fx.processor->process(dat);
730 }
731 }
732 }
733
734 void run(const ossia::token_request& tk, ossia::exec_state_facade st) noexcept override
735 {
736 if(!muted() && !tk.paused())
737 {
738 const auto [tick_start, samples] = st.timings(tk);
739 if(samples <= 0)
740 {
741 // Never call a plug-in with an empty block, but do not lose what the
742 // tick carried: it is delivered at the top of the next real one.
743 this->stashMidi();
744 return;
745 }
746
747 this->setControls();
748 this->setupTimeInfo(tk, st);
749
750 this->dispatchMidi(tick_start, samples);
751
752 if constexpr(UseDouble)
753 {
754 processDouble(tick_start, samples, st.bufferSize());
755 }
756 else
757 {
758 processFloat(tick_start, samples, st.bufferSize());
759 }
760
761 this->readbackMidi(tick_start);
762 }
763 }
764
765 void processFloat(int64_t tick_start, std::size_t samples, int bufferSize)
766 {
767 // In the float case we have temporary buffers for conversion
768 if constexpr(!UseDouble)
769 {
770 // Prepare buffers
771 if(m_totalAudioIns > 0 || m_totalAudioOuts > 0)
772 {
773 float_v.resize(std::max(m_totalAudioIns, m_totalAudioOuts));
774 for(auto& v : float_v)
775 v.resize(samples);
776
777 float** input{};
778 float** output{};
779
780 // Copy inputs
781 if(m_totalAudioIns > 0)
782 {
783 input = (float**)alloca(sizeof(float*) * m_totalAudioIns);
784 int channel_k = 0;
785 int float_k = 0;
786
787 for(std::size_t i = 0; i < m_audioInputChannels.size(); i++)
788 {
789 const int numChannels = m_audioInputChannels[i];
790 auto& port = *m_inlets[i]->template target<ossia::audio_port>();
791 auto& ip = preparePort(port, numChannels, bufferSize);
792
793 Steinberg::Vst::AudioBusBuffers& vst_in = m_vstInput[i];
794 vst_in.channelBuffers32 = input + channel_k;
795 vst_in.silenceFlags = 0;
796
797 for(int k = 0; k < numChannels; k++)
798 {
799 const std::size_t avail = ip[k].size() > std::size_t(tick_start)
800 ? ip[k].size() - std::size_t(tick_start)
801 : 0u;
802 std::copy_n(
803 ip[k].data() + tick_start, std::min(samples, avail),
804 float_v[float_k].data());
805 input[channel_k] = float_v[float_k].data();
806 channel_k++;
807 float_k++;
808 }
809 }
810 }
811
812 // Prepare outputs
813 if(m_totalAudioOuts > 0)
814 {
815 // copy audio data
816 output = (float**)alloca(sizeof(float*) * m_totalAudioOuts);
817
818 int channel_k = 0;
819 int float_k = 0;
820 for(std::size_t i = 0; i < m_audioOutputChannels.size(); i++)
821 {
822 const int numChannels = m_audioOutputChannels[i];
823 auto& port = *m_outlets[i]->template target<ossia::audio_port>();
824 preparePort(port, numChannels, bufferSize);
825
826 Steinberg::Vst::AudioBusBuffers& vst_out = m_vstOutput[i];
827 vst_out.channelBuffers32 = output + channel_k;
828 vst_out.silenceFlags = 0;
829 for(int k = 0; k < numChannels; k++)
830 {
831 output[channel_k] = float_v[float_k].data();
832 channel_k++;
833 float_k++;
834 }
835 }
836 }
837 }
838
839 // Run the process
840 {
841 m_vstData.numSamples = samples;
842
843 fx.processor->process(m_vstData);
844 }
845
846 // Copy the float outputs to the audio outlet buffer
847 if(m_totalAudioOuts > 0)
848 {
849 int float_k = 0;
850 for(std::size_t i = 0; i < m_audioOutputChannels.size(); i++)
851 {
852 const int numChannels = m_audioOutputChannels[i];
853 ossia::audio_port& port = *m_outlets[i]->template target<ossia::audio_port>();
854 for(int k = 0; k < numChannels; k++)
855 {
856 auto& audio_out = port.channel(k);
857 std::copy_n(
858 float_v[float_k].data(), samples, audio_out.data() + tick_start);
859 float_k++;
860 }
861 }
862 }
863 }
864 }
865
866 void processDouble(int64_t tick_start, std::size_t samples, int bufferSize)
867 {
868 // In the double case we use directly the buffers that are part of the
869 // input & output ports
870 if constexpr(UseDouble)
871 {
872 double** input{};
873 double** output{};
874
875 // Copy inputs
876 if(m_totalAudioIns > 0)
877 {
878 input = (double**)alloca(sizeof(double*) * m_totalAudioIns);
879
880 int channel_k = 0;
881 for(std::size_t i = 0; i < m_audioInputChannels.size(); i++)
882 {
883 const int numChannels = m_audioInputChannels[i];
884 auto& port = *m_inlets[i]->template target<ossia::audio_port>();
885 auto& ip = preparePort(port, numChannels, bufferSize);
886
887 Steinberg::Vst::AudioBusBuffers& vst_in = m_vstInput[i];
888 vst_in.channelBuffers64 = input + channel_k;
889 vst_in.silenceFlags = 0;
890 for(int k = 0; k < numChannels; k++)
891 {
892 input[channel_k++] = ip[k].data() + tick_start;
893 }
894 }
895 }
896
897 // Prepare outputs
898 if(m_totalAudioOuts > 0)
899 {
900 output = (double**)alloca(sizeof(double*) * m_totalAudioOuts);
901 int channel_k = 0;
902 for(std::size_t i = 0; i < m_audioOutputChannels.size(); i++)
903 {
904 const int numChannels = m_audioOutputChannels[i];
905 auto& port = *m_outlets[i]->template target<ossia::audio_port>();
906 auto& op = preparePort(port, numChannels, bufferSize);
907
908 Steinberg::Vst::AudioBusBuffers& vst_out = m_vstOutput[i];
909 vst_out.channelBuffers64 = output + channel_k;
910 vst_out.silenceFlags = 0;
911 for(int k = 0; k < numChannels; k++)
912 {
913 output[channel_k++] = op[k].data() + tick_start;
914 }
915 }
916 }
917
918 // Run process
919 {
920 m_vstData.numSamples = samples;
921
922 fx.processor->process(m_vstData);
923 }
924 }
925 }
926
927 struct dummy_t
928 {
929 };
930 std::conditional_t<!UseDouble, std::vector<ossia::float_vector>, dummy_t> float_v;
931};
932
933template <bool b1, typename... Args>
934auto make_vst_fx(Args&... args)
935{
936 return ossia::make_node<vst_node<b1>>(args...);
937}
938}
Definition score-plugin-vst3/Vst3/Node.hpp:73
Definition score-plugin-vst3/Vst3/Node.hpp:26
Definition score-plugin-vst3/Vst3/Node.hpp:100
void stashMidi()
Definition score-plugin-vst3/Vst3/Node.hpp:339
ossia::small_vector< std::pair< int, libremidi::ump >, 8 > m_deferred
Definition score-plugin-vst3/Vst3/Node.hpp:589
ossia::small_vector< std::size_t, 8 > m_midi_only_queues
Definition score-plugin-vst3/Vst3/Node.hpp:250
Definition score-plugin-vst3/Vst3/Node.hpp:594
STL namespace.
Definition Plugin.hpp:50
Definition score-plugin-vst3/Vst3/Node.hpp:928
Definition score-plugin-vst3/Vst3/Node.hpp:103
Definition score-plugin-vst3/Vst3/Node.hpp:239