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