@@ -184,15 +184,19 @@ MPESynthesiserVoice* MPESynthesiser::findVoiceToSteal (MPENote noteToStealVoiceF | |||
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 | |||
// All major OSes use double-locking so this will be lock- and wait-free as long as stealLock is not | |||
// contended. This is always the case if you do not call findVoiceToSteal on multiple threads at | |||
// the same time. | |||
const ScopedLock sl (stealLock); | |||
// this is a list of voices we can steal, sorted by how long they've been running | |||
Array<MPESynthesiserVoice*> usableVoices; | |||
usableVoices.ensureStorageAllocated (voices.size()); | |||
usableVoicesToStealArray.clear(); | |||
for (auto* voice : voices) | |||
{ | |||
jassert (voice->isActive()); // We wouldn't be here otherwise | |||
usableVoices.add (voice); | |||
usableVoicesToStealArray.add (voice); | |||
// NB: Using a functor rather than a lambda here due to scare-stories about | |||
// compilers generating code containing heap allocations.. | |||
@@ -201,7 +205,7 @@ MPESynthesiserVoice* MPESynthesiser::findVoiceToSteal (MPENote noteToStealVoiceF | |||
bool operator() (const MPESynthesiserVoice* a, const MPESynthesiserVoice* b) const noexcept { return a->noteOnTime < b->noteOnTime; } | |||
}; | |||
std::sort (usableVoices.begin(), usableVoices.end(), Sorter()); | |||
std::sort (usableVoicesToStealArray.begin(), usableVoicesToStealArray.end(), Sorter()); | |||
if (! voice->isPlayingButReleased()) // Don't protect released notes | |||
{ | |||
@@ -222,24 +226,24 @@ MPESynthesiserVoice* MPESynthesiser::findVoiceToSteal (MPENote noteToStealVoiceF | |||
// 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) | |||
for (auto* voice : usableVoicesToStealArray) | |||
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) | |||
for (auto* voice : usableVoicesToStealArray) | |||
if (voice != low && voice != top && voice->isPlayingButReleased()) | |||
return voice; | |||
// Oldest voice that doesn't have a finger on it: | |||
for (auto* voice : usableVoices) | |||
for (auto* voice : usableVoicesToStealArray) | |||
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) | |||
for (auto* voice : usableVoicesToStealArray) | |||
if (voice != low && voice != top) | |||
return voice; | |||
@@ -256,9 +260,16 @@ MPESynthesiserVoice* MPESynthesiser::findVoiceToSteal (MPENote noteToStealVoiceF | |||
//============================================================================== | |||
void MPESynthesiser::addVoice (MPESynthesiserVoice* const newVoice) | |||
{ | |||
const ScopedLock sl (voicesLock); | |||
newVoice->setCurrentSampleRate (getSampleRate()); | |||
voices.add (newVoice); | |||
{ | |||
const ScopedLock sl (voicesLock); | |||
newVoice->setCurrentSampleRate (getSampleRate()); | |||
voices.add (newVoice); | |||
} | |||
{ | |||
const ScopedLock sl (stealLock); | |||
usableVoicesToStealArray.ensureStorageAllocated (voices.size() + 1); | |||
} | |||
} | |||
void MPESynthesiser::clearVoices() | |||
@@ -304,6 +304,8 @@ private: | |||
//============================================================================== | |||
std::atomic<bool> shouldStealVoices { false }; | |||
uint32 lastNoteOnCounter = 0; | |||
mutable CriticalSection stealLock; | |||
mutable Array<MPESynthesiserVoice*> usableVoicesToStealArray; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MPESynthesiser) | |||
}; | |||
@@ -98,9 +98,20 @@ void Synthesiser::clearVoices() | |||
SynthesiserVoice* Synthesiser::addVoice (SynthesiserVoice* const newVoice) | |||
{ | |||
const ScopedLock sl (lock); | |||
newVoice->setCurrentPlaybackSampleRate (sampleRate); | |||
return voices.add (newVoice); | |||
SynthesiserVoice* voice; | |||
{ | |||
const ScopedLock sl (lock); | |||
newVoice->setCurrentPlaybackSampleRate (sampleRate); | |||
voice = voices.add (newVoice); | |||
} | |||
{ | |||
const ScopedLock sl (stealLock); | |||
usableVoicesToStealArray.ensureStorageAllocated (voices.size() + 1); | |||
} | |||
return voice; | |||
} | |||
void Synthesiser::removeVoice (const int index) | |||
@@ -514,9 +525,13 @@ SynthesiserVoice* Synthesiser::findVoiceToSteal (SynthesiserSound* soundToPlay, | |||
SynthesiserVoice* low = nullptr; // Lowest sounding note, might be sustained, but NOT in release phase | |||
SynthesiserVoice* top = nullptr; // Highest sounding note, might be sustained, but NOT in release phase | |||
// All major OSes use double-locking so this will be lock- and wait-free as long as the lock is not | |||
// contended. This is always the case if you do not call findVoiceToSteal on multiple threads at | |||
// the same time. | |||
const ScopedLock sl (stealLock); | |||
// this is a list of voices we can steal, sorted by how long they've been running | |||
Array<SynthesiserVoice*> usableVoices; | |||
usableVoices.ensureStorageAllocated (voices.size()); | |||
usableVoicesToStealArray.clear(); | |||
for (auto* voice : voices) | |||
{ | |||
@@ -524,7 +539,7 @@ SynthesiserVoice* Synthesiser::findVoiceToSteal (SynthesiserSound* soundToPlay, | |||
{ | |||
jassert (voice->isVoiceActive()); // We wouldn't be here otherwise | |||
usableVoices.add (voice); | |||
usableVoicesToStealArray.add (voice); | |||
// NB: Using a functor rather than a lambda here due to scare-stories about | |||
// compilers generating code containing heap allocations.. | |||
@@ -533,7 +548,7 @@ SynthesiserVoice* Synthesiser::findVoiceToSteal (SynthesiserSound* soundToPlay, | |||
bool operator() (const SynthesiserVoice* a, const SynthesiserVoice* b) const noexcept { return a->wasStartedBefore (*b); } | |||
}; | |||
std::sort (usableVoices.begin(), usableVoices.end(), Sorter()); | |||
std::sort (usableVoicesToStealArray.begin(), usableVoicesToStealArray.end(), Sorter()); | |||
if (! voice->isPlayingButReleased()) // Don't protect released notes | |||
{ | |||
@@ -553,22 +568,22 @@ SynthesiserVoice* Synthesiser::findVoiceToSteal (SynthesiserSound* soundToPlay, | |||
top = nullptr; | |||
// The oldest note that's playing with the target pitch is ideal.. | |||
for (auto* voice : usableVoices) | |||
for (auto* voice : usableVoicesToStealArray) | |||
if (voice->getCurrentlyPlayingNote() == midiNoteNumber) | |||
return voice; | |||
// Oldest voice that has been released (no finger on it and not held by sustain pedal) | |||
for (auto* voice : usableVoices) | |||
for (auto* voice : usableVoicesToStealArray) | |||
if (voice != low && voice != top && voice->isPlayingButReleased()) | |||
return voice; | |||
// Oldest voice that doesn't have a finger on it: | |||
for (auto* voice : usableVoices) | |||
for (auto* voice : usableVoicesToStealArray) | |||
if (voice != low && voice != top && ! voice->isKeyDown()) | |||
return voice; | |||
// Oldest voice that isn't protected | |||
for (auto* voice : usableVoices) | |||
for (auto* voice : usableVoicesToStealArray) | |||
if (voice != low && voice != top) | |||
return voice; | |||
@@ -627,6 +627,8 @@ private: | |||
bool subBlockSubdivisionIsStrict = false; | |||
bool shouldStealNotes = true; | |||
BigInteger sustainPedalsDown; | |||
mutable CriticalSection stealLock; | |||
mutable Array<SynthesiserVoice*> usableVoicesToStealArray; | |||
template <typename floatType> | |||
void processNextBlock (AudioBuffer<floatType>&, const MidiBuffer&, int startSample, int numSamples); | |||