Merge pull request #734 from jpcima/adsr-smooth-transitions
Adsr smooth transitions
This commit is contained in:
commit
1c45b7a491
5 changed files with 36 additions and 72 deletions
|
|
@ -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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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() );
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue