|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338 |
- /*
- ==============================================================================
-
- This file is part of the JUCE library.
- Copyright (c) 2020 - Raw Material Software Limited
-
- JUCE is an open source library subject to commercial or open-source
- licensing.
-
- The code included in this file is provided under the terms of the ISC license
- http://www.isc.org/downloads/software-support-policy/isc-license. Permission
- To use, copy, modify, and/or distribute this software for any purpose with or
- without fee is hereby granted provided that the above copyright notice and
- this permission notice appear in all copies.
-
- JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
- EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
- DISCLAIMED.
-
- ==============================================================================
- */
-
- namespace juce
- {
-
- MPESynthesiser::MPESynthesiser()
- {
- MPEZoneLayout zoneLayout;
- zoneLayout.setLowerZone (15);
- setZoneLayout (zoneLayout);
- }
-
- MPESynthesiser::MPESynthesiser (MPEInstrument* mpeInstrument) : MPESynthesiserBase (mpeInstrument)
- {
- }
-
- MPESynthesiser::~MPESynthesiser()
- {
- }
-
- //==============================================================================
- void MPESynthesiser::startVoice (MPESynthesiserVoice* voice, MPENote noteToStart)
- {
- jassert (voice != nullptr);
-
- voice->currentlyPlayingNote = noteToStart;
- voice->noteOnTime = lastNoteOnCounter++;
- voice->noteStarted();
- }
-
- void MPESynthesiser::stopVoice (MPESynthesiserVoice* voice, MPENote noteToStop, bool allowTailOff)
- {
- jassert (voice != nullptr);
-
- voice->currentlyPlayingNote = noteToStop;
- voice->noteStopped (allowTailOff);
- }
-
- //==============================================================================
- void MPESynthesiser::noteAdded (MPENote newNote)
- {
- const ScopedLock sl (voicesLock);
-
- if (auto* voice = findFreeVoice (newNote, shouldStealVoices))
- startVoice (voice, newNote);
- }
-
- void MPESynthesiser::notePressureChanged (MPENote changedNote)
- {
- const ScopedLock sl (voicesLock);
-
- for (auto* voice : voices)
- {
- if (voice->isCurrentlyPlayingNote (changedNote))
- {
- voice->currentlyPlayingNote = changedNote;
- voice->notePressureChanged();
- }
- }
- }
-
- void MPESynthesiser::notePitchbendChanged (MPENote changedNote)
- {
- const ScopedLock sl (voicesLock);
-
- for (auto* voice : voices)
- {
- if (voice->isCurrentlyPlayingNote (changedNote))
- {
- voice->currentlyPlayingNote = changedNote;
- voice->notePitchbendChanged();
- }
- }
- }
-
- void MPESynthesiser::noteTimbreChanged (MPENote changedNote)
- {
- const ScopedLock sl (voicesLock);
-
- for (auto* voice : voices)
- {
- if (voice->isCurrentlyPlayingNote (changedNote))
- {
- voice->currentlyPlayingNote = changedNote;
- voice->noteTimbreChanged();
- }
- }
- }
-
- void MPESynthesiser::noteKeyStateChanged (MPENote changedNote)
- {
- const ScopedLock sl (voicesLock);
-
- for (auto* voice : voices)
- {
- if (voice->isCurrentlyPlayingNote (changedNote))
- {
- voice->currentlyPlayingNote = changedNote;
- voice->noteKeyStateChanged();
- }
- }
- }
-
- void MPESynthesiser::noteReleased (MPENote finishedNote)
- {
- const ScopedLock sl (voicesLock);
-
- for (auto i = voices.size(); --i >= 0;)
- {
- auto* voice = voices.getUnchecked (i);
-
- if (voice->isCurrentlyPlayingNote(finishedNote))
- stopVoice (voice, finishedNote, true);
- }
- }
-
- void MPESynthesiser::setCurrentPlaybackSampleRate (const double newRate)
- {
- MPESynthesiserBase::setCurrentPlaybackSampleRate (newRate);
-
- const ScopedLock sl (voicesLock);
-
- turnOffAllVoices (false);
-
- for (auto i = voices.size(); --i >= 0;)
- voices.getUnchecked (i)->setCurrentSampleRate (newRate);
- }
-
- void MPESynthesiser::handleMidiEvent (const MidiMessage& m)
- {
- if (m.isController())
- handleController (m.getChannel(), m.getControllerNumber(), m.getControllerValue());
- else if (m.isProgramChange())
- handleProgramChange (m.getChannel(), m.getProgramChangeNumber());
-
- MPESynthesiserBase::handleMidiEvent (m);
- }
-
- MPESynthesiserVoice* MPESynthesiser::findFreeVoice (MPENote noteToFindVoiceFor, bool stealIfNoneAvailable) const
- {
- const ScopedLock sl (voicesLock);
-
- for (auto* voice : voices)
- {
- if (! voice->isActive())
- return voice;
- }
-
- if (stealIfNoneAvailable)
- return findVoiceToSteal (noteToFindVoiceFor);
-
- return nullptr;
- }
-
- MPESynthesiserVoice* MPESynthesiser::findVoiceToSteal (MPENote noteToStealVoiceFor) const
- {
- // This voice-stealing algorithm applies the following heuristics:
- // - Re-use the oldest notes first
- // - Protect the lowest & topmost notes, even if sustained, but not if they've been released.
-
-
- // apparently you are trying to render audio without having any voices...
- jassert (voices.size() > 0);
-
- // These are the voices we want to protect (ie: only steal if unavoidable)
- MPESynthesiserVoice* low = nullptr; // Lowest sounding note, might be sustained, but NOT in release phase
- MPESynthesiserVoice* top = nullptr; // Highest sounding note, might be sustained, but NOT in release phase
-
- // this is a list of voices we can steal, sorted by how long they've been running
- Array<MPESynthesiserVoice*> usableVoices;
- usableVoices.ensureStorageAllocated (voices.size());
-
- for (auto* voice : voices)
- {
- jassert (voice->isActive()); // We wouldn't be here otherwise
-
- usableVoices.add (voice);
-
- // NB: Using a functor rather than a lambda here due to scare-stories about
- // compilers generating code containing heap allocations..
- struct Sorter
- {
- bool operator() (const MPESynthesiserVoice* a, const MPESynthesiserVoice* b) const noexcept { return a->noteOnTime < b->noteOnTime; }
- };
-
- std::sort (usableVoices.begin(), usableVoices.end(), Sorter());
-
- if (! voice->isPlayingButReleased()) // Don't protect released notes
- {
- auto noteNumber = voice->getCurrentlyPlayingNote().initialNote;
-
- if (low == nullptr || noteNumber < low->getCurrentlyPlayingNote().initialNote)
- low = voice;
-
- if (top == nullptr || noteNumber > top->getCurrentlyPlayingNote().initialNote)
- top = voice;
- }
- }
-
- // Eliminate pathological cases (ie: only 1 note playing): we always give precedence to the lowest note(s)
- if (top == low)
- top = nullptr;
-
- // If we want to re-use the voice to trigger a new note,
- // then The oldest note that's playing the same note number is ideal.
- if (noteToStealVoiceFor.isValid())
- for (auto* voice : usableVoices)
- if (voice->getCurrentlyPlayingNote().initialNote == noteToStealVoiceFor.initialNote)
- return voice;
-
- // Oldest voice that has been released (no finger on it and not held by sustain pedal)
- for (auto* voice : usableVoices)
- if (voice != low && voice != top && voice->isPlayingButReleased())
- return voice;
-
- // Oldest voice that doesn't have a finger on it:
- for (auto* voice : usableVoices)
- if (voice != low && voice != top
- && voice->getCurrentlyPlayingNote().keyState != MPENote::keyDown
- && voice->getCurrentlyPlayingNote().keyState != MPENote::keyDownAndSustained)
- return voice;
-
- // Oldest voice that isn't protected
- for (auto* voice : usableVoices)
- if (voice != low && voice != top)
- return voice;
-
- // We've only got "protected" voices now: lowest note takes priority
- jassert (low != nullptr);
-
- // Duophonic synth: give priority to the bass note:
- if (top != nullptr)
- return top;
-
- return low;
- }
-
- //==============================================================================
- void MPESynthesiser::addVoice (MPESynthesiserVoice* const newVoice)
- {
- const ScopedLock sl (voicesLock);
- newVoice->setCurrentSampleRate (getSampleRate());
- voices.add (newVoice);
- }
-
- void MPESynthesiser::clearVoices()
- {
- const ScopedLock sl (voicesLock);
- voices.clear();
- }
-
- MPESynthesiserVoice* MPESynthesiser::getVoice (const int index) const
- {
- const ScopedLock sl (voicesLock);
- return voices [index];
- }
-
- void MPESynthesiser::removeVoice (const int index)
- {
- const ScopedLock sl (voicesLock);
- voices.remove (index);
- }
-
- void MPESynthesiser::reduceNumVoices (const int newNumVoices)
- {
- // we can't possibly get to a negative number of voices...
- jassert (newNumVoices >= 0);
-
- const ScopedLock sl (voicesLock);
-
- while (voices.size() > newNumVoices)
- {
- if (auto* voice = findFreeVoice ({}, true))
- voices.removeObject (voice);
- else
- voices.remove (0); // if there's no voice to steal, kill the oldest voice
- }
- }
-
- void MPESynthesiser::turnOffAllVoices (bool allowTailOff)
- {
- {
- const ScopedLock sl (voicesLock);
-
- // first turn off all voices (it's more efficient to do this immediately
- // rather than to go through the MPEInstrument for this).
- for (auto* voice : voices)
- voice->noteStopped (allowTailOff);
- }
-
- // finally make sure the MPE Instrument also doesn't have any notes anymore.
- instrument->releaseAllNotes();
- }
-
- //==============================================================================
- void MPESynthesiser::renderNextSubBlock (AudioBuffer<float>& buffer, int startSample, int numSamples)
- {
- const ScopedLock sl (voicesLock);
-
- for (auto* voice : voices)
- {
- if (voice->isActive())
- voice->renderNextBlock (buffer, startSample, numSamples);
- }
- }
-
- void MPESynthesiser::renderNextSubBlock (AudioBuffer<double>& buffer, int startSample, int numSamples)
- {
- const ScopedLock sl (voicesLock);
-
- for (auto* voice : voices)
- {
- if (voice->isActive())
- voice->renderNextBlock (buffer, startSample, numSamples);
- }
- }
-
- } // namespace juce
|