[sdiy] Fast envelope generation

Olivier Gillet ol.gillet at gmail.com
Wed Dec 19 09:03:29 CET 2012

> So how many multiplies per sample for this table look up?
>    1. interpolation part 1
>    2. interpolation part 2
>    3. scaling the exponential value to fit the step the envelope needs to make
>    4. optionally recalculating the speed for dynamic parameter changes
> Is this correct?

* One multiplication for the interpolation (step = table[n] +
(table[n+1] - table[n]) * fractional_part)
* One multiplication for the scaling (value = start + (end - start) *
step -- during attack ; or value = start - (start - end) * step --
during decay and release).

> Wait a second; shouldn't the speed be a -exp(x) calculation?

Well, it's just another table lookup - that you have to do anyway for
the RC calculation too if you want to present a meaningful range of
times to the user.

> By comparison, the RC simulation is only one multiply per sample.

This argument is debatable on an 8-bit micro - in which a
multiplication longer than 8-bit has to be decomposed into 8x8
multiplications - so a 24-bit or 32-bit multiplication starts costing
you a lot.

With the RC approach, if you want slow times with a fine control over
the time (for example for syncing the envelope to the tempo) you'll
have to switch to a 16-bit or 24-bit multiplication - or have a scheme
in place for reducing the sample rate for getting slower times (which
is going to be difficult to manage if the envelope time is modulated).

You'll going to need more resolution anyway if you want the timing to
be consistent between small steps (a decay from 500 to 100) ; and
large ones (5000 to 1000) because of rounding issues. Here's an

x(0) = 500
x(n+1) = x(n) + ((100 - x(n)) * 1000 >> 16)
y(0) = 5000
y(n+1) = y(n) + ((1000 - y(n)) * 1000 >> 16)

Plot of 10x (blue) and y (green): http://i.imgur.com/eztAS.png

As you can see, the timing is not consistent - and I don't think users
would be happy if a decay to a 90% sustain level would be faster than
a decay to a 10% sustain level. The solution is either to increase the
precision to 24-bit ; or to always compute the exponential ramp full
width (from 0 to 65535), and rescale it - just as it is done with the
table lookup approach. Both solutions make things costlier than the
table lookup.

By contrast, when using the lookup table approach, you can directly
throw a 32-bit register for the phase accumulator (it doesn't hurt,
it's just extra additions), even if the interpolation/scaling is still
done with a resolution of 8-bit. You have independent control on the
resolution of the output value (costs you more multiplications for
interpolation/mixing), and on the resolution on the time axis (costs
you more additions for the phase accumulator). Getting a very good
resolution on the time axis also makes it possible to sync envelopes
to MIDI clock - with exactly the same code as the LFO sync code indeed
(which is also likely to use a phase accumulator with the same time
lookup tables and refresh rates).

My other favorite feature of the lookup table approach is that it's
easy to switch the envelope curve *while preserving all things related
to timing*. You can't do that without lots of complicated parameter
adjustment with the "tweak the RC simulation equation" approach.


More information about the Synth-diy mailing list