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 static const constexpr double ppq_reference = 960.;
103
104 auto& time_info = fx->info;
105 time_info.samplePos = this->m_processed_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 { return ""; }
179
180 void all_notes_off() noexcept override
181 {
182 if constexpr(IsSynth)
183 {
184 // copy midi data
185 // should be 32 but some VSTs read a bit out of bounds apparently ! so we allocate a bit more memory
186 constexpr auto sz = sizeof(VstEvents) + sizeof(void*) * 32 * 2;
187 VstEvents* events = (VstEvents*)alloca(sz);
188 std::memset(events, 0, sz);
189
190 events->numEvents = 32;
191
192 VstMidiEvent ev[64] = {};
193 memset(&ev, 0, sizeof(ev));
194
195 // All notes off
196 for(int i = 0; i < 16; i++)
197 {
198 auto& e = ev[i];
199 e.type = kVstMidiType;
200 e.flags = kVstMidiEventIsRealtime;
201 e.byteSize = sizeof(VstMidiEvent);
202
203 e.midiData[0] = (char)(uint8_t)176 + i;
204 e.midiData[1] = (char)(uint8_t)123;
205 e.midiData[2] = 0;
206 e.midiData[3] = 0;
207
208 events->events[i] = reinterpret_cast<VstEvent*>(&e);
209 }
210
211 // All sound off
212 for(int i = 0; i < 16; i++)
213 {
214 auto& e = ev[16 + i];
215 e.type = kVstMidiType;
216 e.flags = kVstMidiEventIsRealtime;
217 e.byteSize = sizeof(VstMidiEvent);
218
219 e.midiData[0] = (char)(uint8_t)176 + i;
220 e.midiData[1] = (char)(uint8_t)121;
221 e.midiData[2] = 0;
222 e.midiData[3] = 0;
223
224 events->events[16 + i] = reinterpret_cast<VstEvent*>(&e);
225 }
226
227 dispatch(effProcessEvents, 0, 0, events, 0.f);
228
229 if constexpr(!UseDouble)
230 {
231 float* dummy = (float*)alloca(sizeof(float) * m_bs);
232 std::fill_n(dummy, m_bs, 0.f);
233
234 float** input
235 = (float**)alloca(sizeof(float*) * std::max(2, this->fx->fx->numInputs));
236 for(int i = 0; i < this->fx->fx->numInputs; i++)
237 input[i] = dummy;
238
239 float** output
240 = (float**)alloca(sizeof(float*) * std::max(2, this->fx->fx->numOutputs));
241 for(int i = 0; i < this->fx->fx->numOutputs; i++)
242 output[i] = dummy;
243
244 fx->fx->processReplacing(fx->fx, input, output, m_bs);
245 }
246 else
247 {
248 double* dummy = (double*)alloca(sizeof(double) * m_bs);
249 std::fill_n(dummy, m_bs, 0.);
250
251 double** input
252 = (double**)alloca(sizeof(double*) * std::max(2, this->fx->fx->numInputs));
253 for(int i = 0; i < this->fx->fx->numInputs; i++)
254 input[i] = dummy;
255
256 double** output
257 = (double**)alloca(sizeof(double*) * std::max(2, this->fx->fx->numOutputs));
258 for(int i = 0; i < this->fx->fx->numOutputs; i++)
259 output[i] = dummy;
260
261 fx->fx->processDoubleReplacing(fx->fx, input, output, m_bs);
262 }
263 }
264 }
265
266 // Note: the function that does the actual tick is passed as an argument
267 // since some plug-ins only store pointers to VstEvents struct,
268 // which would go out of scope if this function was just called like this.
269 template <typename Fun>
270 void dispatchMidi(int64_t offset, Fun&& f)
271 {
272 // copy midi data
273 auto& ip = static_cast<ossia::midi_inlet*>(m_inlets[1])->data.messages;
274 const auto n_mess = ip.size();
275 if(n_mess == 0)
276 {
277 f();
278 return;
279 }
280
281 // -2 since two are already available ?
282 const auto sz = sizeof(VstEvents) + sizeof(void*) * n_mess * 2;
283 VstEvents* events = (VstEvents*)alloca(sz);
284 std::memset(events, 0, sz);
285 events->numEvents = n_mess;
286
287 ossia::small_vector<VstMidiEvent, 16> vec;
288 vec.resize(n_mess);
289 std::size_t i = 0;
290 for(libremidi::ump& mess : ip)
291 {
292 if(auto type = mess.get_type();
293 (type == libremidi::midi2::message_type::SYSEX7)
294 || (type == libremidi::midi2::message_type::SYSEX8_MDS))
295 continue;
296
297 VstMidiEvent& e = vec[i];
298 std::memset(&e, 0, sizeof(VstMidiEvent));
299 e.type = kVstMidiType;
300 e.byteSize = sizeof(VstMidiEvent);
301 e.deltaFrames = mess.timestamp - offset;
302 e.flags = kVstMidiEventIsRealtime;
303
304 if(auto n = cmidi2_convert_single_ump_to_midi1((uint8_t*)e.midiData, 4, mess.data);
305 n > 0)
306 {
307 events->events[i] = reinterpret_cast<VstEvent*>(&e);
308 i++;
309 }
310 else
311 {
312 events->numEvents--;
313 }
314 }
315 dispatch(effProcessEvents, 0, 0, events, 0.f);
316 f();
317 }
318
319 void run(const ossia::token_request& tk, ossia::exec_state_facade st) noexcept override
320 {
321 if(!muted() && tk.date > tk.prev_date)
322 {
323 const auto timings = st.timings(tk);
324 this->setControls();
325 this->setupTimeInfo(tk, st);
326
327 if constexpr(UseDouble)
328 {
329 if constexpr(IsSynth)
330 {
331 dispatchMidi(timings.start_sample, [this, timings] {
332 processDouble(timings.start_sample, timings.length);
333 });
334 }
335 else
336 {
337 processDouble(timings.start_sample, timings.length);
338 }
339 }
340 else
341 {
342 if constexpr(IsSynth)
343 {
344 dispatchMidi(timings.start_sample, [this, timings] {
345 processFloat(timings.start_sample, timings.length);
346 });
347 }
348 else
349 {
350 processFloat(timings.start_sample, timings.length);
351 }
352 }
353
354 // upmix mono VSTs to stereo
355 if(this->fx->fx->numOutputs == 1)
356 {
357 auto& op = m_outlets[0]->template target<ossia::audio_port>()->get();
358 op[1].assign(op[0].begin(), op[0].end());
359 }
360 }
361 }
362
363 void processFloat(int64_t offset, int64_t samples)
364 {
365 if constexpr(!UseDouble)
366 {
367 if(samples <= 0)
368 return;
369
370 SCORE_ASSERT(m_bs >= offset + samples);
371
372 const auto max_i = std::max(2, this->fx->fx->numInputs);
373 const auto max_o = std::max(2, this->fx->fx->numOutputs);
374 const auto max_io = std::max(max_i, max_o);
375
376 const auto max_samples = std::max(samples, (int64_t)m_bs); // * 16;
377
378 //qDebug() << samples << m_bs << max_samples << offset << " ::: " << max_i << max_o << max_io;
379
380 // prepare ossia::graph_node buffers
381 auto& ip = prepareInput(offset, samples);
382 SCORE_ASSERT(ip.size() >= 2);
383
384 auto& op = prepareOutput(offset, samples);
385 SCORE_ASSERT(op.size() >= 2);
386
387 // copy io
388 float_v[0].resize(max_samples);
389 float_v[1].resize(max_samples);
390
391 std::copy_n(ip[0].data() + offset, samples, float_v[0].data());
392 std::copy_n(ip[1].data() + offset, samples, float_v[1].data());
393
394 float** io = (float**)alloca(sizeof(float*) * max_io);
395 io[0] = float_v[0].data();
396 io[1] = float_v[1].data();
397
398 if(max_io > 2)
399 {
400 // Note that alloca has *function* scope, not block scope so it is
401 // freed at the end
402 float* dummy = (float*)alloca(sizeof(float) * max_samples);
403 std::fill_n(dummy, max_samples, 0.f);
404
405 for(int i = 2; i < max_io; i++)
406 io[i] = dummy;
407 }
408
409 fx->fx->processReplacing(fx->fx, io, io, samples);
410
411 std::copy_n(float_v[0].data(), samples, op[0].data() + offset);
412 std::copy_n(float_v[1].data(), samples, op[1].data() + offset);
413
414 float_v[0].clear();
415 float_v[1].clear();
416 }
417 }
418
419 void processDouble(int64_t offset, int64_t samples)
420 {
421 if constexpr(UseDouble)
422 {
423 if(samples <= 0)
424 return;
425
426 SCORE_ASSERT(m_bs >= offset + samples);
427
428 const auto max_i = std::max(2, this->fx->fx->numInputs);
429 const auto max_o = std::max(2, this->fx->fx->numOutputs);
430 const auto max_io = std::max(max_i, max_o);
431
432 // copy audio data
433 auto& ip = prepareInput(offset, samples);
434 SCORE_ASSERT(ip.size() >= 2);
435
436 auto& op = prepareOutput(offset, samples);
437 SCORE_ASSERT(op.size() >= 2);
438
439 double** input = (double**)alloca(sizeof(double*) * max_i);
440 input[0] = ip[0].data() + offset;
441 input[1] = ip[1].data() + offset;
442
443 double** output = (double**)alloca(sizeof(double*) * max_o);
444 output[0] = op[0].data() + offset;
445 output[1] = op[1].data() + offset;
446
447 if(max_io > 2)
448 {
449 // Note that alloca has *function* scope, not block scope so it is
450 // freed at the end
451 double* dummy = (double*)alloca(sizeof(double) * m_bs);
452 std::fill_n(dummy, m_bs, 0.);
453 for(int i = 2; i < this->fx->fx->numInputs; i++)
454 input[i] = dummy;
455 for(int i = 2; i < this->fx->fx->numOutputs; i++)
456 output[i] = dummy;
457 }
458
459 fx->fx->processDoubleReplacing(fx->fx, input, output, samples);
460 }
461 }
462
463 struct dummy_t
464 {
465 };
466 std::conditional_t<!UseDouble, std::array<ossia::float_vector, 2>, dummy_t> float_v;
467};
468
469template <bool b1, bool b2, typename... Args>
470auto make_vst_fx(Args&... args)
471{
472 return ossia::make_node<vst_node<b1, b2>>(args...);
473}
474}
Definition score-plugin-vst/Vst/Node.hpp:17
Definition score-plugin-vst/Vst/Node.hpp:126
Definition score-plugin-vst/Vst/Node.hpp:464
Definition score-plugin-vst/Vst/Node.hpp:31