iPlug2 - C++ Audio Plug-in Framework
Loading...
Searching...
No Matches
VoiceAllocator.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 "VoiceAllocator.h"
12
13#include <algorithm>
14#include <numeric>
15#include <iostream>
16
17using namespace iplug;
18
19std::ostream& operator<< (std::ostream& out, const VoiceInputEvent& r)
20{
21 out << "[z" << (int)r.mAddress.mZone << " c" << (int)r.mAddress.mChannel << " k" << (int)r.mAddress.mKey << " f" << (int)r.mAddress.mFlags << "]" ;
22 out << "[action:" << r.mAction << " ctl:" << r.mControllerNumber << " val:" << r.mValue << " off:" << r.mSampleOffset << "]";
23 return out;
24}
25
26VoiceAllocator::VoiceAllocator()
27{
28 // setup default key->pitch fn
29 mKeyToPitchFn = [](int k){return (k - 69.f)/12.f;};
30
31 mSustainedNotes.reserve(128);
32 mHeldKeys.reserve(128);
33}
34
35VoiceAllocator::~VoiceAllocator()
36{
37}
38
39void VoiceAllocator::Clear()
40{
41 mHeldKeys.clear();
42 mSustainedNotes.clear();
44}
45
46void VoiceAllocator::ClearVoiceInputs(SynthVoice* pVoice)
47{
48 for(int i=0; i<kNumVoiceControlRamps; ++i)
49 {
50 pVoice->mInputs[i].Clear();
51 }
52}
53
54void VoiceAllocator::AddVoice(SynthVoice* pVoice, uint8_t zone)
55{
56 if(mVoicePtrs.size() + 1 < UCHAR_MAX)
57 {
58 mVoicePtrs.push_back(pVoice);
59 ClearVoiceInputs(pVoice);
60 pVoice->mKey = -1;
61 pVoice->mZone = zone;
62
63 // make a glides structures for the control ramps of the new voice
64 mVoiceGlides.emplace_back(ControlRampProcessor::Create(pVoice->mInputs));
65 }
66 else
67 {
68 throw std::runtime_error{"VoiceAllocator: max voices exceeded!"};
69 }
70}
71
72VoiceAllocator::VoiceBitsArray VoiceAllocator::VoicesMatchingAddress(VoiceAddress addr)
73{
74 const int n = static_cast<int>(mVoicePtrs.size());
75 VoiceBitsArray v;
76
77 // set all bits to true
78 for(int i=0; i<n; ++i)
79 {
80 v[i] = true;
81 }
82
83 // for each criterion present in address, clear any voice bits not matching
84
85 // zone
86 if(addr.mZone != kAllZones)
87 {
88 for(int i=0; i<n; ++i)
89 {
90 v[i] = v[i] & (mVoicePtrs[i]->mZone == addr.mZone);
91 }
92 }
93
94 // setting the flag kVoicesAll returns all voices matching the zone of the address.
95 if(addr.mFlags & kVoicesAll) return v;
96
97 // channel
98 if(addr.mChannel != kAllChannels)
99 {
100 for(int i=0; i<n; ++i)
101 {
102 v[i] = v[i] & (mVoicePtrs[i]->mChannel == addr.mChannel);
103 }
104 }
105
106 // Key
107 if(addr.mKey != kAllKeys)
108 {
109 for(int i=0; i<n; ++i)
110 {
111 v[i] = v[i] & (mVoicePtrs[i]->mKey == addr.mKey);
112 }
113 }
114
115 // busy flag
116 if(addr.mFlags & kVoicesBusy)
117 {
118 for(int i=0; i<n; ++i)
119 {
120 v[i] = v[i] & mVoicePtrs[i]->GetBusy();
121 }
122 }
123
124 // most recent
125 if(addr.mFlags & kVoicesMostRecent)
126 {
127 int64_t maxT = -1;
128 int maxIdx = -1;
129 for(int i=0; i<n; ++i)
130 {
131 if(v[i])
132 {
133 int64_t vt = mVoicePtrs[i]->mLastTriggeredTime;
134 if(vt > maxT)
135 {
136 maxT = vt;
137 maxIdx = i;
138 }
139 }
140 }
141
142 // set all bits to false
143 //v.reset();
144 for(int i=0; i<n; ++i)
145 {
146 v[i] = 0;
147 }
148
149 if(maxIdx >= 0)
150 {
151 v[maxIdx] = true;
152 }
153 }
154 return v;
155}
156
157void VoiceAllocator::SendControlToVoiceInputs(VoiceBitsArray v, int ctlIdx, float val, int glideSamples)
158{
159 // send control change to all matched voices through glide generators
160 for(int i=0; i<mVoicePtrs.size(); ++i)
161 {
162 if(v[i])
163 {
164 mVoiceGlides[i]->at(ctlIdx).SetTarget(val, 0, glideSamples, mBlockSize);
165 }
166 }
167}
168
169void VoiceAllocator::SendControlToVoicesDirect(VoiceBitsArray v, int ctlIdx, float val)
170{
171 // send generic control change directly to voice
172 for(int i=0; i<mVoicePtrs.size(); ++i)
173 {
174 if(v[i])
175 {
176 mVoicePtrs[i]->SetControl(ctlIdx, val);
177 }
178 }
179}
180
181void VoiceAllocator::SendProgramChangeToVoices(VoiceBitsArray v, int pgm)
182{
183 for(int i=0; i<mVoicePtrs.size(); ++i)
184 {
185 if(v[i])
186 {
187 mVoicePtrs[i]->SetProgramNumber(pgm);
188 }
189 }
190}
191
192void VoiceAllocator::ProcessEvents(int blockSize, int64_t sampleTime)
193{
194 while(mInputQueue.ElementsAvailable())
195 {
196 VoiceInputEvent event;
197 mInputQueue.Pop(event);
198 VoiceAllocator::VoiceBitsArray voices = VoicesMatchingAddress(event.mAddress);
199
200 switch(event.mAction)
201 {
202 case kNoteOnAction:
203 {
204 NoteOn(event, sampleTime);
205 break;
206 }
207 case kNoteOffAction:
208 {
209 if(event.mAddress.mFlags == kVoicesAll)
210 {
212 }
213 else
214 {
215 NoteOff(event, sampleTime);
216 }
217 break;
218 }
219 case kPitchBendAction:
220 {
221 SendControlToVoiceInputs(voices, kVoiceControlPitchBend, event.mValue, mControlGlideSamples);
222 break;
223 }
224 case kPressureAction:
225 {
226 SendControlToVoiceInputs(voices, kVoiceControlPressure, event.mValue, mControlGlideSamples);
227 break;
228 }
229 case kTimbreAction:
230 {
231 SendControlToVoiceInputs(voices, kVoiceControlTimbre, event.mValue, mControlGlideSamples);
232 break;
233 }
234 case kSustainAction:
235 {
236 mSustainPedalDown = (bool) (event.mValue >= 0.5);
237 if (!mSustainPedalDown) // sustain pedal released
238 {
239 // if notes are sustaining, check that they're not still held and if not then stop voice
240 if (!mSustainedNotes.empty())
241 {
242 for (auto susNotesItr = mSustainedNotes.begin(); susNotesItr != mSustainedNotes.end();)
243 {
244 uint8_t key = *susNotesItr;
245 bool held = std::find(mHeldKeys.begin(), mHeldKeys.end(), key) != mHeldKeys.end();
246 if (!held)
247 {
248 StopVoices(VoicesMatchingAddress({event.mAddress.mZone, kAllChannels, key, 0}), event.mSampleOffset);
249 susNotesItr = mSustainedNotes.erase(susNotesItr);
250 }
251 else
252 susNotesItr++;
253 }
254 }
255 }
256 break;
257 }
258 case kControllerAction:
259 {
260 // called for any continuous controller other than the special #74 specified in MPE
261 SendControlToVoicesDirect(voices, event.mControllerNumber, event.mValue);
262 break;
263 }
264 case kProgramChangeAction:
265 {
266 SendProgramChangeToVoices(voices, event.mControllerNumber);
267 break;
268 }
269 case kNullAction:
270 default:
271 {
272 break;
273 }
274 }
275 }
276
277 // update any glides in progress, writing voice control outputs
278 for(auto& glides : mVoiceGlides)
279 {
280 for(int i=0; i<kNumVoiceControlRamps; ++i)
281 {
282 glides->at(i).Process(blockSize);
283 }
284 }
285}
286
287void VoiceAllocator::CalcGlideTimesInSamples()
288{
289 mNoteGlideSamples = static_cast<int>(mNoteGlideTime * mSampleRate);
290 mControlGlideSamples = static_cast<int>(mControlGlideTime * mSampleRate);
291}
292
293int VoiceAllocator::FindFreeVoiceIndex(int startIndex) const
294{
295 size_t voices = mVoicePtrs.size();
296 for(int i=0; i<voices; ++i)
297 {
298 int j = (startIndex + i)%voices;
299 SynthVoice* pv = mVoicePtrs[j];
300 if(!pv->GetBusy())
301 {
302 return j;
303 }
304 }
305 return -1;
306}
307
308int VoiceAllocator::FindVoiceIndexToSteal(int64_t sampleTime) const
309{
310 size_t voices = mVoicePtrs.size();
311 int64_t earliestTime = sampleTime;
312 int longestPlayingVoiceIdx = 0;
313 for(int i=0; i<voices; ++i)
314 {
315 SynthVoice* pv = mVoicePtrs[i];
316 if(pv->mLastTriggeredTime < earliestTime)
317 {
318 earliestTime = pv->mLastTriggeredTime;
319 longestPlayingVoiceIdx = i;
320 }
321 }
322 return longestPlayingVoiceIdx;
323}
324
325// start a single voice and set its current channel and key.
326void VoiceAllocator::StartVoice(int voiceIdx, int channel, int key, float pitch, float velocity, int sampleOffset, int64_t sampleTime, bool retrig)
327{
328 if(!retrig)
329 {
330 // add immediate sample-accurate change for trigger
331 mVoiceGlides[voiceIdx]->at(kVoiceControlGate).SetTarget(velocity, sampleOffset, 1, mBlockSize);
332 }
333
334 // add glide for pitch
335 mVoiceGlides[voiceIdx]->at(kVoiceControlPitch).SetTarget(pitch, sampleOffset, mNoteGlideSamples, mBlockSize);
336
337 // set things directly in voice
338 SynthVoice* pVoice = mVoicePtrs[voiceIdx];
339 pVoice->mLastTriggeredTime = sampleTime;
340 pVoice->mChannel = channel;
341 pVoice->mKey = key;
342 pVoice->mGain = 1.;
343
344 // call voice's Trigger method
345 pVoice->Trigger(velocity, retrig);
346}
347
348// start all of the voice indexes marked in the VoieBitsArray and set the current channel and key of each.
349void VoiceAllocator::StartVoices(VoiceBitsArray vbits, int channel, int key, float pitch, float velocity, int sampleOffset, int64_t sampleTime, bool retrig)
350{
351 for(int i=0; i<mVoicePtrs.size(); ++i)
352 {
353 if(vbits[i])
354 {
355 StartVoice(i, channel, key, pitch, velocity, sampleOffset, sampleTime, retrig);
356 }
357 }
358}
359
360void VoiceAllocator::StopVoice(int voiceIdx, int sampleOffset)
361{
362 mVoiceGlides[voiceIdx]->at(kVoiceControlGate).SetTarget(0.0, sampleOffset, 1, mBlockSize);
363 mVoicePtrs[voiceIdx]->mKey = -1;
364 mVoicePtrs[voiceIdx]->Release();
365}
366
367// stop all voices marked in the VoiceBitsArray.
368void VoiceAllocator::StopVoices(VoiceBitsArray vbits, int sampleOffset)
369{
370 for(int i=0; i<mVoicePtrs.size(); ++i)
371 {
372 if(vbits[i])
373 {
374 StopVoice(i, sampleOffset);
375 }
376 }
377}
378
380{
381 mHeldKeys.clear();
382 mSustainedNotes.clear();
383 mSustainPedalDown = false;
384
385 size_t voices = mVoicePtrs.size();
386 for (int v = 0; v < voices; v++)
387 {
388 StopVoice(v, 0);
389 }
390}
391
393{
395 for (int v = 0; v < mVoicePtrs.size(); v++)
396 {
397 mVoicePtrs[v]->mGain = 0.;
398 }
399}
400
401void VoiceAllocator::NoteOn(VoiceInputEvent e, int64_t sampleTime)
402{
403 int channel = e.mAddress.mChannel;
404 int key = e.mAddress.mKey;
405 int offset = e.mSampleOffset;
406 float velocity = e.mValue;
407 float pitch = mKeyToPitchFn(key + static_cast<int>(mPitchOffset));
408
409 switch(mPolyMode)
410 {
411 case kPolyModeMono:
412 {
413 // TODO retrig / legato
414 bool retrig = false;
415
416 // trigger all voices in zone
417 StartVoices(VoicesMatchingAddress({e.mAddress.mZone, kAllChannels, kAllKeys, 0}), channel, key, pitch, velocity, offset, sampleTime, retrig);
418
419 // in mono modes only ever 1 sustained note
420 mSustainedNotes.clear();
421 break;
422 }
423 case kPolyModePoly:
424 {
425 int i = FindFreeVoiceIndex(mVoiceRotateIndex);
426 if(i < 0)
427 {
428 i = FindVoiceIndexToSteal(sampleTime);
429 }
430 if(mRotateVoices)
431 {
432 mVoiceRotateIndex = i + 1;
433 }
434 if(i >= 0)
435 {
436 bool retrig = false;
437 StartVoice(i, channel, key, pitch, velocity, offset, sampleTime, retrig);
438 }
439 break;
440 }
441
442 default:
443 break;
444 }
445
446 // add to held keys
447 if(std::find(mHeldKeys.begin(), mHeldKeys.end(), key) == mHeldKeys.end())
448 {
449 mHeldKeys.push_back(key);
450 mMinHeldVelocity = std::min(velocity, mMinHeldVelocity);
451 }
452
453 // add to sustained notes
454 if(std::find(mSustainedNotes.begin(), mSustainedNotes.end(), key) == mSustainedNotes.end())
455 {
456 mSustainedNotes.push_back(key);
457 }
458}
459
460void VoiceAllocator::NoteOff(VoiceInputEvent e, int64_t sampleTime)
461{
462 int channel = e.mAddress.mChannel;
463 int key = e.mAddress.mKey;
464 int offset = e.mSampleOffset;
465
466 // remove from held keys
467 mHeldKeys.erase(std::remove(mHeldKeys.begin(), mHeldKeys.end(), key), mHeldKeys.end());
468 if(mHeldKeys.empty())
469 {
470 mMinHeldVelocity = 1.0f;
471 }
472
473 if(mPolyMode == kPolyModeMono)
474 {
475 bool doPlayQueuedKey = false;
476 int queuedKey = 0;
477
478 // if there are still held keys...
479 if(!mHeldKeys.empty())
480 {
481 queuedKey = mHeldKeys.back();
482 if (queuedKey != mVoicePtrs[0]->mKey)
483 {
484 doPlayQueuedKey = true;
485 if(mSustainPedalDown)
486 {
487 // in mono modes only ever 1 sustained note
488 mSustainedNotes.clear();
489 mSustainedNotes.push_back(queuedKey);
490 }
491 }
492 }
493 else if(mSustainPedalDown)
494 {
495 if(!mSustainedNotes.empty())
496 {
497 queuedKey = mSustainedNotes.back();
498 if (queuedKey != mVoicePtrs[0]->mKey)
499 {
500 doPlayQueuedKey = true;
501 }
502 }
503 }
504 else
505 {
506 // there are no held keys, so no voices in the zone should be playing.
507 StopVoices(VoicesMatchingAddress({e.mAddress.mZone, kAllChannels, kAllKeys, 0}), offset);
508 }
509
510 if(doPlayQueuedKey)
511 {
512 // trigger the queued key for all voices in the zone at the minimum held velocity.
513 // alternatively the release velocity of the note off could be used here.
514 float pitch = mKeyToPitchFn(queuedKey + static_cast<int>(mPitchOffset));
515 bool retrig = false;
516
517 StartVoices(VoicesMatchingAddress({e.mAddress.mZone, kAllChannels, kAllKeys, 0}), channel, queuedKey, pitch, mMinHeldVelocity, offset, sampleTime, retrig);
518 }
519 }
520 else // poly
521 {
522 if (!mSustainPedalDown)
523 {
524 StopVoices(VoicesMatchingAddress(e.mAddress), e.mSampleOffset);
525 mSustainedNotes.erase(std::remove(mSustainedNotes.begin(), mSustainedNotes.end(), key), mSustainedNotes.end());
526 }
527 }
528}
529
530void VoiceAllocator::ProcessVoices(sample** inputs, sample** outputs, int nInputs, int nOutputs, int startIndex, int blockSize)
531{
532 for(auto pVoice : mVoicePtrs)
533 {
534 // TODO distribute voices across cores
535 if(pVoice->GetBusy())
536 {
537 pVoice->ProcessSamplesAccumulating(inputs, outputs, nInputs, nOutputs, startIndex, blockSize);
538 }
539 }
540}
A VoiceInputEvent describes a change in input to be applied to one more more voices.
bool Pop(T &item)
Definition: IPlugQueue.h:74
size_t ElementsAvailable() const
Definition: IPlugQueue.h:106
virtual void ProcessSamplesAccumulating(sample **inputs, sample **outputs, int nInputs, int nOutputs, int startIdx, int nFrames)
Process a block of audio data for the voice.
Definition: SynthVoice.h:78
virtual void Trigger(double level, bool isRetrigger)
Trigger is called by the VoiceAllocator when a new voice should start, or if the voice limit has been...
Definition: SynthVoice.h:66
virtual bool GetBusy() const =0
void HardKillAllVoices()
Stop all voices from making sound immdiately.
void SoftKillAllVoices()
Turn all voice gates off, allowing any voice envelopes to finish.
void ProcessEvents(int samples, int64_t sampleTime)
Process all input events and generate voice outputs.
void AddVoice(SynthVoice *pv, uint8_t zone)
Add a synth voice to the allocator.