Merge pull request #274 from paulfd/voice-cleanup

The synth now resets the voices
This commit is contained in:
Paul Ferrand 2020-06-14 23:29:57 +02:00 committed by GitHub
commit 9857d7b706
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 46 additions and 12 deletions

View file

@ -40,6 +40,12 @@ void sfz::MidiState::noteOffEvent(int delay, int noteNumber, float velocity) noe
}
void sfz::MidiState::allNotesOff(int delay) noexcept
{
for (int note = 0; note < 128; note++)
noteOffEvent(delay, note, 0.0f);
}
void sfz::MidiState::setSampleRate(float sampleRate) noexcept
{
this->sampleRate = sampleRate;

View file

@ -25,6 +25,7 @@ public:
/**
* @brief Update the state after a note on event
*
* @param delay
* @param noteNumber
* @param velocity
*/
@ -33,11 +34,22 @@ public:
/**
* @brief Update the state after a note off event
*
* @param delay
* @param noteNumber
* @param velocity
*/
void noteOffEvent(int delay, int noteNumber, float velocity) noexcept;
/**
* @brief Set all notes off
*
* @param delay
*/
void allNotesOff(int delay) noexcept;
/**
* @brief Get the number of active notes
*/
int getActiveNotes() const noexcept { return activeNotes; }
/**

View file

@ -708,6 +708,9 @@ void sfz::Synth::renderBlock(AudioSpan<float> buffer) noexcept
callbackBreakdown.amplitude += voice->getLastAmplitudeDuration();
callbackBreakdown.filters += voice->getLastFilterDuration();
callbackBreakdown.panning += voice->getLastPanningDuration();
if (voice->toBeCleanedUp())
voice->reset();
}
}
@ -892,6 +895,13 @@ void sfz::Synth::hdcc(int delay, int ccNumber, float normValue) noexcept
return;
}
if (ccNumber == config::allNotesOffCC || ccNumber == config::allSoundOffCC) {
for (auto& voice : voices)
voice->reset();
resources.midiState.allNotesOff(delay);
return;
}
for (auto& voice : voices)
voice->registerCC(delay, ccNumber, normValue);

View file

@ -77,7 +77,7 @@ void sfz::Voice::startVoice(Region* region, int delay, int number, float value,
} else {
currentPromise = resources.filePool.getFilePromise(region->sampleId);
if (currentPromise == nullptr) {
reset();
switchState(State::cleanMeUp);
return;
}
speedRatio = static_cast<float>(currentPromise->sampleRate / this->sampleRate);
@ -139,7 +139,7 @@ void sfz::Voice::release(int delay, bool fastRelease) noexcept
return;
if (egEnvelope.getRemainingDelay() > delay) {
reset();
switchState(State::cleanMeUp);
} else {
egEnvelope.startRelease(delay, fastRelease);
}
@ -173,21 +173,16 @@ void sfz::Voice::registerCC(int delay, int ccNumber, float ccValue) noexcept
if (region == nullptr)
return;
if (state == State::idle)
if (state != State::playing)
return;
if (ccNumber == config::allNotesOffCC || ccNumber == config::allSoundOffCC) {
reset();
return;
}
if (region->checkSustain && noteIsOff && ccNumber == config::sustainCC && ccValue < config::halfCCThreshold)
release(delay);
}
void sfz::Voice::registerPitchWheel(int delay, float pitch) noexcept
{
if (state == State::idle)
if (state != State::playing)
return;
UNUSED(delay);
UNUSED(pitch);
@ -229,6 +224,8 @@ void sfz::Voice::renderBlock(AudioSpan<float> buffer) noexcept
ASSERT(static_cast<int>(buffer.getNumFrames()) <= samplesPerBlock);
buffer.fill(0.0f);
ASSERT(region != nullptr);
const auto delay = min(static_cast<size_t>(initialDelay), buffer.getNumFrames());
auto delayed_buffer = buffer.subspan(delay);
initialDelay -= static_cast<int>(delay);
@ -252,7 +249,7 @@ void sfz::Voice::renderBlock(AudioSpan<float> buffer) noexcept
}
if (!egEnvelope.isSmoothing())
reset();
switchState(State::cleanMeUp);
updateChannelPowers(buffer);
@ -683,7 +680,7 @@ float sfz::Voice::getAverageEnvelope() const noexcept
bool sfz::Voice::releasedOrFree() const noexcept
{
return state == State::idle || egEnvelope.isReleased();
return state != State::playing || egEnvelope.isReleased();
}
uint32_t sfz::Voice::getSourcePosition() const noexcept

View file

@ -53,7 +53,8 @@ public:
enum class State {
idle,
playing
playing,
cleanMeUp,
};
class StateListener {
@ -61,6 +62,11 @@ public:
virtual void onVoiceStateChanged(NumericId<Voice> /*id*/, State /*state*/) {}
};
/**
* @brief Return true if the voice is to be cleaned up (zombie state)
*/
bool toBeCleanedUp() const { return state == State::cleanMeUp; }
/**
* @brief Sets the listener which is called when the voice state changes.
*/

View file

@ -152,15 +152,18 @@ TEST_CASE("[Synth] Releasing before the EG started smoothing (initial delay) kil
{
sfz::Synth synth;
synth.setSamplesPerBlock(1024);
sfz::AudioBuffer<float> buffer { 2, 1024 };
synth.setNumVoices(1);
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/delay_release.sfz");
synth.noteOn(0, 60, 63);
REQUIRE( !synth.getVoiceView(0)->isFree() );
synth.noteOff(100, 60, 63);
synth.renderBlock(buffer);
REQUIRE( synth.getVoiceView(0)->isFree() );
synth.noteOn(200, 60, 63);
REQUIRE( !synth.getVoiceView(0)->isFree() );
synth.noteOff(1000, 60, 63);
synth.renderBlock(buffer);
REQUIRE( !synth.getVoiceView(0)->isFree() );
}