Paul Goossens
2000 Waterloo Maple Inc.
The following section describes the use of Maple for defining the functions required to generate the attack and decay envelopes for a synthesizer. The objectives are to:
1) Generate a "natural" attack and decay curves, based on an exponential.
2) Allow the user to set parameters that would allow them to control the duration and "shape" of the curve.
3) Since this will be used as an attenuation function, the output must be between 0 and 1.
4) Put the functions together so that the Attack, Sustain, Decay envelope can be generated with a single function call.
Not only is this a good example of the use of Maple's function coding, it shows the use of its algebraic solver to great effect.
> restart;
First, we'll consider the Attack function. The exponential function is a good basis for creating a natural growth curve. We need the output of the function to be 0 at t=0. Since is going to be 1, we subtract 1 from the basic function.
> growth:=a*(exp(b*t)-1);
> plot(subs(a=1,b=1,growth),t=0..1);
Now, we need to find the parameters that cause the function to output 1 at a defined attack time, At .
> eqn:=a*(exp(b*t)-1)=1;
Solving algebraically for a , we get...
> solve(eqn,a);
Now substitute t for At , the defined attack time..
> subs(t=At,%);
...and then substitute this back into the original expression.
> eqn1:=subs(a=%,lhs(eqn));
This is the basic growth function that we will use for the attack function, the user can define the period over which the fuction will rise from 0 to 1. b is the "shape" factor that determines the concavity of the curve. Try different values of b. Note that a negative value of b will produce a convex curve.
> plot([subs(At=0.2,b=1,eqn1),1],t=0..1);
Now, we need to define a function that will limit it's output to a defined range, in this case, 0 and 1.
> Limiter:=(s,ll,ul)->if s<ll then ll else if s>ul then ul else s end if; end if:
Quick test..
> Limiter(-10, 0, 5);
> Limiter(1.1,0,1);
Now, we can put the components together as a Maple procedure
> Attack:=proc(At,As,t) Limiter((exp(As*t)-1)/(exp(As*At)-1),0,1); end:
We can see how this looks in a plot. Try different parameter values.
(Note: the single quotes around the attack function are there to overcome an idiosyncrasy in the way that Maple handles Boolean functions in plots).
> plot('Attack(.2,1,t)',t=0..1,y=0..1.5);
Let's consider the decay function. The basic function is a little simpler than for the attack function.
> decay:=a*exp(-b*t);
> plot(subs(a=1,b=6,decay),t=0..1);
The parameter, a , defines the starting value for the decay. Since this is always going to be 1, we can eliminate a from the exression.
The plot shows a natural exponential curve to attenuate the signal towards 0. However, since the exponential will be asymptotic to 0, it will never reach 0. Therefore, we need to find a parameter that will reach a certain tolerance at the defined decay time, Dt.
> eqn2:=(exp(-b*t))=Tol;
Solve this for b..
> solve(eqn2,b);
Substitue t for the decay time, Dt...
> subs(t=Dt,%);
...and substitute this back into the original expression...
> eqn2:=subs(b=%,lhs(eqn2));
Let's see what this looks like. Try some different parameters to see the effect.
> plot(subs(Tol=0.001,Dt=0.4,eqn2),t=0..1);
A quick aside on Tol. The expression 10*log10(Tol) has a specific meaning and called the attenuation factor, generally given in decibels, dB. If we evaluate this for Tol = 0.001, we get..
> 10*log10(0.001);
Thus, the attenuation factor is -30dB.
Now, we can create the Decay function...
> Decay:=proc(Dt,Ds,t) Limiter(evalf(exp(ln(Ds)/Dt*t)),0,1): end:
> plot('Decay(1,.001,t)',t=0..1,y=0..1.5);
>
Now, we can put the Attack and Decay functions together in a procedure called ASD (Attack, Sustain, Decay). The sustain is just the period between the Attack ending and the Decay starting. The function can be readily created using Maple's piecewise function.
> ASD:=proc(At,As,St,Dt,Ds,t) piecewise(t<At, Attack(At,As,t), t>(At+St), Decay(Dt,Ds,(t-(At+St))), 1); #otherwise end:
Finally, here's a quick procedure that can be used to visualize the complete envelope..
> ViewEnvelope:=proc(At,As,St,Dt,Ds,Duration) plot('ASD(At,As,St,Dt,Ds,t)',t=0..Duration,y=0..1.5); end:
And here's the envelope...
> ViewEnvelope(.2,20,.1,.7,.001,1);