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