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