iPlug2 - C++ Audio Plug-in Framework
Loading...
Searching...
No Matches
IPlugVST3_ProcessorBase.cpp
1/*
2 ==============================================================================
3
4 This file is part of the iPlug 2 library. Copyright (C) the iPlug 2 developers.
5
6 See LICENSE.txt for more info.
7
8 ==============================================================================
9 */
10
11#include "pluginterfaces/vst/ivstparameterchanges.h"
12#include "pluginterfaces/vst/vstspeaker.h"
13#include "pluginterfaces/vst/ivstmidicontrollers.h"
14#include "public.sdk/source/vst/vsteventshelper.h"
15#include "IPlugVST3_ProcessorBase.h"
16
17using namespace iplug;
18using namespace Steinberg;
19using namespace Vst;
20
21#ifndef CUSTOM_BUSTYPE_FUNC
22uint64_t iplug::GetAPIBusTypeForChannelIOConfig(int configIdx, ERoute dir, int busIdx, const IOConfig* pConfig, WDL_TypedBuf<uint64_t>* APIBusTypes)
23{
24 assert(pConfig != nullptr);
25 assert(busIdx >= 0 && busIdx < pConfig->NBuses(dir));
26
27 int numChans = pConfig->GetBusInfo(dir, busIdx)->NChans();
28
29 switch (numChans)
30 {
31 case 0: return SpeakerArr::kEmpty;
32 case 1: return SpeakerArr::kMono;
33 case 2: return SpeakerArr::kStereo;
34 case 3: return SpeakerArr::k30Cine; // CHECK - not the same as protools
35 case 4: return SpeakerArr::kAmbi1stOrderACN;
36 case 5: return SpeakerArr::k50;
37 case 6: return SpeakerArr::k51;
38 case 7: return SpeakerArr::k70Cine;
39 case 8: return SpeakerArr::k71CineSideFill; // CHECK - not the same as protools
40 case 9: return SpeakerArr::kAmbi2cdOrderACN;
41 case 10:return SpeakerArr::k71_2; // aka k91Atmos
42 case 16:return SpeakerArr::kAmbi3rdOrderACN;
43 default:
44 DBGMSG("do not yet know what to do here\n");
45 assert(0);
46 return SpeakerArr::kEmpty;
47 }
48}
49#endif
50
51IPlugVST3ProcessorBase::IPlugVST3ProcessorBase(Config c, IPlugAPIBase& plug)
52: IPlugProcessor(c, kAPIVST3)
53, mPlug(plug)
54{
55 SetChannelConnections(ERoute::kInput, 0, MaxNChannels(ERoute::kInput), true);
56 SetChannelConnections(ERoute::kOutput, 0, MaxNChannels(ERoute::kOutput), true);
57
58 mMaxNChansForMainInputBus = MaxNChannelsForBus(ERoute::kInput, 0);
59
60 InitLatencyDelay();
61
62 IPlugProcessor::SetBlockSize(DEFAULT_BLOCK_SIZE);
63
64 // Make sure the process context is predictably initialised in case it is used before process is called
65 memset(&mProcessContext, 0, sizeof(ProcessContext));
66}
67
68void IPlugVST3ProcessorBase::ProcessMidiIn(IEventList* pEventList, IPlugQueue<IMidiMsg>& editorQueue, IPlugQueue<IMidiMsg>& processorQueue)
69{
70 IMidiMsg msg;
71
72 if (pEventList)
73 {
74 int32 numEvent = pEventList->getEventCount();
75 for (int32 i=0; i<numEvent; i++)
76 {
77 Event event;
78 if (pEventList->getEvent(i, event) == kResultOk)
79 {
80 switch (event.type)
81 {
82 case Event::kNoteOnEvent:
83 {
84 msg.MakeNoteOnMsg(event.noteOn.pitch, event.noteOn.velocity * 127, event.sampleOffset, event.noteOn.channel);
85 ProcessMidiMsg(msg);
86 processorQueue.Push(msg);
87 break;
88 }
89
90 case Event::kNoteOffEvent:
91 {
92 msg.MakeNoteOffMsg(event.noteOff.pitch, event.sampleOffset, event.noteOff.channel);
93 ProcessMidiMsg(msg);
94 processorQueue.Push(msg);
95 break;
96 }
97 case Event::kPolyPressureEvent:
98 {
99 msg.MakePolyATMsg(event.polyPressure.pitch, event.polyPressure.pressure * 127., event.sampleOffset, event.polyPressure.channel);
100 ProcessMidiMsg(msg);
101 processorQueue.Push(msg);
102 break;
103 }
104 case Event::kDataEvent:
105 {
106 ISysEx syx = ISysEx(event.sampleOffset, event.data.bytes, event.data.size);
107 ProcessSysEx(syx);
108 break;
109 }
110 }
111 }
112 }
113 }
114
115 while (editorQueue.Pop(msg))
116 {
117 ProcessMidiMsg(msg);
118 }
119}
120
121void IPlugVST3ProcessorBase::ProcessMidiOut(IPlugQueue<SysExData>& sysExQueue, SysExData& sysExBuf, IEventList* pOutputEvents, int32 numSamples)
122{
123 if (!mMidiOutputQueue.Empty() && pOutputEvents)
124 {
125 Event toAdd = {0};
126 IMidiMsg msg;
127
128 while (!mMidiOutputQueue.Empty())
129 {
130 IMidiMsg& msg = mMidiOutputQueue.Peek();
131
132 if (msg.StatusMsg() == IMidiMsg::kNoteOn)
133 {
134 Helpers::init(toAdd, Event::kNoteOnEvent, 0 /*bus id*/, msg.mOffset);
135
136 toAdd.noteOn.channel = msg.Channel();
137 toAdd.noteOn.pitch = msg.NoteNumber();
138 toAdd.noteOn.tuning = 0.;
139 toAdd.noteOn.velocity = (float) msg.Velocity() * (1.f / 127.f);
140 pOutputEvents->addEvent(toAdd);
141 }
142 else if (msg.StatusMsg() == IMidiMsg::kNoteOff)
143 {
144 Helpers::init(toAdd, Event::kNoteOffEvent, 0 /*bus id*/, msg.mOffset);
145
146 toAdd.noteOff.channel = msg.Channel();
147 toAdd.noteOff.pitch = msg.NoteNumber();
148 toAdd.noteOff.velocity = (float) msg.Velocity() * (1.f / 127.f);
149 pOutputEvents->addEvent(toAdd);
150 }
151 else if (msg.StatusMsg() == IMidiMsg::kPolyAftertouch)
152 {
153 Helpers::initLegacyMIDICCOutEvent(toAdd, ControllerNumbers::kCtrlPolyPressure, msg.Channel(), msg.mData1, msg.mData2);
154 toAdd.sampleOffset = msg.mOffset;
155 pOutputEvents->addEvent(toAdd);
156 }
157 else if (msg.StatusMsg() == IMidiMsg::kChannelAftertouch)
158 {
159 Helpers::initLegacyMIDICCOutEvent(toAdd, ControllerNumbers::kAfterTouch, msg.Channel(), msg.mData1, msg.mData2);
160 toAdd.sampleOffset = msg.mOffset;
161 pOutputEvents->addEvent(toAdd);
162 }
163 else if (msg.StatusMsg() == IMidiMsg::kProgramChange)
164 {
165 Helpers::initLegacyMIDICCOutEvent(toAdd, ControllerNumbers::kCtrlProgramChange, msg.Channel(), msg.Program(), 0);
166 toAdd.sampleOffset = msg.mOffset;
167 pOutputEvents->addEvent(toAdd);
168 }
169 else if (msg.StatusMsg() == IMidiMsg::kControlChange)
170 {
171 Helpers::initLegacyMIDICCOutEvent(toAdd, msg.mData1, msg.Channel(), msg.mData2, 0 /* value2?*/);
172 toAdd.sampleOffset = msg.mOffset;
173 pOutputEvents->addEvent(toAdd);
174 }
175 else if (msg.StatusMsg() == IMidiMsg::kPitchWheel)
176 {
177 toAdd.type = Event::kLegacyMIDICCOutEvent;
178 toAdd.midiCCOut.channel = msg.Channel();
179 toAdd.sampleOffset = msg.mOffset;
180 toAdd.midiCCOut.controlNumber = ControllerNumbers::kPitchBend;
181 int16 tmp = static_cast<int16> (msg.PitchWheel() * 0x3FFF);
182 toAdd.midiCCOut.value = (tmp & 0x7F);
183 toAdd.midiCCOut.value2 = ((tmp >> 7) & 0x7F);
184 pOutputEvents->addEvent(toAdd);
185 }
186
187 mMidiOutputQueue.Remove();
188 }
189 }
190
191 mMidiOutputQueue.Flush(numSamples);
192
193 // Output SYSEX from the editor, which has bypassed the processors' ProcessSysEx()
194 if (sysExQueue.ElementsAvailable())
195 {
196 Event toAdd = {0};
197
198 while (sysExQueue.Pop(sysExBuf))
199 {
200 toAdd.type = Event::kDataEvent;
201 toAdd.sampleOffset = sysExBuf.mOffset;
202 toAdd.data.type = DataEvent::kMidiSysEx;
203 toAdd.data.size = sysExBuf.mSize;
204 toAdd.data.bytes = (uint8*) sysExBuf.mData; // TODO! this is a problem if more than one message in this block!
205 pOutputEvents->addEvent(toAdd);
206 }
207 }
208}
209
210void IPlugVST3ProcessorBase::AttachBuffers(ERoute direction, int idx, int n, AudioBusBuffers& pBus, int nFrames, int32 sampleSize)
211{
212 if (sampleSize == kSample32)
213 IPlugProcessor::AttachBuffers(direction, idx, n, pBus.channelBuffers32, nFrames);
214 else if (sampleSize == kSample64)
215 IPlugProcessor::AttachBuffers(direction, idx, n, pBus.channelBuffers64, nFrames);
216}
217
218bool IPlugVST3ProcessorBase::SetupProcessing(const ProcessSetup& setup, ProcessSetup& storedSetup)
219{
220 if ((setup.symbolicSampleSize != kSample32) && (setup.symbolicSampleSize != kSample64))
221 return false;
222
223 storedSetup = setup;
224
225 SetSampleRate(setup.sampleRate);
226 IPlugProcessor::SetBlockSize(setup.maxSamplesPerBlock);
227 mMidiOutputQueue.Resize(setup.maxSamplesPerBlock);
228 OnReset();
229
230 return true;
231}
232
233bool IPlugVST3ProcessorBase::SetProcessing(bool state)
234{
235 if (!state)
236 OnReset();
237
238 return true;
239}
240
241bool IPlugVST3ProcessorBase::CanProcessSampleSize(int32 symbolicSampleSize)
242{
243 switch (symbolicSampleSize)
244 {
245 case kSample32: // fall through
246 case kSample64: return true;
247 default: return false;
248 }
249}
250
251void IPlugVST3ProcessorBase::PrepareProcessContext(ProcessData& data, ProcessSetup& setup)
252{
253 ITimeInfo timeInfo;
254
255 if (data.processContext)
256 memcpy(&mProcessContext, data.processContext, sizeof(ProcessContext));
257
258 if (mProcessContext.state & ProcessContext::kProjectTimeMusicValid)
259 timeInfo.mSamplePos = (double) mProcessContext.projectTimeSamples;
260 timeInfo.mPPQPos = mProcessContext.projectTimeMusic;
261 timeInfo.mTempo = mProcessContext.tempo;
262 timeInfo.mLastBar = mProcessContext.barPositionMusic;
263 timeInfo.mCycleStart = mProcessContext.cycleStartMusic;
264 timeInfo.mCycleEnd = mProcessContext.cycleEndMusic;
265 timeInfo.mNumerator = mProcessContext.timeSigNumerator;
266 timeInfo.mDenominator = mProcessContext.timeSigDenominator;
267 timeInfo.mTransportIsRunning = mProcessContext.state & ProcessContext::kPlaying;
268 timeInfo.mTransportLoopEnabled = mProcessContext.state & ProcessContext::kCycleActive;
269 const bool offline = setup.processMode == Steinberg::Vst::kOffline;
270 SetTimeInfo(timeInfo);
271 SetRenderingOffline(offline);
272}
273
274void IPlugVST3ProcessorBase::ProcessParameterChanges(ProcessData& data, IPlugQueue<IMidiMsg>& fromProcessor)
275{
276 IParameterChanges* paramChanges = data.inputParameterChanges;
277
278 if (paramChanges)
279 {
280 int32 numParamsChanged = paramChanges->getParameterCount();
281
282 for (int32 i = 0; i < numParamsChanged; i++)
283 {
284 IParamValueQueue* paramQueue = paramChanges->getParameterData(i);
285 if (paramQueue)
286 {
287 int32 numPoints = paramQueue->getPointCount();
288 int32 offsetSamples;
289 double value;
290
291 if (paramQueue->getPoint(numPoints - 1, offsetSamples, value) == kResultTrue)
292 {
293 int idx = paramQueue->getParameterId();
294
295 switch (idx)
296 {
297 case kBypassParam:
298 {
299 const bool bypassed = (value > 0.5);
300
301 if (bypassed != GetBypassed())
302 SetBypassed(bypassed);
303
304 break;
305 }
306 default:
307 {
308 if (idx >= 0 && idx < mPlug.NParams())
309 {
310#ifdef PARAMS_MUTEX
311 mPlug.mParams_mutex.Enter();
312#endif
313 mPlug.GetParam(idx)->SetNormalized(value);
314
315 // In VST3 non distributed the same parameter value is also set via IPlugVST3Controller::setParamNormalized(ParamID tag, ParamValue value)
316 mPlug.OnParamChange(idx, kHost, offsetSamples);
317#ifdef PARAMS_MUTEX
318 mPlug.mParams_mutex.Leave();
319#endif
320 }
321 else if (idx >= kMIDICCParamStartIdx)
322 {
323 int index = idx - kMIDICCParamStartIdx;
324 int channel = index / kCountCtrlNumber;
325 int ctrlr = index % kCountCtrlNumber;
326
327 IMidiMsg msg;
328
329 if (ctrlr == kAfterTouch)
330 msg.MakeChannelATMsg((int) (value * 127.), offsetSamples, channel);
331 else if (ctrlr == kPitchBend)
332 msg.MakePitchWheelMsg((value * 2.)-1., channel, offsetSamples);
333 else
334 msg.MakeControlChangeMsg((IMidiMsg::EControlChangeMsg) ctrlr, value, channel, offsetSamples);
335
336 fromProcessor.Push(msg);
337 ProcessMidiMsg(msg);
338 }
339 }
340 break;
341 }
342 }
343 }
344 }
345 }
346}
347
348void IPlugVST3ProcessorBase::ProcessAudio(ProcessData& data, ProcessSetup& setup, const BusList& ins, const BusList& outs)
349{
350 int32 sampleSize = setup.symbolicSampleSize;
351
352 if (sampleSize == kSample32 || sampleSize == kSample64)
353 {
354 if (data.numInputs)
355 {
356 SetChannelConnections(ERoute::kInput, 0, MaxNChannels(ERoute::kInput), false);
357
358 if (ins.size() > 1)
359 {
360 if (ins[1].get()->isActive()) // Sidechain is active
361 {
362 mSidechainActive = true;
363 SetChannelConnections(ERoute::kInput, 0, data.inputs[0].numChannels, true);
364 SetChannelConnections(ERoute::kInput, mMaxNChansForMainInputBus, data.inputs[1].numChannels, true);
365 }
366 else
367 {
368 if (mSidechainActive)
369 {
370 ZeroScratchBuffers();
371 mSidechainActive = false;
372 }
373
374 SetChannelConnections(ERoute::kInput, 0, data.inputs[0].numChannels, true);
375 }
376
377 AttachBuffers(ERoute::kInput, 0, data.inputs[0].numChannels, data.inputs[0], data.numSamples, sampleSize);
378
379 if(mSidechainActive)
380 AttachBuffers(ERoute::kInput, mMaxNChansForMainInputBus, data.inputs[1].numChannels, data.inputs[1], data.numSamples, sampleSize);
381 }
382 else
383 {
384 SetChannelConnections(ERoute::kInput, 0, MaxNChannels(ERoute::kInput), false);
385 SetChannelConnections(ERoute::kInput, 0, data.inputs[0].numChannels, true);
386 AttachBuffers(ERoute::kInput, 0, data.inputs[0].numChannels, data.inputs[0], data.numSamples, sampleSize);
387 }
388 }
389
390 for (int outBus = 0, chanOffset = 0; outBus < data.numOutputs; outBus++)
391 {
392 int busChannels = data.outputs[outBus].numChannels;
393 SetChannelConnections(ERoute::kOutput, chanOffset, busChannels, outs[outBus].get()->isActive());
394 SetChannelConnections(ERoute::kOutput, chanOffset + busChannels, MaxNChannels(ERoute::kOutput) - (chanOffset + busChannels), false);
395 AttachBuffers(ERoute::kOutput, chanOffset, busChannels, data.outputs[outBus], data.numSamples, sampleSize);
396 chanOffset += busChannels;
397 }
398
399 if (GetBypassed())
400 {
401 if (sampleSize == kSample32)
402 PassThroughBuffers(0.f, data.numSamples); // single precision
403 else
404 PassThroughBuffers(0.0, data.numSamples); // double precision
405 }
406 else
407 {
408#ifdef PARAMS_MUTEX
409 mPlug.mParams_mutex.Enter();
410#endif
411 if (sampleSize == kSample32)
412 ProcessBuffers(0.f, data.numSamples); // single precision
413 else
414 ProcessBuffers(0.0, data.numSamples); // double precision
415#ifdef PARAMS_MUTEX
416 mPlug.mParams_mutex.Leave();
417#endif
418 }
419 }
420}
421
422void IPlugVST3ProcessorBase::Process(ProcessData& data, ProcessSetup& setup, const BusList& ins, const BusList& outs, IPlugQueue<IMidiMsg>& fromEditor, IPlugQueue<IMidiMsg>& fromProcessor, IPlugQueue<SysExData>& sysExFromEditor, SysExData& sysExBuf)
423{
424 PrepareProcessContext(data, setup);
425 ProcessParameterChanges(data, fromProcessor);
426
427 if (DoesMIDIIn())
428 {
429 ProcessMidiIn(data.inputEvents, fromEditor, fromProcessor);
430 }
431
432 ProcessAudio(data, setup, ins, outs);
433
434 if (DoesMIDIOut())
435 {
436 ProcessMidiOut(sysExFromEditor, sysExBuf, data.outputEvents, data.numSamples);
437 }
438}
439
441{
442 mMidiOutputQueue.Add(msg);
443 return true;
444}
The base class of an IPlug plug-in, which interacts with the different plug-in APIs.
Definition: IPlugAPIBase.h:43
The base class for IPlug Audio Processing.
virtual void ProcessMidiMsg(const IMidiMsg &msg)
Override this method to handle incoming MIDI messages.
bool DoesMIDIIn() const
bool GetBypassed() const
int MaxNChannels(ERoute direction) const
virtual void ProcessSysEx(ISysEx &msg)
Override this method to handle incoming MIDI System Exclusive (SysEx) messages.
bool DoesMIDIOut() const
virtual void OnReset()
Override this method in your plug-in class to do something prior to playback etc.
A lock-free SPSC queue used to transfer data between threads based on MLQueue.h by Randy Jones based ...
Definition: IPlugQueue.h:32
bool Pop(T &item)
Definition: IPlugQueue.h:74
size_t ElementsAvailable() const
Definition: IPlugQueue.h:88
bool Push(const T &item)
Definition: IPlugQueue.h:57
bool SendMidiMsg(const IMidiMsg &msg) override
Send a single MIDI message // TODO: info about what thread should this be called on or not called on!
ERoute
Used to identify whether a bus/channel connection is an input or an output.
Encapsulates information about the host transport state.
Definition: IPlugStructs.h:585
Encapsulates a MIDI message and provides helper functions.
Definition: IPlugMidi.h:31
void MakeNoteOnMsg(int noteNumber, int velocity, int offset, int channel=0)
Make a Note On message.
Definition: IPlugMidi.h:145
int Channel() const
Gets the channel of a MIDI message.
Definition: IPlugMidi.h:236
void MakeNoteOffMsg(int noteNumber, int offset, int channel=0)
Make a Note Off message.
Definition: IPlugMidi.h:158
void MakePitchWheelMsg(double value, int channel=0, int offset=0)
Create a pitch wheel/bend message.
Definition: IPlugMidi.h:170
int Velocity() const
Get the velocity value of a NoteOn/NoteOff message.
Definition: IPlugMidi.h:270
void MakeControlChangeMsg(EControlChangeMsg idx, double value, int channel=0, int offset=0)
Create a CC message.
Definition: IPlugMidi.h:186
int Program() const
Get the program index from a Program Change message.
Definition: IPlugMidi.h:310
EControlChangeMsg
Constants for MIDI CC messages.
Definition: IPlugMidi.h:50
void MakeChannelATMsg(int pressure, int offset, int channel)
Create a Channel AfterTouch message.
Definition: IPlugMidi.h:211
void MakePolyATMsg(int noteNumber, int pressure, int offset, int channel)
Create a Poly AfterTouch message.
Definition: IPlugMidi.h:225
double PitchWheel() const
Get the value from a Pitchwheel message.
Definition: IPlugMidi.h:321
int NoteNumber() const
Gets the MIDI note number.
Definition: IPlugMidi.h:255
EStatusMsg StatusMsg() const
Gets the MIDI Status message.
Definition: IPlugMidi.h:243
An IOConfig is used to store bus info for each input/output configuration defined in the channel io s...
Definition: IPlugStructs.h:504
const IBusInfo * GetBusInfo(ERoute direction, int index) const
Definition: IPlugStructs.h:526
A struct for dealing with SysEx messages.
Definition: IPlugMidi.h:539
This structure is used when queueing Sysex messages.
Definition: IPlugStructs.h:45