|Home||Papers||Projects||Code Fragments||Dissertations||Presentations||Posters||Reports||Proposals||Lectures given||Course notes|
Fast Exponential Envelopes
Werner Van Belle1 - email@example.com, firstname.lastname@example.org
Abstract : Computing ADSR curves is a task any synth has to deal with. In this article I present a method to compute exponential patches quickly and incrementally. Namely by multiplying the last envelopevalue with a constant multiplier (r) and then adding a constant delta (d) to it. This solution is used in the Audiotool Heisenberg synth to generate ADSR curves for pitch as well as amplitude.
ADSR Envelopes, Exponential Patches, Incremental offsetted exponential
Analytic Form 1
Analytic Form 2
Differential of y(x)
Integrating the Differential of y(x)
Analytic form 3
Scaling and Offsetting
A Normalized Curve
Determining the multiplier
|Computing the serie of powers|
Analytic form 4
Incremental calculation based on the differential
Acknowledgments & History
The presented solution is based on exponential patches of which the linearity can be chosen as wanted.
|Illustrating the various curves that can be drawn.|
The following equation
can be used to draw arbitrary exponential curves through three points
This can be done by choosing correct values for r and d, as follows:
To calculate , given d, r, and y1 values:
The above algorithm can be computed in .
Equation main describes how can be incrementally calculated. It actually was created first because it is fast and because we assumed that we could draw any exponential patch with it. Further study provided than various analytic forms.
To determine a direct form of equation main, we set up the following series of dependencies, that then provides us with the resulting equation:
The direct form is thus equivalent to
To obtain a second analytic form we can investigate the differential of and see whether the integration is sensible.
To calculate the differential we subtract y(x) from y(x+1) and name the result d(x). d(x) will thus give us at each x position the value that should be added to the current y.
This function is an exponential, indicating that we could calculate y(x) slightly different, namely by calculating the differential in an incremental fashion and accumulating these values. Section Incremental based on differential focuses on an alternate method to compute y based on this insight. At the moment however, a differential that is an exponential begs to be integrated.
Integrating the equation of the differential gives a y-translated version of y(x), namely
At x=0, a translated version of the integral should run through y1, which gives the following
Thus, another analytic form is
Equation analytical2 is a tricky beast to calculate because the ratio r can be 1, in which case log(r)=0, resulting in a division by 0. Secondly this equation is made using integration and differentiation which both assumed a continuous space. For integer values this will thus not work entirely correctly. See section Analytic form 4 for a better calculation.
In this section we study how we can offset and scale the curve. That will eventually allow us to write a normalized curve that goes from y1=0 to y2=1. That normalized curve can then be solved against r and d and subsequently scaled and translated back to its wanted position.
Because it is necessary below that we denote exactly which function we are talking about we will no longer write y(x), but rather y(x,r,d,i) in which x the point is where we want to know the value of the curve. r specifies the ratio multiplier. d specifies the delta added with each cycle and i specifies the initial condition. y(x,r,d,i) will be a non-normalized curve. The normalized curve will be called
It will be normalized such that
We now aim to understand how offsetting a curve affects the values r and d. Or rather how we can, given a type of y-function (called f), modify r and d such that the new function g is shifted along the y axis.
The above equation can be satisfied by simply ensuring that the shape of f and g are the same. (This is possible because the values at x=0 is for both curves already the same. Consequently we will only be looking at f' and g'.
If we now set we obtain an expression for
This result allows us to write the following
To scale an exponential patch we merely need to multiply d with the required scaling factor m, as shown below
Once we know $r$ for an unscaled, zero-offset function, we can first scale it to reach and the offset it to start at . This is done as follows:
Given that we can scale and offset the curve any way we want, it is time to create a normalized curve that goes from over to .
To start at position 0, it suffices to set the fourth argument to y to 0.
To scale the curve such that after x2 steps is reached, it suffices to set d to the inverse of the value at that position.
The calculation of>/p>
which thus means that
and that the equation for the unscaled curve is
Which is an interesting result because the normalized curve depends only on r and no longer on d. It is also interesting because scaling and offsetting is something we can do after the facts, leading to the following third analytic form
The fact that we have an equation for and for the unscaled equation makes it possible to write the analytic form of before as a multiple of powers. Namely
In this section we determine values for r such that the resulting curve goes through the point . This means that the normalized curve should go through . The y value of the last point will be called b. Given the equation for the normalized curve we can write
Defining gives us
Given this value for r we can now scale the equation correctly and offset it
This section explains how to calculate p(x) and was created in absence of a sensible solution. See section analytic form 4 for a more up to date solution
Equation ypx poses a slight computational challenge since we potentially have to perform x steps to calculate p its value (A Horner scheme should not be considered because of that). Factorization of the polynomial in prime numbers could be a worthwhile attempt, but is unnecessary, because it is easier to realize that all coefficients of the polynomial are 1. This makes it possible to split the polynomial in two when we want.
When the polynomial is of uneven order we can work with the even part of of the polynomial and remember the remainder
Combining these two branches gives (in which we assume that x/2 is performed as integer division).
To create a non-recursive loop of the above we work with a term (which accumulates the 1's form the odd part) and an accumulator (which calculates the current value of $y\left(x\right)$). We write an equation of the form
With start condition
when x=0. When x is even we have
which shows how we calculated the new T' and A' to go into the next step
In the odd case we have
In this case
This loop must finally be multiplied with d, as to obtain the correct value of y.
which leads to the two following insights regarding the odd and even cases.
By integrating d into A for the initial case we effectively multiply A throughout the entire chain, leaving
If we do the same for T (which starts of at 0), we see that
Consequently. It suffices to start the calculation of y as a calculation of p and set the initial value of A to d.
A last improvement on the algorithm is that the term , which can be rewritten as
which has a constant .
Analytic form 4
Which means that, after some deductions
To determine r we use the bend factor b (the relative point through which the curve should go at midpoint (x1+x2)/2.
Then assuming that
gives us the solution for the quadratic equation
the other solution is s=1. The end result is thus that
Plugging r and d into the equation eventually leads to the following expression
which itself can be written as
Because the differential of y (Eq. diff is an exponential curve, we could consider calculating the delta at each step, relying on an incremental scheme to calculate the exponential and then add that delta to the current running envelope.
To calculate an exponential increasing/decreasing one merely needs to decide on the multiplier for each step, which would then result in the following:
At each step we would perform the following
|Variable reads||1 (y)||2 (d, y)|
|Constant read||2 (r, d)||1 (m)|
|Variable writes||1 (y)||2 (d, y)|
|Multiplication||1 (y.r)||1 (d.m)|
|Additions||1 (y.r + d)||1 (y+d)|
The main difference between both is 1 variable write less, which in a very crude 'every operation is equally expensive' would mean that our presented methods is about 16% faster than the alternate.
The first verison of this document was written in April 2012. In may 2012 Anthony Liekens found an analytic solution for p(x), which resulted in an extension of this document (the fourth analytic variant).
We presented a method to calculate an exponential patch quickly, using only 1 multiplication and 1 addition per sample. To make this possible, an algorithm was presented to calculate the required multiplier and delta, while at the same time providing a direct calculation of y(x).
Although, this is the essence of a fast ADSR envelope, it is clear that these patches need to be pieced together within a larger program, that will at appropriate times (when changing from Attack to Decay, or Sustain to Release) set new values for the multiplier and delta.