OpenShot Audio Library | OpenShotAudio 0.4.0
juce_Synthesiser.cpp
1/*
2 ==============================================================================
3
4 This file is part of the JUCE library.
5 Copyright (c) 2022 - Raw Material Software Limited
6
7 JUCE is an open source library subject to commercial or open-source
8 licensing.
9
10 The code included in this file is provided under the terms of the ISC license
11 http://www.isc.org/downloads/software-support-policy/isc-license. Permission
12 To use, copy, modify, and/or distribute this software for any purpose with or
13 without fee is hereby granted provided that the above copyright notice and
14 this permission notice appear in all copies.
15
16 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
17 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
18 DISCLAIMED.
19
20 ==============================================================================
21*/
22
23namespace juce
24{
25
26SynthesiserSound::SynthesiserSound() {}
28
29//==============================================================================
32
33bool SynthesiserVoice::isPlayingChannel (const int midiChannel) const
34{
35 return currentPlayingMidiChannel == midiChannel;
36}
37
39{
40 currentSampleRate = newRate;
41}
42
44{
45 return getCurrentlyPlayingNote() >= 0;
46}
47
49{
50 currentlyPlayingNote = -1;
51 currentlyPlayingSound = nullptr;
52 currentPlayingMidiChannel = 0;
53}
54
57
58bool SynthesiserVoice::wasStartedBefore (const SynthesiserVoice& other) const noexcept
59{
60 return noteOnTime < other.noteOnTime;
61}
62
64 int startSample, int numSamples)
65{
66 AudioBuffer<double> subBuffer (outputBuffer.getArrayOfWritePointers(),
67 outputBuffer.getNumChannels(),
68 startSample, numSamples);
69
70 tempBuffer.makeCopyOf (subBuffer, true);
71 renderNextBlock (tempBuffer, 0, numSamples);
72 subBuffer.makeCopyOf (tempBuffer, true);
73}
74
75//==============================================================================
77{
78 for (int i = 0; i < numElementsInArray (lastPitchWheelValues); ++i)
79 lastPitchWheelValues[i] = 0x2000;
80}
81
83{
84}
85
86//==============================================================================
88{
89 const ScopedLock sl (lock);
90 return voices [index];
91}
92
94{
95 const ScopedLock sl (lock);
96 voices.clear();
97}
98
100{
101 SynthesiserVoice* voice;
102
103 {
104 const ScopedLock sl (lock);
105 newVoice->setCurrentPlaybackSampleRate (sampleRate);
106 voice = voices.add (newVoice);
107 }
108
109 {
110 const ScopedLock sl (stealLock);
111 usableVoicesToStealArray.ensureStorageAllocated (voices.size() + 1);
112 }
113
114 return voice;
115}
116
117void Synthesiser::removeVoice (const int index)
118{
119 const ScopedLock sl (lock);
120 voices.remove (index);
121}
122
124{
125 const ScopedLock sl (lock);
126 sounds.clear();
127}
128
130{
131 const ScopedLock sl (lock);
132 return sounds.add (newSound);
133}
134
135void Synthesiser::removeSound (const int index)
136{
137 const ScopedLock sl (lock);
138 sounds.remove (index);
139}
140
141void Synthesiser::setNoteStealingEnabled (const bool shouldSteal)
142{
143 shouldStealNotes = shouldSteal;
144}
145
146void Synthesiser::setMinimumRenderingSubdivisionSize (int numSamples, bool shouldBeStrict) noexcept
147{
148 jassert (numSamples > 0); // it wouldn't make much sense for this to be less than 1
149 minimumSubBlockSize = numSamples;
150 subBlockSubdivisionIsStrict = shouldBeStrict;
151}
152
153//==============================================================================
155{
156 if (! approximatelyEqual (sampleRate, newRate))
157 {
158 const ScopedLock sl (lock);
159 allNotesOff (0, false);
160 sampleRate = newRate;
161
162 for (auto* voice : voices)
163 voice->setCurrentPlaybackSampleRate (newRate);
164 }
165}
166
167template <typename floatType>
168void Synthesiser::processNextBlock (AudioBuffer<floatType>& outputAudio,
169 const MidiBuffer& midiData,
170 int startSample,
171 int numSamples)
172{
173 // must set the sample rate before using this!
174 jassert (! exactlyEqual (sampleRate, 0.0));
175 const int targetChannels = outputAudio.getNumChannels();
176
177 auto midiIterator = midiData.findNextSamplePosition (startSample);
178
179 bool firstEvent = true;
180
181 const ScopedLock sl (lock);
182
183 for (; numSamples > 0; ++midiIterator)
184 {
185 if (midiIterator == midiData.cend())
186 {
187 if (targetChannels > 0)
188 renderVoices (outputAudio, startSample, numSamples);
189
190 return;
192
193 const auto metadata = *midiIterator;
194 const int samplesToNextMidiMessage = metadata.samplePosition - startSample;
195
196 if (samplesToNextMidiMessage >= numSamples)
197 {
198 if (targetChannels > 0)
199 renderVoices (outputAudio, startSample, numSamples);
200
201 handleMidiEvent (metadata.getMessage());
202 break;
203 }
204
205 if (samplesToNextMidiMessage < ((firstEvent && ! subBlockSubdivisionIsStrict) ? 1 : minimumSubBlockSize))
206 {
207 handleMidiEvent (metadata.getMessage());
208 continue;
209 }
210
211 firstEvent = false;
212
213 if (targetChannels > 0)
214 renderVoices (outputAudio, startSample, samplesToNextMidiMessage);
215
216 handleMidiEvent (metadata.getMessage());
217 startSample += samplesToNextMidiMessage;
218 numSamples -= samplesToNextMidiMessage;
219 }
220
221 std::for_each (midiIterator,
222 midiData.cend(),
223 [&] (const MidiMessageMetadata& meta) { handleMidiEvent (meta.getMessage()); });
224}
225
226// explicit template instantiation
227template void Synthesiser::processNextBlock<float> (AudioBuffer<float>&, const MidiBuffer&, int, int);
228template void Synthesiser::processNextBlock<double> (AudioBuffer<double>&, const MidiBuffer&, int, int);
229
230void Synthesiser::renderNextBlock (AudioBuffer<float>& outputAudio, const MidiBuffer& inputMidi,
231 int startSample, int numSamples)
232{
233 processNextBlock (outputAudio, inputMidi, startSample, numSamples);
234}
235
236void Synthesiser::renderNextBlock (AudioBuffer<double>& outputAudio, const MidiBuffer& inputMidi,
237 int startSample, int numSamples)
238{
239 processNextBlock (outputAudio, inputMidi, startSample, numSamples);
240}
241
242void Synthesiser::renderVoices (AudioBuffer<float>& buffer, int startSample, int numSamples)
243{
244 for (auto* voice : voices)
245 voice->renderNextBlock (buffer, startSample, numSamples);
246}
247
248void Synthesiser::renderVoices (AudioBuffer<double>& buffer, int startSample, int numSamples)
249{
250 for (auto* voice : voices)
251 voice->renderNextBlock (buffer, startSample, numSamples);
252}
253
255{
256 const int channel = m.getChannel();
257
258 if (m.isNoteOn())
259 {
260 noteOn (channel, m.getNoteNumber(), m.getFloatVelocity());
261 }
262 else if (m.isNoteOff())
263 {
264 noteOff (channel, m.getNoteNumber(), m.getFloatVelocity(), true);
265 }
266 else if (m.isAllNotesOff() || m.isAllSoundOff())
267 {
268 allNotesOff (channel, true);
269 }
270 else if (m.isPitchWheel())
271 {
272 const int wheelPos = m.getPitchWheelValue();
273 lastPitchWheelValues [channel - 1] = wheelPos;
274 handlePitchWheel (channel, wheelPos);
275 }
276 else if (m.isAftertouch())
277 {
279 }
280 else if (m.isChannelPressure())
281 {
283 }
284 else if (m.isController())
285 {
287 }
288 else if (m.isProgramChange())
289 {
291 }
292}
293
294//==============================================================================
295void Synthesiser::noteOn (const int midiChannel,
296 const int midiNoteNumber,
297 const float velocity)
298{
299 const ScopedLock sl (lock);
300
301 for (auto* sound : sounds)
302 {
303 if (sound->appliesToNote (midiNoteNumber) && sound->appliesToChannel (midiChannel))
304 {
305 // If hitting a note that's still ringing, stop it first (it could be
306 // still playing because of the sustain or sostenuto pedal).
307 for (auto* voice : voices)
308 if (voice->getCurrentlyPlayingNote() == midiNoteNumber && voice->isPlayingChannel (midiChannel))
309 stopVoice (voice, 1.0f, true);
310
311 startVoice (findFreeVoice (sound, midiChannel, midiNoteNumber, shouldStealNotes),
312 sound, midiChannel, midiNoteNumber, velocity);
313 }
314 }
315}
316
318 SynthesiserSound* const sound,
319 const int midiChannel,
320 const int midiNoteNumber,
321 const float velocity)
322{
323 if (voice != nullptr && sound != nullptr)
324 {
325 if (voice->currentlyPlayingSound != nullptr)
326 voice->stopNote (0.0f, false);
327
328 voice->currentlyPlayingNote = midiNoteNumber;
329 voice->currentPlayingMidiChannel = midiChannel;
330 voice->noteOnTime = ++lastNoteOnCounter;
331 voice->currentlyPlayingSound = sound;
332 voice->setKeyDown (true);
333 voice->setSostenutoPedalDown (false);
334 voice->setSustainPedalDown (sustainPedalsDown[midiChannel]);
335
336 voice->startNote (midiNoteNumber, velocity, sound,
337 lastPitchWheelValues [midiChannel - 1]);
338 }
339}
340
341void Synthesiser::stopVoice (SynthesiserVoice* voice, float velocity, const bool allowTailOff)
342{
343 jassert (voice != nullptr);
344
345 voice->stopNote (velocity, allowTailOff);
346
347 // the subclass MUST call clearCurrentNote() if it's not tailing off! RTFM for stopNote()!
348 jassert (allowTailOff || (voice->getCurrentlyPlayingNote() < 0 && voice->getCurrentlyPlayingSound() == nullptr));
349}
350
351void Synthesiser::noteOff (const int midiChannel,
352 const int midiNoteNumber,
353 const float velocity,
354 const bool allowTailOff)
355{
356 const ScopedLock sl (lock);
357
358 for (auto* voice : voices)
359 {
360 if (voice->getCurrentlyPlayingNote() == midiNoteNumber
361 && voice->isPlayingChannel (midiChannel))
362 {
363 if (auto sound = voice->getCurrentlyPlayingSound())
364 {
365 if (sound->appliesToNote (midiNoteNumber)
366 && sound->appliesToChannel (midiChannel))
367 {
368 jassert (! voice->keyIsDown || voice->isSustainPedalDown() == sustainPedalsDown [midiChannel]);
369
370 voice->setKeyDown (false);
371
372 if (! (voice->isSustainPedalDown() || voice->isSostenutoPedalDown()))
373 stopVoice (voice, velocity, allowTailOff);
374 }
375 }
376 }
377 }
378}
379
380void Synthesiser::allNotesOff (const int midiChannel, const bool allowTailOff)
381{
382 const ScopedLock sl (lock);
383
384 for (auto* voice : voices)
385 if (midiChannel <= 0 || voice->isPlayingChannel (midiChannel))
386 voice->stopNote (1.0f, allowTailOff);
387
388 sustainPedalsDown.clear();
389}
390
391void Synthesiser::handlePitchWheel (const int midiChannel, const int wheelValue)
392{
393 const ScopedLock sl (lock);
394
395 for (auto* voice : voices)
396 if (midiChannel <= 0 || voice->isPlayingChannel (midiChannel))
397 voice->pitchWheelMoved (wheelValue);
398}
399
400void Synthesiser::handleController (const int midiChannel,
401 const int controllerNumber,
402 const int controllerValue)
403{
404 switch (controllerNumber)
405 {
406 case 0x40: handleSustainPedal (midiChannel, controllerValue >= 64); break;
407 case 0x42: handleSostenutoPedal (midiChannel, controllerValue >= 64); break;
408 case 0x43: handleSoftPedal (midiChannel, controllerValue >= 64); break;
409 default: break;
410 }
411
412 const ScopedLock sl (lock);
413
414 for (auto* voice : voices)
415 if (midiChannel <= 0 || voice->isPlayingChannel (midiChannel))
416 voice->controllerMoved (controllerNumber, controllerValue);
417}
418
419void Synthesiser::handleAftertouch (int midiChannel, int midiNoteNumber, int aftertouchValue)
420{
421 const ScopedLock sl (lock);
422
423 for (auto* voice : voices)
424 if (voice->getCurrentlyPlayingNote() == midiNoteNumber
425 && (midiChannel <= 0 || voice->isPlayingChannel (midiChannel)))
426 voice->aftertouchChanged (aftertouchValue);
427}
428
429void Synthesiser::handleChannelPressure (int midiChannel, int channelPressureValue)
430{
431 const ScopedLock sl (lock);
432
433 for (auto* voice : voices)
434 if (midiChannel <= 0 || voice->isPlayingChannel (midiChannel))
435 voice->channelPressureChanged (channelPressureValue);
436}
437
438void Synthesiser::handleSustainPedal (int midiChannel, bool isDown)
439{
440 jassert (midiChannel > 0 && midiChannel <= 16);
441 const ScopedLock sl (lock);
442
443 if (isDown)
444 {
445 sustainPedalsDown.setBit (midiChannel);
446
447 for (auto* voice : voices)
448 if (voice->isPlayingChannel (midiChannel) && voice->isKeyDown())
449 voice->setSustainPedalDown (true);
450 }
451 else
452 {
453 for (auto* voice : voices)
454 {
455 if (voice->isPlayingChannel (midiChannel))
456 {
457 voice->setSustainPedalDown (false);
458
459 if (! (voice->isKeyDown() || voice->isSostenutoPedalDown()))
460 stopVoice (voice, 1.0f, true);
461 }
462 }
463
464 sustainPedalsDown.clearBit (midiChannel);
465 }
466}
467
468void Synthesiser::handleSostenutoPedal (int midiChannel, bool isDown)
469{
470 jassert (midiChannel > 0 && midiChannel <= 16);
471 const ScopedLock sl (lock);
472
473 for (auto* voice : voices)
474 {
475 if (voice->isPlayingChannel (midiChannel))
476 {
477 if (isDown)
478 voice->setSostenutoPedalDown (true);
479 else if (voice->isSostenutoPedalDown())
480 stopVoice (voice, 1.0f, true);
481 }
482 }
483}
484
485void Synthesiser::handleSoftPedal ([[maybe_unused]] int midiChannel, bool /*isDown*/)
486{
487 jassert (midiChannel > 0 && midiChannel <= 16);
488}
489
490void Synthesiser::handleProgramChange ([[maybe_unused]] int midiChannel,
491 [[maybe_unused]] int programNumber)
492{
493 jassert (midiChannel > 0 && midiChannel <= 16);
494}
495
496//==============================================================================
498 int midiChannel, int midiNoteNumber,
499 const bool stealIfNoneAvailable) const
500{
501 const ScopedLock sl (lock);
502
503 for (auto* voice : voices)
504 if ((! voice->isVoiceActive()) && voice->canPlaySound (soundToPlay))
505 return voice;
506
507 if (stealIfNoneAvailable)
508 return findVoiceToSteal (soundToPlay, midiChannel, midiNoteNumber);
509
510 return nullptr;
511}
512
514 int /*midiChannel*/, int midiNoteNumber) const
515{
516 // This voice-stealing algorithm applies the following heuristics:
517 // - Re-use the oldest notes first
518 // - Protect the lowest & topmost notes, even if sustained, but not if they've been released.
519
520 // apparently you are trying to render audio without having any voices...
521 jassert (! voices.isEmpty());
522
523 // These are the voices we want to protect (ie: only steal if unavoidable)
524 SynthesiserVoice* low = nullptr; // Lowest sounding note, might be sustained, but NOT in release phase
525 SynthesiserVoice* top = nullptr; // Highest sounding note, might be sustained, but NOT in release phase
526
527 // All major OSes use double-locking so this will be lock- and wait-free as long as the lock is not
528 // contended. This is always the case if you do not call findVoiceToSteal on multiple threads at
529 // the same time.
530 const ScopedLock sl (stealLock);
531
532 // this is a list of voices we can steal, sorted by how long they've been running
533 usableVoicesToStealArray.clear();
534
535 for (auto* voice : voices)
536 {
537 if (voice->canPlaySound (soundToPlay))
538 {
539 jassert (voice->isVoiceActive()); // We wouldn't be here otherwise
540
541 usableVoicesToStealArray.add (voice);
542
543 // NB: Using a functor rather than a lambda here due to scare-stories about
544 // compilers generating code containing heap allocations..
545 struct Sorter
546 {
547 bool operator() (const SynthesiserVoice* a, const SynthesiserVoice* b) const noexcept { return a->wasStartedBefore (*b); }
548 };
549
550 std::sort (usableVoicesToStealArray.begin(), usableVoicesToStealArray.end(), Sorter());
551
552 if (! voice->isPlayingButReleased()) // Don't protect released notes
553 {
554 auto note = voice->getCurrentlyPlayingNote();
555
556 if (low == nullptr || note < low->getCurrentlyPlayingNote())
557 low = voice;
558
559 if (top == nullptr || note > top->getCurrentlyPlayingNote())
560 top = voice;
561 }
562 }
563 }
564
565 // Eliminate pathological cases (ie: only 1 note playing): we always give precedence to the lowest note(s)
566 if (top == low)
567 top = nullptr;
568
569 // The oldest note that's playing with the target pitch is ideal..
570 for (auto* voice : usableVoicesToStealArray)
571 if (voice->getCurrentlyPlayingNote() == midiNoteNumber)
572 return voice;
573
574 // Oldest voice that has been released (no finger on it and not held by sustain pedal)
575 for (auto* voice : usableVoicesToStealArray)
576 if (voice != low && voice != top && voice->isPlayingButReleased())
577 return voice;
578
579 // Oldest voice that doesn't have a finger on it:
580 for (auto* voice : usableVoicesToStealArray)
581 if (voice != low && voice != top && ! voice->isKeyDown())
582 return voice;
583
584 // Oldest voice that isn't protected
585 for (auto* voice : usableVoicesToStealArray)
586 if (voice != low && voice != top)
587 return voice;
588
589 // We've only got "protected" voices now: lowest note takes priority
590 jassert (low != nullptr);
591
592 // Duophonic synth: give priority to the bass note:
593 if (top != nullptr)
594 return top;
595
596 return low;
597}
598
599} // namespace juce
void makeCopyOf(const AudioBuffer< OtherType > &other, bool avoidReallocating=false)
int getNumChannels() const noexcept
Type *const * getArrayOfWritePointers() noexcept
BigInteger & clear() noexcept
BigInteger & clearBit(int bitNumber) noexcept
BigInteger & setBit(int bitNumber)
MidiBufferIterator findNextSamplePosition(int samplePosition) const noexcept
MidiBufferIterator cend() const noexcept
bool isAftertouch() const noexcept
bool isNoteOn(bool returnTrueForVelocity0=false) const noexcept
float getFloatVelocity() const noexcept
int getChannel() const noexcept
bool isProgramChange() const noexcept
bool isController() const noexcept
bool isAllSoundOff() const noexcept
int getControllerNumber() const noexcept
int getChannelPressureValue() const noexcept
bool isNoteOff(bool returnTrueForNoteOnVelocity0=true) const noexcept
bool isPitchWheel() const noexcept
int getNoteNumber() const noexcept
int getProgramChangeNumber() const noexcept
int getAfterTouchValue() const noexcept
int getControllerValue() const noexcept
bool isAllNotesOff() const noexcept
bool isChannelPressure() const noexcept
int getPitchWheelValue() const noexcept
virtual void stopNote(float velocity, bool allowTailOff)=0
void setSustainPedalDown(bool isNowDown) noexcept
virtual void channelPressureChanged(int newChannelPressureValue)
void setSostenutoPedalDown(bool isNowDown) noexcept
virtual void renderNextBlock(AudioBuffer< float > &outputBuffer, int startSample, int numSamples)=0
virtual bool isPlayingChannel(int midiChannel) const
bool isKeyDown() const noexcept
void setKeyDown(bool isNowDown) noexcept
virtual void setCurrentPlaybackSampleRate(double newRate)
virtual void aftertouchChanged(int newAftertouchValue)
int getCurrentlyPlayingNote() const noexcept
virtual void startNote(int midiNoteNumber, float velocity, SynthesiserSound *sound, int currentPitchWheelPosition)=0
bool wasStartedBefore(const SynthesiserVoice &other) const noexcept
bool isPlayingButReleased() const noexcept
SynthesiserSound::Ptr getCurrentlyPlayingSound() const noexcept
virtual bool isVoiceActive() const
virtual SynthesiserVoice * findFreeVoice(SynthesiserSound *soundToPlay, int midiChannel, int midiNoteNumber, bool stealIfNoneAvailable) const
virtual void handleProgramChange(int midiChannel, int programNumber)
void removeVoice(int index)
void startVoice(SynthesiserVoice *voice, SynthesiserSound *sound, int midiChannel, int midiNoteNumber, float velocity)
virtual void handleAftertouch(int midiChannel, int midiNoteNumber, int aftertouchValue)
void renderNextBlock(AudioBuffer< float > &outputAudio, const MidiBuffer &inputMidi, int startSample, int numSamples)
virtual void handleController(int midiChannel, int controllerNumber, int controllerValue)
virtual SynthesiserVoice * findVoiceToSteal(SynthesiserSound *soundToPlay, int midiChannel, int midiNoteNumber) const
virtual void handleSoftPedal(int midiChannel, bool isDown)
void removeSound(int index)
virtual void allNotesOff(int midiChannel, bool allowTailOff)
SynthesiserVoice * addVoice(SynthesiserVoice *newVoice)
void stopVoice(SynthesiserVoice *, float velocity, bool allowTailOff)
SynthesiserSound * addSound(const SynthesiserSound::Ptr &newSound)
virtual void renderVoices(AudioBuffer< float > &outputAudio, int startSample, int numSamples)
virtual void handleMidiEvent(const MidiMessage &)
void setNoteStealingEnabled(bool shouldStealNotes)
virtual void handlePitchWheel(int midiChannel, int wheelValue)
CriticalSection lock
virtual void noteOn(int midiChannel, int midiNoteNumber, float velocity)
SynthesiserVoice * getVoice(int index) const
virtual void noteOff(int midiChannel, int midiNoteNumber, float velocity, bool allowTailOff)
virtual void handleChannelPressure(int midiChannel, int channelPressureValue)
virtual void setCurrentPlaybackSampleRate(double sampleRate)
void setMinimumRenderingSubdivisionSize(int numSamples, bool shouldBeStrict=false) noexcept
virtual void handleSustainPedal(int midiChannel, bool isDown)
virtual void handleSostenutoPedal(int midiChannel, bool isDown)