|
|
|
@@ -24,20 +24,8 @@ SynthesiserSound::SynthesiserSound() {} |
|
|
|
SynthesiserSound::~SynthesiserSound() {}
|
|
|
|
|
|
|
|
//==============================================================================
|
|
|
|
SynthesiserVoice::SynthesiserVoice()
|
|
|
|
: currentSampleRate (44100.0),
|
|
|
|
currentlyPlayingNote (-1),
|
|
|
|
currentPlayingMidiChannel (0),
|
|
|
|
noteOnTime (0),
|
|
|
|
keyIsDown (false),
|
|
|
|
sustainPedalDown (false),
|
|
|
|
sostenutoPedalDown (false)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
SynthesiserVoice::~SynthesiserVoice()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
SynthesiserVoice::SynthesiserVoice() {}
|
|
|
|
SynthesiserVoice::~SynthesiserVoice() {}
|
|
|
|
|
|
|
|
bool SynthesiserVoice::isPlayingChannel (const int midiChannel) const
|
|
|
|
{
|
|
|
|
@@ -83,11 +71,6 @@ void SynthesiserVoice::renderNextBlock (AudioBuffer<double>& outputBuffer, |
|
|
|
|
|
|
|
//==============================================================================
|
|
|
|
Synthesiser::Synthesiser()
|
|
|
|
: sampleRate (0),
|
|
|
|
lastNoteOnCounter (0),
|
|
|
|
minimumSubBlockSize (32),
|
|
|
|
subBlockSubdivisionIsStrict (false),
|
|
|
|
shouldStealNotes (true)
|
|
|
|
{
|
|
|
|
for (int i = 0; i < numElementsInArray (lastPitchWheelValues); ++i)
|
|
|
|
lastPitchWheelValues[i] = 0x2000;
|
|
|
|
@@ -159,13 +142,11 @@ void Synthesiser::setCurrentPlaybackSampleRate (const double newRate) |
|
|
|
if (sampleRate != newRate)
|
|
|
|
{
|
|
|
|
const ScopedLock sl (lock);
|
|
|
|
|
|
|
|
allNotesOff (0, false);
|
|
|
|
|
|
|
|
sampleRate = newRate;
|
|
|
|
|
|
|
|
for (int i = voices.size(); --i >= 0;)
|
|
|
|
voices.getUnchecked (i)->setCurrentPlaybackSampleRate (newRate);
|
|
|
|
for (auto* voice : voices)
|
|
|
|
voice->setCurrentPlaybackSampleRate (newRate);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@@ -230,25 +211,19 @@ void Synthesiser::processNextBlock (AudioBuffer<floatType>& outputAudio, |
|
|
|
}
|
|
|
|
|
|
|
|
// explicit template instantiation
|
|
|
|
template void Synthesiser::processNextBlock<float> (AudioBuffer<float>& outputAudio,
|
|
|
|
const MidiBuffer& midiData,
|
|
|
|
int startSample,
|
|
|
|
int numSamples);
|
|
|
|
template void Synthesiser::processNextBlock<double> (AudioBuffer<double>& outputAudio,
|
|
|
|
const MidiBuffer& midiData,
|
|
|
|
int startSample,
|
|
|
|
int numSamples);
|
|
|
|
template void Synthesiser::processNextBlock<float> (AudioBuffer<float>&, const MidiBuffer&, int, int);
|
|
|
|
template void Synthesiser::processNextBlock<double> (AudioBuffer<double>&, const MidiBuffer&, int, int);
|
|
|
|
|
|
|
|
void Synthesiser::renderVoices (AudioBuffer<float>& buffer, int startSample, int numSamples)
|
|
|
|
{
|
|
|
|
for (int i = voices.size(); --i >= 0;)
|
|
|
|
voices.getUnchecked (i)->renderNextBlock (buffer, startSample, numSamples);
|
|
|
|
for (auto* voice : voices)
|
|
|
|
voice->renderNextBlock (buffer, startSample, numSamples);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Synthesiser::renderVoices (AudioBuffer<double>& buffer, int startSample, int numSamples)
|
|
|
|
{
|
|
|
|
for (int i = voices.size(); --i >= 0;)
|
|
|
|
voices.getUnchecked (i)->renderNextBlock (buffer, startSample, numSamples);
|
|
|
|
for (auto* voice : voices)
|
|
|
|
voice->renderNextBlock (buffer, startSample, numSamples);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Synthesiser::handleMidiEvent (const MidiMessage& m)
|
|
|
|
@@ -298,23 +273,15 @@ void Synthesiser::noteOn (const int midiChannel, |
|
|
|
{
|
|
|
|
const ScopedLock sl (lock);
|
|
|
|
|
|
|
|
for (int i = sounds.size(); --i >= 0;)
|
|
|
|
for (auto* sound : sounds)
|
|
|
|
{
|
|
|
|
SynthesiserSound* const sound = sounds.getUnchecked(i);
|
|
|
|
|
|
|
|
if (sound->appliesToNote (midiNoteNumber)
|
|
|
|
&& sound->appliesToChannel (midiChannel))
|
|
|
|
if (sound->appliesToNote (midiNoteNumber) && sound->appliesToChannel (midiChannel))
|
|
|
|
{
|
|
|
|
// If hitting a note that's still ringing, stop it first (it could be
|
|
|
|
// still playing because of the sustain or sostenuto pedal).
|
|
|
|
for (int j = voices.size(); --j >= 0;)
|
|
|
|
{
|
|
|
|
SynthesiserVoice* const voice = voices.getUnchecked (j);
|
|
|
|
|
|
|
|
if (voice->getCurrentlyPlayingNote() == midiNoteNumber
|
|
|
|
&& voice->isPlayingChannel (midiChannel))
|
|
|
|
for (auto* voice : voices)
|
|
|
|
if (voice->getCurrentlyPlayingNote() == midiNoteNumber && voice->isPlayingChannel (midiChannel))
|
|
|
|
stopVoice (voice, 1.0f, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
startVoice (findFreeVoice (sound, midiChannel, midiNoteNumber, shouldStealNotes),
|
|
|
|
sound, midiChannel, midiNoteNumber, velocity);
|
|
|
|
@@ -363,10 +330,8 @@ void Synthesiser::noteOff (const int midiChannel, |
|
|
|
{
|
|
|
|
const ScopedLock sl (lock);
|
|
|
|
|
|
|
|
for (int i = voices.size(); --i >= 0;)
|
|
|
|
for (auto* voice : voices)
|
|
|
|
{
|
|
|
|
SynthesiserVoice* const voice = voices.getUnchecked (i);
|
|
|
|
|
|
|
|
if (voice->getCurrentlyPlayingNote() == midiNoteNumber
|
|
|
|
&& voice->isPlayingChannel (midiChannel))
|
|
|
|
{
|
|
|
|
@@ -391,13 +356,9 @@ void Synthesiser::allNotesOff (const int midiChannel, const bool allowTailOff) |
|
|
|
{
|
|
|
|
const ScopedLock sl (lock);
|
|
|
|
|
|
|
|
for (int i = voices.size(); --i >= 0;)
|
|
|
|
{
|
|
|
|
SynthesiserVoice* const voice = voices.getUnchecked (i);
|
|
|
|
|
|
|
|
for (auto* voice : voices)
|
|
|
|
if (midiChannel <= 0 || voice->isPlayingChannel (midiChannel))
|
|
|
|
voice->stopNote (1.0f, allowTailOff);
|
|
|
|
}
|
|
|
|
|
|
|
|
sustainPedalsDown.clear();
|
|
|
|
}
|
|
|
|
@@ -406,13 +367,9 @@ void Synthesiser::handlePitchWheel (const int midiChannel, const int wheelValue) |
|
|
|
{
|
|
|
|
const ScopedLock sl (lock);
|
|
|
|
|
|
|
|
for (int i = voices.size(); --i >= 0;)
|
|
|
|
{
|
|
|
|
SynthesiserVoice* const voice = voices.getUnchecked (i);
|
|
|
|
|
|
|
|
for (auto* voice : voices)
|
|
|
|
if (midiChannel <= 0 || voice->isPlayingChannel (midiChannel))
|
|
|
|
voice->pitchWheelMoved (wheelValue);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Synthesiser::handleController (const int midiChannel,
|
|
|
|
@@ -429,40 +386,28 @@ void Synthesiser::handleController (const int midiChannel, |
|
|
|
|
|
|
|
const ScopedLock sl (lock);
|
|
|
|
|
|
|
|
for (int i = voices.size(); --i >= 0;)
|
|
|
|
{
|
|
|
|
SynthesiserVoice* const voice = voices.getUnchecked (i);
|
|
|
|
|
|
|
|
for (auto* voice : voices)
|
|
|
|
if (midiChannel <= 0 || voice->isPlayingChannel (midiChannel))
|
|
|
|
voice->controllerMoved (controllerNumber, controllerValue);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Synthesiser::handleAftertouch (int midiChannel, int midiNoteNumber, int aftertouchValue)
|
|
|
|
{
|
|
|
|
const ScopedLock sl (lock);
|
|
|
|
|
|
|
|
for (int i = voices.size(); --i >= 0;)
|
|
|
|
{
|
|
|
|
SynthesiserVoice* const voice = voices.getUnchecked (i);
|
|
|
|
|
|
|
|
for (auto* voice : voices)
|
|
|
|
if (voice->getCurrentlyPlayingNote() == midiNoteNumber
|
|
|
|
&& (midiChannel <= 0 || voice->isPlayingChannel (midiChannel)))
|
|
|
|
voice->aftertouchChanged (aftertouchValue);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Synthesiser::handleChannelPressure (int midiChannel, int channelPressureValue)
|
|
|
|
{
|
|
|
|
const ScopedLock sl (lock);
|
|
|
|
|
|
|
|
for (int i = voices.size(); --i >= 0;)
|
|
|
|
{
|
|
|
|
SynthesiserVoice* const voice = voices.getUnchecked (i);
|
|
|
|
|
|
|
|
for (auto* voice : voices)
|
|
|
|
if (midiChannel <= 0 || voice->isPlayingChannel (midiChannel))
|
|
|
|
voice->channelPressureChanged (channelPressureValue);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Synthesiser::handleSustainPedal (int midiChannel, bool isDown)
|
|
|
|
@@ -474,25 +419,19 @@ void Synthesiser::handleSustainPedal (int midiChannel, bool isDown) |
|
|
|
{
|
|
|
|
sustainPedalsDown.setBit (midiChannel);
|
|
|
|
|
|
|
|
for (int i = voices.size(); --i >= 0;)
|
|
|
|
{
|
|
|
|
SynthesiserVoice* const voice = voices.getUnchecked (i);
|
|
|
|
|
|
|
|
for (auto* voice : voices)
|
|
|
|
if (voice->isPlayingChannel (midiChannel) && voice->isKeyDown())
|
|
|
|
voice->sustainPedalDown = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
for (int i = voices.size(); --i >= 0;)
|
|
|
|
for (auto* voice : voices)
|
|
|
|
{
|
|
|
|
SynthesiserVoice* const voice = voices.getUnchecked (i);
|
|
|
|
|
|
|
|
if (voice->isPlayingChannel (midiChannel))
|
|
|
|
{
|
|
|
|
voice->sustainPedalDown = false;
|
|
|
|
|
|
|
|
if (! voice->isKeyDown())
|
|
|
|
if (! (voice->isKeyDown() || voice->isSostenutoPedalDown()))
|
|
|
|
stopVoice (voice, 1.0f, true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@@ -506,10 +445,8 @@ void Synthesiser::handleSostenutoPedal (int midiChannel, bool isDown) |
|
|
|
jassert (midiChannel > 0 && midiChannel <= 16);
|
|
|
|
const ScopedLock sl (lock);
|
|
|
|
|
|
|
|
for (int i = voices.size(); --i >= 0;)
|
|
|
|
for (auto* voice : voices)
|
|
|
|
{
|
|
|
|
SynthesiserVoice* const voice = voices.getUnchecked (i);
|
|
|
|
|
|
|
|
if (voice->isPlayingChannel (midiChannel))
|
|
|
|
{
|
|
|
|
if (isDown)
|
|
|
|
@@ -539,13 +476,9 @@ SynthesiserVoice* Synthesiser::findFreeVoice (SynthesiserSound* soundToPlay, |
|
|
|
{
|
|
|
|
const ScopedLock sl (lock);
|
|
|
|
|
|
|
|
for (int i = 0; i < voices.size(); ++i)
|
|
|
|
{
|
|
|
|
SynthesiserVoice* const voice = voices.getUnchecked (i);
|
|
|
|
|
|
|
|
for (auto* voice : voices)
|
|
|
|
if ((! voice->isVoiceActive()) && voice->canPlaySound (soundToPlay))
|
|
|
|
return voice;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (stealIfNoneAvailable)
|
|
|
|
return findVoiceToSteal (soundToPlay, midiChannel, midiNoteNumber);
|
|
|
|
@@ -569,7 +502,7 @@ SynthesiserVoice* Synthesiser::findVoiceToSteal (SynthesiserSound* soundToPlay, |
|
|
|
// - 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);
|
|
|
|
jassert (! voices.isEmpty());
|
|
|
|
|
|
|
|
// These are the voices we want to protect (ie: only steal if unavoidable)
|
|
|
|
SynthesiserVoice* low = nullptr; // Lowest sounding note, might be sustained, but NOT in release phase
|
|
|
|
@@ -579,10 +512,8 @@ SynthesiserVoice* Synthesiser::findVoiceToSteal (SynthesiserSound* soundToPlay, |
|
|
|
Array<SynthesiserVoice*> usableVoices;
|
|
|
|
usableVoices.ensureStorageAllocated (voices.size());
|
|
|
|
|
|
|
|
for (int i = 0; i < voices.size(); ++i)
|
|
|
|
for (auto* voice : voices)
|
|
|
|
{
|
|
|
|
SynthesiserVoice* const voice = voices.getUnchecked (i);
|
|
|
|
|
|
|
|
if (voice->canPlaySound (soundToPlay))
|
|
|
|
{
|
|
|
|
jassert (voice->isVoiceActive()); // We wouldn't be here otherwise
|
|
|
|
@@ -592,7 +523,7 @@ SynthesiserVoice* Synthesiser::findVoiceToSteal (SynthesiserSound* soundToPlay, |
|
|
|
|
|
|
|
if (! voice->isPlayingButReleased()) // Don't protect released notes
|
|
|
|
{
|
|
|
|
const int note = voice->getCurrentlyPlayingNote();
|
|
|
|
auto note = voice->getCurrentlyPlayingNote();
|
|
|
|
|
|
|
|
if (low == nullptr || note < low->getCurrentlyPlayingNote())
|
|
|
|
low = voice;
|
|
|
|
@@ -607,43 +538,25 @@ SynthesiserVoice* Synthesiser::findVoiceToSteal (SynthesiserSound* soundToPlay, |
|
|
|
if (top == low)
|
|
|
|
top = nullptr;
|
|
|
|
|
|
|
|
const int numUsableVoices = usableVoices.size();
|
|
|
|
|
|
|
|
// The oldest note that's playing with the target pitch is ideal..
|
|
|
|
for (int i = 0; i < numUsableVoices; ++i)
|
|
|
|
{
|
|
|
|
SynthesiserVoice* const voice = usableVoices.getUnchecked (i);
|
|
|
|
|
|
|
|
for (auto* voice : usableVoices)
|
|
|
|
if (voice->getCurrentlyPlayingNote() == midiNoteNumber)
|
|
|
|
return voice;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Oldest voice that has been released (no finger on it and not held by sustain pedal)
|
|
|
|
for (int i = 0; i < numUsableVoices; ++i)
|
|
|
|
{
|
|
|
|
SynthesiserVoice* const voice = usableVoices.getUnchecked (i);
|
|
|
|
|
|
|
|
for (auto* voice : usableVoices)
|
|
|
|
if (voice != low && voice != top && voice->isPlayingButReleased())
|
|
|
|
return voice;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Oldest voice that doesn't have a finger on it:
|
|
|
|
for (int i = 0; i < numUsableVoices; ++i)
|
|
|
|
{
|
|
|
|
SynthesiserVoice* const voice = usableVoices.getUnchecked (i);
|
|
|
|
|
|
|
|
for (auto* voice : usableVoices)
|
|
|
|
if (voice != low && voice != top && ! voice->isKeyDown())
|
|
|
|
return voice;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Oldest voice that isn't protected
|
|
|
|
for (int i = 0; i < numUsableVoices; ++i)
|
|
|
|
{
|
|
|
|
SynthesiserVoice* const voice = usableVoices.getUnchecked (i);
|
|
|
|
|
|
|
|
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);
|
|
|
|
|