In our first Simple Synthesis Addendum we learned how to connect a VCO to a VCA and control their ‘frequency’ and ‘gain’ AudioParams using a MIDI controller via the Web MIDI API. Good stuff! We now have a simple synth we can play. In this post we’ll learn how to shape our notes by building a Envelope Generator with configurable attack, decay, sustain and release using the Web Audio API’s scheduling methods. We’ll also give our Envelope Generator a ‘Mode’ setting, which will give us the ability to create some really long envelopes to play with.
An envelope generator produces a contoured signal over time. Our envelope generator will start on noteOn and begin its journey of release on noteOff. We need to break our envelope generator into two functions so we can hold our note down as long as we need to before we release the key and commence the closing of our envelope. Let’s take a look at these two functions:
Our
envGenOn()
takes in the ‘gain’ AudioParam of the GainNode we want to control along with values for the first three stages of our envelope, Attack, Decay, and Sustain. The first two, Attack and Decay, are time values, while sustain is a gain value. Our envGenOff()
takes in the ‘gain’ AudioParam of the GainNode we want to control along with value for Release, which is a time value. We’re setting the values of our envelope with HTML sliders in place of hardware knobs, but you could set these values in any way imaginable.
Scheduling
You’ll notice that we’re using a few scheduling methods. These are a key part of our envelope. In fact, they are what shapes our envelope. As their name implies, they create a linear ramp to a value over an amount of time. exponentialRampToValueAtTime(value, endTime)
and custom curves are also available. Let’s look at this line in the envGenOn()
function : vcaGain.linearRampToValueAtTime(1, now + a)
. What we are saying here is: create a linear change, to ‘1’, in gain value, in the duration of time set by ‘now’ (audioContext.currentTime) + our value for ‘a’, or Attack. A key to understanding how these scheduling methods work is knowing when the change starts, as the ‘time’ value in linearRampToValueAtTime(value, endTime) specifies when the change ends. To specify when you want this change to start, another scheduling method must set a value on an AudioParam first. In our case, setValueAtTime(value, time)
, which initiates a AudioParam value change by immediately setting a AudioParam to a value. In other words, the change starts at the specified time of the previous event. If you tried to set a value directly, GainNode.gain.value = 0
, and then use linearRampToValueAtTime(value, endTime)
, the value would stay at the directly assigned value and no scheduling would occur. Fortunately, you only need to do this once each time you want to automate an AudioParam, as you can see by our multiple calls to linearRampToValueAtTime(value, endTime)
.
The slider values are initially set to increments of 1/10th of a second, which is fine, but a little limiting. I’ve added a ‘Mode’ setting which was modelled after the Doepfer A140 ADSR Envelope Generator that Emmett posted the video of at the end of his post. In my version, I’m simply multiplying the time-based values of our envelope by a factor to increase or decrease their influence on the shape of the envelope. The default setting, ‘Low’, gives us values in the range of 0 – 1 second, the ‘Medium’ setting gives us values between 0 – 5 seconds, and the ‘High’ setting, values between 0 – 10 seconds.
What about velocity, you ask? If you have velocity turned on on your controller then the velocity changes our master volume so that our envelope keeps its shape regardless of velocity.