diff --git a/src/sfizz/ADSREnvelope.cpp b/src/sfizz/ADSREnvelope.cpp index 78415f8a..8d92ea10 100644 --- a/src/sfizz/ADSREnvelope.cpp +++ b/src/sfizz/ADSREnvelope.cpp @@ -48,12 +48,14 @@ void ADSREnvelope::reset(const EGDescription& desc, const Region& region, this->hold = secondsToSamples(desc.getHold(state, velocity)); this->peak = 1.0; this->sustain = normalizePercents(desc.getSustain(state, velocity)); - this->sustain = max(this->sustain, config::virtuallyZero); this->start = this->peak * normalizePercents(desc.getStart(state, velocity)); releaseDelay = 0; + sustainThreshold = this->sustain + config::virtuallyZero; shouldRelease = false; - freeRunning = ((region.trigger == SfzTrigger::release) + freeRunning = ( + (region.trigger == SfzTrigger::release) + || (this->sustain == 0.0f) || (region.trigger == SfzTrigger::release_key) || (region.loopMode == SfzLoopMode::one_shot && (region.isGenerator() || region.oscillator))); currentValue = this->start; @@ -89,7 +91,7 @@ Type ADSREnvelope::getNextValue() noexcept // fallthrough case State::Decay: currentValue *= decayRate; - if (currentValue > sustain) + if (currentValue > sustainThreshold ) return currentValue; currentState = State::Sustain; @@ -159,7 +161,7 @@ void ADSREnvelope::getBlock(absl::Span output) noexcept case State::Decay: while (count < size && (currentValue *= decayRate) > sustain) output[count++] = currentValue; - if (currentValue <= sustain) { + if (currentValue <= sustainThreshold) { currentValue = sustain; currentState = State::Sustain; } diff --git a/src/sfizz/ADSREnvelope.h b/src/sfizz/ADSREnvelope.h index 892388f7..eafd2f90 100644 --- a/src/sfizz/ADSREnvelope.h +++ b/src/sfizz/ADSREnvelope.h @@ -102,6 +102,7 @@ private: Type start { 0 }; Type peak { 0 }; Type sustain { 0 }; + Type sustainThreshold { config::virtuallyZero }; int releaseDelay { 0 }; bool shouldRelease { false }; bool freeRunning { false }; diff --git a/tests/SynthT.cpp b/tests/SynthT.cpp index 5d167385..78f017b6 100644 --- a/tests/SynthT.cpp +++ b/tests/SynthT.cpp @@ -1167,6 +1167,55 @@ TEST_CASE("[Synth] end=-1 voices are immediately killed after triggering but the REQUIRE( numPlayingVoices(synth) == 0 ); } +TEST_CASE("[Synth] end=0 voices are immediately killed after triggering but they kill other voices") +{ + sfz::Synth synth; + sfz::AudioBuffer buffer { 2, 256 }; + + synth.loadSfzString(fs::current_path(), R"( + key=60 end=0 sample=*sine + key=61 end=0 sample=*silence + key=62 sample=*sine off_by=2 + key=63 end=0 sample=*saw group=2 + )"); + synth.noteOn(0, 60, 85); + REQUIRE( synth.getNumActiveVoices(true) == 0 ); + synth.noteOn(0, 61, 85); + REQUIRE( synth.getNumActiveVoices(true) == 0 ); + synth.noteOn(0, 62, 85); + REQUIRE( synth.getNumActiveVoices(true) == 1 ); + REQUIRE( numPlayingVoices(synth) == 1 ); + synth.noteOn(1, 63, 85); + synth.renderBlock(buffer); + REQUIRE( numPlayingVoices(synth) == 0 ); +} + +TEST_CASE("[Synth] ampeg_sustain = 0 puts the ampeg envelope in free-running mode, which kills the voice almost instantly in most cases") +{ + sfz::Synth synth; + sfz::AudioBuffer buffer { 2, 256 }; + + synth.loadSfzString(fs::current_path(), R"( + key=60 sample=*sine ampeg_sustain=0 + key=61 sample=*sine ampeg_sustain=0 ampeg_attack=0.1 ampeg_decay=0.1 + )"); + + synth.noteOn(0, 60, 85); + REQUIRE( synth.getNumActiveVoices(true) == 1 ); + synth.renderBlock(buffer); + REQUIRE( synth.getNumActiveVoices(true) == 0 ); + synth.noteOn(0, 61, 85); + REQUIRE( synth.getNumActiveVoices(true) == 1 ); + // Render a bit; this does not kill the voice + for (unsigned i = 0; i < 5; ++i) + synth.renderBlock(buffer); + REQUIRE( synth.getNumActiveVoices(true) == 1 ); + // Render about half a second + for (unsigned i = 0; i < 100; ++i) + synth.renderBlock(buffer); + REQUIRE( synth.getNumActiveVoices(true) == 0 ); +} + TEST_CASE("[Synth] Off by standard") { sfz::Synth synth; @@ -1222,4 +1271,3 @@ TEST_CASE("[Synth] Off by same note") auto playingVoices = getPlayingVoices(synth); REQUIRE( playingVoices.front()->getRegion()->sampleId.filename() == "*triangle" ); } -