Sustain tweaks:

- Separate the sustain value from the sustain threshold after which the envelope goes from decay to sustain
- If the sustain value is set to 0, the envelope is freerunning
This commit is contained in:
Paul Ferrand 2020-08-19 19:13:50 +02:00
parent f6e0bb8b3f
commit f6f1e68f18
3 changed files with 56 additions and 5 deletions

View file

@ -48,12 +48,14 @@ void ADSREnvelope<Type>::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<Type>::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<Type>::getBlock(absl::Span<Type> 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;
}

View file

@ -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 };

View file

@ -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<float> buffer { 2, 256 };
synth.loadSfzString(fs::current_path(), R"(
<region> key=60 end=0 sample=*sine
<region> key=61 end=0 sample=*silence
<region> key=62 sample=*sine off_by=2
<region> 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<float> buffer { 2, 256 };
synth.loadSfzString(fs::current_path(), R"(
<region> key=60 sample=*sine ampeg_sustain=0
<region> 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" );
}