I am working on modifying a simple audio envelope posted here. The posted version runs simple exponential attack, decay, sustain, and release cases of a switch and progresses through the envelope based on expected sample length duration of each phase.
I require more complex and individualized equations for each case. I could calculate the predicted length of each case based on my equations and maintain this time-based methodology. However, I was thinking it would be easier to control the progress through the cases based on amplitude cutoffs. ie. If level >= 1.0, move from attack case to decay case .
But I am having trouble making this work or figuring out why it won't.
Here is the pertinent original code:
enum EnvelopeStage {
ENVELOPE_STAGE_OFF = 0,
ENVELOPE_STAGE_ATTACK,
ENVELOPE_STAGE_DECAY,
ENVELOPE_STAGE_SUSTAIN,
ENVELOPE_STAGE_RELEASE,
kNumEnvelopeStages
};
double EnvelopeGenerator::nextSample() {
if (currentStage != ENVELOPE_STAGE_OFF &&
currentStage != ENVELOPE_STAGE_SUSTAIN) {
if (currentSampleIndex == nextStageSampleIndex) {
EnvelopeStage newStage = static_cast<EnvelopeStage>(
(currentStage + 1) % kNumEnvelopeStages
);
enterStage(newStage);
}
currentLevel *= multiplier;
currentSampleIndex++;
}
return currentLevel;
}
void EnvelopeGenerator::calculateMultiplier(double startLevel,
double endLevel,
unsigned long long lengthInSamples) {
multiplier = 1.0 + (log(endLevel) - log(startLevel)) / (lengthInSamples);
}
void EnvelopeGenerator::enterStage(EnvelopeStage newStage) {
currentStage = newStage;
currentSampleIndex = 0;
if (currentStage == ENVELOPE_STAGE_OFF ||
currentStage == ENVELOPE_STAGE_SUSTAIN) {
nextStageSampleIndex = 0;
} else {
nextStageSampleIndex = stageValue[currentStage] * sampleRate;
}
switch (newStage) {
case ENVELOPE_STAGE_OFF:
currentLevel = 0.0;
multiplier = 1.0;
break;
case ENVELOPE_STAGE_ATTACK:
currentLevel = minimumLevel;
calculateMultiplier(currentLevel,
1.0,
nextStageSampleIndex);
break;
case ENVELOPE_STAGE_DECAY:
currentLevel = 1.0;
calculateMultiplier(currentLevel,
fmax(stageValue[ENVELOPE_STAGE_SUSTAIN], minimumLevel),
nextStageSampleIndex);
break;
case ENVELOPE_STAGE_SUSTAIN:
currentLevel = stageValue[ENVELOPE_STAGE_SUSTAIN];
multiplier = 1.0;
break;
case ENVELOPE_STAGE_RELEASE:
// We could go from ATTACK/DECAY to RELEASE,
// so we're not changing currentLevel here.
calculateMultiplier(currentLevel,
minimumLevel,
nextStageSampleIndex);
break;
default:
break;
}
}
So progress through the cases is being controlled by double EnvelopeGenerator::nextSample()
.
Here is what I am trying instead to do to control it differently:
double EnvelopeGenerator::nextSample() {
if (currentStage = ENVELOPE_STAGE_ATTACK) {
currentLevel = pow(currentSampleIndex / 30000.0f, 2.0f);
currentSampleIndex++;
if (currentLevel >= 1.0) {
enterStage(ENVELOPE_STAGE_DECAY);
}
}
else if (currentStage = ENVELOPE_STAGE_DECAY) {
currentLevel = 1/ pow((currentSampleIndex / 30000.0f)+1, 2.0f);
currentSampleIndex++;
if (currentLevel = 0.1) {
enterStage(ENVELOPE_STAGE_RELEASE);
}
}
else if (currentStage = ENVELOPE_STAGE_RELEASE) {
currentLevel = currentLevel = 1 / pow((currentSampleIndex / 30000.0f) + 1, 2.0f);
currentSampleIndex++;
if (currentLevel = 0.0001) {
enterStage(ENVELOPE_STAGE_OFF);
}
}
else {}
Those are not the final equations I need for each case. They're just simple test equations. But currently with this instead for the nextSample
function, I am just getting a looping attack phase indefinitely.
I cannot understand why it is looping infinitely on attack. I thought I made it clear that if the level hits 1.0, it should switch to decay case.
What am I doing wrong and how do I fix it? I do not have sufficient experience with C++ to know how to do this properly.