Skip to content

Instantly share code, notes, and snippets.

@borogove
Created March 12, 2022 20:01
Show Gist options
  • Save borogove/e7b64d50fcc1ffa18ea7527b9312d651 to your computer and use it in GitHub Desktop.
Save borogove/e7b64d50fcc1ffa18ea7527b9312d651 to your computer and use it in GitHub Desktop.
// Weirdolope - a minimal one-control envelope generator
// (c) 2022 Russell Borogove
// Use at your own risk
// This envelope generator uses floating point math;
// if your MCU doesn't have floating-point hardware
// support you're gonna have a bad time.
// Call updateEnvelope() periodically at a rate >= 1000 Hz
// Set envelopeState to ENVELOPE_STATE_ATTACK to
// begin the envelope and ENVELOPE_STATE_RELEASE to
// release it.
// envelopeLevel will range from 0.0 to 1.0.
// Implement setEnvelope() to scale that value
// appropriately and send it to your DAC.
// Thanks to "synthesizers, yo" user meem for
// getting me some nice clean samples of guitars
// and pianos that I could measure some
// representative envelope time curves from.
// attack rate, amplitude change per millisec
float AttackRateTable[9] =
{
1.00000f, // instant
0.08333f, // guitar
0.04000f, // bass
0.01667f, // piano bass
0.10000f, // piano treble
0.20000f, // chiff organ
0.10000f, // synth lead
0.00250f, // synth pad
0.00050f, // long ambient pad
};
float DecayRateTable[9] =
{
0.96555f, // instant
0.99929f, // guitar
0.98851f, // bass
0.99934f, // piano bass
0.98746f, // piano treble
0.98000f, // chiff organ
0.99644f, // synth lead
0.99978f, // synth pad
0.99988f, // long ambient pad
};
// End-of-decay level
float SustainLevelTable[9] =
{
0.70f, // instant
0.72f, // guitar
0.75f, // bass
0.70f, // piano bass
0.70f, // piano treble
0.70f, // chiff organ
0.70f, // synth lead
0.80f, // synth pad
0.90f, // long ambient pad
};
// Unlike a typical synth ADSR envelope, we use an
// exponentially decaying sustain.
float SustainRateTable[9] =
{
0.99880f, // instant
0.99928f, // guitar
0.99957f, // bass
0.99913f, // piano bass
0.99015f, // piano treble
0.99977f, // chiff organ
0.99991f, // synth lead
1.0f, // synth pad
1.0f, // long ambient pad
};
// Unlike a typical synth ADSR envelope, we use an
// exponentially decaying sustain
float ReleaseRateTable[9] =
{
0.99312f, // instant
0.99421f, // guitar
0.99484f, // bass
0.99092f, // piano bass
0.97727f, // piano treble
0.99500f, // chiff organ
0.99500f, // synth lead
0.99650f, // synth pad
0.99800f, // long ambient pad
};
int EnvA = 0;
int EnvB = 1;
float EnvAlpha = 0.0f;
const int ENVELOPE_STATE_IDLE = 0;
const int ENVELOPE_STATE_ATTACK = 1;
const int ENVELOPE_STATE_DECAY = 2;
const int ENVELOPE_STATE_SUSTAIN = 3;
const int ENVELOPE_STATE_RELEASE = 4;
int envelopeState = ENVELOPE_STATE_IDLE;
float envelopeLevel = 0.0f;
// silence the envelope when it reaches this level, well below 12 bit dac resolution.
float envelopeStopLevel = 0.0001f;
unsigned long nextEnvelopeUpdate = 0;
float lerp( float y0, float y1, float alpha )
{
return (y1-y0)*alpha + y0;
}
void setEnvelope()
{
// unipolar
cvValues[ CV_CHANNEL_ENVELOPE ] = envelopeLevel;
}
void updateEnvelope()
{
int envelopeControl = analogRead( PIN_ENVELOPE );
float x = constrain( 8.0f * (float)(envelopeControl-10) / 1003.1f, 0.0f, 7.999f );
EnvA = int(x);
EnvB = EnvA+1;
EnvAlpha = constrain( x - EnvA, 0.0f, 1.0f );
long ttg = nextEnvelopeUpdate - micros();
float delta = 0.0f;
float damp = 1.0f;
float sustainLevel = 0.7f;
if (ttg < 0)
{
nextEnvelopeUpdate += 1000;
switch (envelopeState)
{
case ENVELOPE_STATE_IDLE:
envelopeLevel = 0.0f;
break;
case ENVELOPE_STATE_ATTACK:
delta = lerp( AttackRateTable[EnvA], AttackRateTable[EnvB], EnvAlpha );
envelopeLevel += delta;
if (envelopeLevel >= 1.0f)
{
envelopeLevel = 1.0f;
envelopeState = ENVELOPE_STATE_DECAY;
}
break;
case ENVELOPE_STATE_DECAY:
damp = lerp( DecayRateTable[EnvA], DecayRateTable[EnvB], EnvAlpha );
envelopeLevel *= damp;
sustainLevel = lerp(SustainLevelTable[EnvA],SustainLevelTable[EnvB],EnvAlpha);
if (envelopeLevel <= sustainLevel)
{
envelopeState = ENVELOPE_STATE_SUSTAIN;
}
break;
case ENVELOPE_STATE_SUSTAIN:
damp = lerp( SustainRateTable[EnvA], SustainRateTable[EnvB], EnvAlpha );
envelopeLevel *= damp;
if (envelopeLevel <= envelopeStopLevel)
{
envelopeState = ENVELOPE_STATE_IDLE;
}
break;
case ENVELOPE_STATE_RELEASE:
damp = lerp( ReleaseRateTable[EnvA], ReleaseRateTable[EnvB], EnvAlpha );
envelopeLevel *= damp;
if (envelopeLevel <= envelopeStopLevel)
{
envelopeState = ENVELOPE_STATE_IDLE;
}
break;
}
setEnvelope();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment