Merge pull request #734 from jpcima/adsr-smooth-transitions

Adsr smooth transitions
This commit is contained in:
JP Cimalando 2021-03-23 00:21:53 +01:00 committed by GitHub
commit 1c45b7a491
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 36 additions and 72 deletions

View file

@ -44,9 +44,8 @@ void ADSREnvelope::reset(const EGDescription& desc, const Region& region, const
this->decayRate = secondsToExpRate(desc.getDecay(state, velocity));
this->releaseRate = secondsToExpRate(desc.getRelease(state, velocity));
this->hold = secondsToSamples(desc.getHold(state, velocity));
this->peak = 1.0;
this->sustain = normalizePercents(desc.getSustain(state, velocity));
this->start = this->peak * normalizePercents(desc.getStart(state, velocity));
this->start = normalizePercents(desc.getStart(state, velocity));
releaseDelay = 0;
sustainThreshold = this->sustain + config::virtuallyZero;
@ -59,63 +58,13 @@ void ADSREnvelope::reset(const EGDescription& desc, const Region& region, const
currentState = State::Delay;
}
Float ADSREnvelope::getNextValue() noexcept
{
if (shouldRelease && releaseDelay-- == 0)
currentState = State::Release;
switch (currentState) {
case State::Delay:
if (delay-- > 0)
return start;
currentState = State::Attack;
// fallthrough
case State::Attack:
currentValue += peak * attackStep;
if (currentValue < peak)
return currentValue;
currentState = State::Hold;
currentValue = peak;
// fallthrough
case State::Hold:
if (hold-- > 0)
return currentValue;
currentState = State::Decay;
// fallthrough
case State::Decay:
currentValue *= decayRate;
if (currentValue > sustainThreshold )
return currentValue;
currentState = State::Sustain;
currentValue = sustain;
// fallthrough
case State::Sustain:
if (freeRunning)
shouldRelease = true;
return currentValue;
case State::Release:
currentValue *= releaseRate;
if (currentValue > config::egReleaseThreshold)
return currentValue;
currentState = State::Done;
currentValue = 0.0;
// fallthrough
default:
return 0.0;
}
}
void ADSREnvelope::getBlock(absl::Span<Float> output) noexcept
{
State currentState = this->currentState;
Float currentValue = this->currentValue;
bool shouldRelease = this->shouldRelease;
int releaseDelay = this->releaseDelay;
Float transitionDelta = this->transitionDelta;
while (!output.empty()) {
size_t count = 0;
@ -130,6 +79,8 @@ void ADSREnvelope::getBlock(absl::Span<Float> output) noexcept
size = std::min<size_t>(size, releaseDelay);
}
Float previousValue;
switch (currentState) {
case State::Delay:
while (count < size && delay-- > 0) {
@ -140,10 +91,10 @@ void ADSREnvelope::getBlock(absl::Span<Float> output) noexcept
currentState = State::Attack;
break;
case State::Attack:
while (count < size && (currentValue += peak * attackStep) < peak)
while (count < size && (currentValue += attackStep) < 1)
output[count++] = currentValue;
if (currentValue >= peak) {
currentValue = peak;
if (currentValue >= 1) {
currentValue = 1;
currentState = State::Hold;
}
break;
@ -157,8 +108,9 @@ void ADSREnvelope::getBlock(absl::Span<Float> output) noexcept
while (count < size && (currentValue *= decayRate) > sustain)
output[count++] = currentValue;
if (currentValue <= sustainThreshold) {
currentValue = sustain;
currentState = State::Sustain;
currentValue = std::max(sustain, currentValue);
transitionDelta = (sustain - currentValue) / (sampleRate * config::egTransitionTime);
}
break;
case State::Sustain:
@ -166,16 +118,27 @@ void ADSREnvelope::getBlock(absl::Span<Float> output) noexcept
shouldRelease = true;
break;
}
count = size;
currentValue = sustain;
sfz::fill(output.first(count), currentValue);
while (count < size) {
currentValue = std::max(sustain, currentValue + transitionDelta);
output[count++] = currentValue;
}
break;
case State::Release:
previousValue = currentValue;
while (count < size && (currentValue *= releaseRate) > config::egReleaseThreshold)
output[count++] = currentValue;
output[count++] = previousValue = currentValue;
if (currentValue <= config::egReleaseThreshold) {
currentValue = 0;
currentState = State::Fadeout;
currentValue = previousValue;
transitionDelta = -currentValue / (sampleRate * config::egTransitionTime);
}
break;
case State::Fadeout:
while (count < size && (currentValue += transitionDelta) > 0)
output[count++] = currentValue;
if (currentValue <= 0) {
currentState = State::Done;
currentValue = 0;
}
break;
default:
@ -195,6 +158,7 @@ void ADSREnvelope::getBlock(absl::Span<Float> output) noexcept
this->currentValue = currentValue;
this->shouldRelease = shouldRelease;
this->releaseDelay = releaseDelay;
this->transitionDelta = transitionDelta;
ASSERT(!hasNanInf(output));
}

View file

@ -30,12 +30,6 @@ public:
* @param velocity
*/
void reset(const EGDescription& desc, const Region& region, const MidiState& state, int delay, float velocity, float sampleRate) noexcept;
/**
* @brief Get the next value for the envelope
*
* @return Float
*/
Float getNextValue() noexcept;
/**
* @brief Get a block of values for the envelope. This method tries hard to be efficient
* and hopefully it is.
@ -68,7 +62,7 @@ public:
* @return true
* @return false
*/
bool isReleased() const noexcept { return currentState == State::Release || shouldRelease; }
bool isReleased() const noexcept { return currentState >= State::Release || shouldRelease; }
/**
* @brief Get the remaining delay samples
*
@ -89,6 +83,7 @@ private:
Decay,
Sustain,
Release,
Fadeout,
Done
};
State currentState { State::Done };
@ -99,12 +94,12 @@ private:
Float releaseRate { 0 };
int hold { 0 };
Float start { 0 };
Float peak { 0 };
Float sustain { 0 };
Float sustainThreshold { config::virtuallyZero };
int releaseDelay { 0 };
bool shouldRelease { false };
bool freeRunning { false };
Float transitionDelta {};
LEAK_DETECTOR(ADSREnvelope);
};

View file

@ -108,6 +108,11 @@ namespace config {
finished.
*/
constexpr float egReleaseThreshold = 1e-4;
/**
Duration of a linear transition user to smooth cases of otherwise
immediate level transitions. (eg. decay->sustain or release->off)
*/
constexpr float egTransitionTime = 50e-3;
/**
Default metadata for MIDIName documents
*/

View file

@ -517,7 +517,7 @@ TEST_CASE("[Files] Off modes")
REQUIRE( synth.getNumActiveVoices() == 3 );
REQUIRE( numPlayingVoices(synth) == 1 );
AudioBuffer<float> buffer { 2, 256 };
for (unsigned i = 0; i < 10; ++i) // Not enough for the "normal" voice to die
for (unsigned i = 0; i < 20; ++i) // Not enough for the "normal" voice to die
synth.renderBlock(buffer);
REQUIRE( synth.getNumActiveVoices() == 2 );
REQUIRE( fastVoice->isFree() );

View file

@ -31,7 +31,7 @@ TEST_CASE("[Synth] Play and check active voices")
synth.noteOn(0, 36, 89);
REQUIRE(synth.getNumActiveVoices() == 2);
// Render for a while
for (int i = 0; i < 200; ++i)
for (int i = 0; i < 300; ++i)
synth.renderBlock(buffer);
REQUIRE(synth.getNumActiveVoices() == 0);
}