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 void sfz::MidiState::setSampleRate(float sampleRate) noexcept
{ {
this->sampleRate = sampleRate; this->sampleRate = sampleRate;

View file

@ -25,6 +25,7 @@ public:
/** /**
* @brief Update the state after a note on event * @brief Update the state after a note on event
* *
* @param delay
* @param noteNumber * @param noteNumber
* @param velocity * @param velocity
*/ */
@ -33,11 +34,22 @@ public:
/** /**
* @brief Update the state after a note off event * @brief Update the state after a note off event
* *
* @param delay
* @param noteNumber * @param noteNumber
* @param velocity * @param velocity
*/ */
void noteOffEvent(int delay, int noteNumber, float velocity) noexcept; 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; } 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.amplitude += voice->getLastAmplitudeDuration();
callbackBreakdown.filters += voice->getLastFilterDuration(); callbackBreakdown.filters += voice->getLastFilterDuration();
callbackBreakdown.panning += voice->getLastPanningDuration(); 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; return;
} }
if (ccNumber == config::allNotesOffCC || ccNumber == config::allSoundOffCC) {
for (auto& voice : voices)
voice->reset();
resources.midiState.allNotesOff(delay);
return;
}
for (auto& voice : voices) for (auto& voice : voices)
voice->registerCC(delay, ccNumber, normValue); voice->registerCC(delay, ccNumber, normValue);

View file

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

View file

@ -53,7 +53,8 @@ public:
enum class State { enum class State {
idle, idle,
playing playing,
cleanMeUp,
}; };
class StateListener { class StateListener {
@ -61,6 +62,11 @@ public:
virtual void onVoiceStateChanged(NumericId<Voice> /*id*/, State /*state*/) {} 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. * @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; sfz::Synth synth;
synth.setSamplesPerBlock(1024); synth.setSamplesPerBlock(1024);
sfz::AudioBuffer<float> buffer { 2, 1024 };
synth.setNumVoices(1); synth.setNumVoices(1);
synth.loadSfzFile(fs::current_path() / "tests/TestFiles/delay_release.sfz"); synth.loadSfzFile(fs::current_path() / "tests/TestFiles/delay_release.sfz");
synth.noteOn(0, 60, 63); synth.noteOn(0, 60, 63);
REQUIRE( !synth.getVoiceView(0)->isFree() ); REQUIRE( !synth.getVoiceView(0)->isFree() );
synth.noteOff(100, 60, 63); synth.noteOff(100, 60, 63);
synth.renderBlock(buffer);
REQUIRE( synth.getVoiceView(0)->isFree() ); REQUIRE( synth.getVoiceView(0)->isFree() );
synth.noteOn(200, 60, 63); synth.noteOn(200, 60, 63);
REQUIRE( !synth.getVoiceView(0)->isFree() ); REQUIRE( !synth.getVoiceView(0)->isFree() );
synth.noteOff(1000, 60, 63); synth.noteOff(1000, 60, 63);
synth.renderBlock(buffer);
REQUIRE( !synth.getVoiceView(0)->isFree() ); REQUIRE( !synth.getVoiceView(0)->isFree() );
} }