//------------------------------------------------------------------------------------------------------------------------------------------
// Get the phase parameters for the given envelope, phase and current envelope level
//------------------------------------------------------------------------------------------------------------------------------------------
static EnvPhaseParams getEnvPhaseParams(const AdsrEnvelope env, const EnvPhase phase, const int16_t envLevel) noexcept {
// Envelope level shouldn't be negative, but just in case...
const int32_t absEnvLevel = std::abs((int32_t) envLevel);
// Gather basic info for the envelope phase
int32_t targetLevel;
int32_t stepUnscaled;
uint32_t stepScale;
bool bExponential;
switch (phase) {
// Attack phase: ramps up to the maximum volume always
case EnvPhase::Attack: {
targetLevel = MAX_ENV_LEVEL;
stepScale = env.attackShift;
stepUnscaled = 7 - (int32_t) env.attackStep;
bExponential = env.bAttackExp;
} break;
// Decay phase: ramps down to the sustain level and always exponentially
case EnvPhase::Decay: {
targetLevel = std::min<int32_t>(((int32_t) env.sustainLevel + 1) * 2048, MAX_ENV_LEVEL);
stepScale = env.decayShift;
stepUnscaled = -8;
bExponential = true;
} break;
// Sustain phase: has no target level (-1, lasts forever) and can ramp up or down
case EnvPhase::Sustain: {
targetLevel = -1;
stepScale = env.sustainShift;
stepUnscaled = (env.bSustainDec) ? (int32_t) env.sustainStep - 8 : 7 - (int32_t) env.sustainStep;
bExponential = env.bSustainExp;
} break;
// Release, off or unknown phase: fade out to zero
case EnvPhase::Release:
case EnvPhase::Off:
default: {
targetLevel = MIN_ENV_LEVEL;
stepScale = env.releaseShift;
stepUnscaled = -8;
bExponential = env.bReleaseExp;
} break;
}
// Scale the step accordingly and decide how many cycles an envelope step takes
const int32_t stepScaleMultiplier = 1 << std::max<int32_t>(0, 11 - stepScale);
EnvPhaseParams params;
params.targetLevel = targetLevel;
params.stepCycles = 1 << std::max<int32_t>(0, stepScale - 11);
params.step = stepUnscaled * stepScaleMultiplier;
if (bExponential) {
// Adjustments based on the current envelope level when the envelope mode is 'exponential'.
// Slower fade-outs as the envelope level decreases & 4x slower fade-ins when envelope level surpasses '0x6000'.
if ((stepUnscaled > 0) && (absEnvLevel > 0x6000)) {
params.stepCycles *= 4;
}
else if (stepUnscaled < 0) {
params.step = (int32_t)(((int64_t) params.step * absEnvLevel) >> 15);
}
}
return params;
}
//------------------------------------------------------------------------------------------------------------------------------------------
// Step the ADSR envelope for the given voice
//------------------------------------------------------------------------------------------------------------------------------------------
static void stepVoiceEnvelope(Voice& voice) noexcept {
// Don't process the envelope if we must wait a few more cycles
if (voice.envWaitCycles > 0) {
voice.envWaitCycles--;
if (voice.envWaitCycles > 0)
return;
}
// Step the envelope in it's current phase and compute the new envelope level
const EnvPhaseParams envParams = getEnvPhaseParams(voice.env, voice.envPhase, voice.envLevel);
int32_t newEnvLevel = std::clamp<int32_t>(voice.envLevel + envParams.step, MIN_ENV_LEVEL, MAX_ENV_LEVEL);
// Do state transitions when ramping up or down, unless we're in the 'sustain' phase (targetLevel < 0)
bool bReachedTargetLevel = false;
if (envParams.targetLevel >= 0) {
if (envParams.step > 0) {
bReachedTargetLevel = (newEnvLevel >= envParams.targetLevel);
} else if (envParams.step < 0) {
bReachedTargetLevel = (newEnvLevel <= envParams.targetLevel);
}
}
if (bReachedTargetLevel) {
newEnvLevel = envParams.targetLevel;
voice.envPhase = getNextEnvPhase(voice.envPhase);
voice.envWaitCycles = 0;
} else {
voice.envWaitCycles = envParams.stepCycles;
}
voice.envLevel = (int16_t) newEnvLevel;
}