Home > development | html5 | javascript | music | ui | web > The ADSR Envelope with Audiolib.js

The ADSR Envelope with Audiolib.js

So, let’s talk envelopes. First what they are, and what they can do to the quality of a sound.

Demo here: http://www.benfarrell.com/labs/examples/envelopes-12-17/, but read on for how it all works!

An envelope is a real simple concept actually. Take any signal, like a sound. Maybe that sound plays at a constant volume. When you apply an “envelope” to that sound, you are changing the volume of that sound while it’s played. It might go up and down, back up again, whatever.

How it goes up and down, and the speed at which the volume is changed is up to the details of the envelope used.

You could create an envelope that takes 8 hours to complete. Maybe you want to go to sleep with some music, and then wake up in the morning with music. If you know it takes you 30 minutes to fall asleep, you’ll start the music playing at a loud volume. Over the next 30 minutes, you envelope your sound from loud to quiet, to off. In the morning, 30 minutes before you wake up, the envelope makes the music go from off, to quiet, to loud again. It wakes you up!

While this 8 hours illustrates how an envelope works, it’s a bit different when talking musical tones.

It’s not that different, however. We’re still talking about volume over time, but we’re talking milliseconds instead of hours or even minutes or seconds.

The character or personality of a musical tone can be changed greatly by altering the envelope. While we talk about this in terms of 1/1000th of a second, you don’t really notice the volume changing as you listen to the tone. Your ear doesn’t necessarily detect that the volume is going up and down.

Instead, the sound just has a different tonal quality! A piano for example wouldn’t sound as sharp if it didn’t go from no sound to loud that quickly. An accordion though, has a longer time as it goes from quiet to loud. And that quality – the “attack” (amongst other factors) create the personality of the tone.

Let’s talk specifics now. ADSR.

That’s Attack, Decay, Sustain, Release. The ADSR envelope is just one type of envelope, but it’s a popular one that’s been used in electronic music for decades.

  • Attack is the period of time after the initial release, it’s typically the loudest part of the sound
  • Decay is the phase while you’re going from the attack to the sustain – you’re “decaying”  the volume from this sharp initial phase to the normal volume phase
  • Sustain is the normal phase of the sound.  It is typically less volume than the attack, and go on for an indefinite period of time, or for a specific amount of time
  • Release is the draw down from the sustain period to no sound.  A fade out

the attack, decay, sustain, and release phases

I’ve talked about Audiolib.js in previous posts.  Audiolib is the Javascript library that enables you to make these dynamic sounds in Chrome and Firefox.

Audio programming isn’t easy though!  So while Audiolib helps out in awesome ways, it doesn’t have concepts of notes and music theory.  You have to tell it what frequencies to play, and if playing a chord, which individual frequencies make up the chord – which I explored and created my own helpers for.

The ADSR envelope is another example of something that Audiolib.js provides, however, it doesn’t provide any obvious usage for it.

There’s actually a VERY good reason for this.  While, we’re talking about envelopes on musical tones, the 8 hour sleepy-time envelope is another good usage example.  And that’s restricting envelopes to the volume of a sound.  There are tons more examples in audio synthesis that envelopes can be applied.  Like effects – you can apply a distortion effect to a sound (like a rock guitar).  But you can envelope the amount of distortion which is applied to the guitar.  This has nothing to do with volume, and everything to do with just how much of something is applied to something else.

So, I’d like to create a usage of our ADSR envelope that is limited to producing a musical tone – especially in the example of producing live sound by using a trigger (here it will be your computer/laptop keyboard).

Let’s start with our previous example where we extend the Audiolib.js Oscillator via a plugin.  We extended it to simply take a musical notation, like an “A” and set the correct frequency:

audioLib.generators('Note', function (sampleRate, notation, octave){
// extend Oscillator
for ( var prop in audioLib.generators.Oscillator.prototype) {
this[prop] = audioLib.generators.Oscillator.prototype[prop];
}
// do constructor routine for Note and Oscillator
var that = this;
 
// are we defining the octave separately? If so add it
if (octave) {
notation += octave;
}
that.frequency = Note.getFrequencyForNotation(notation);
that.waveTable = new Float32Array(1);
that.sampleRate = sampleRate;
that.waveShapes = that.waveShapes.slice(0);
}, {});

So let’s figure out how to work in an ADSR envelope. The usage for the Audiolib.js version is like so:

myEnvelope = audioLib.ADSREnvelope(sampleRate, attack, decay, sustain, release, sustainTime, releaseTime);

The parameters work like so:

  1. Sample Rate:  The sample rate of the audio – I won’t go into it here, as it’s a basic setting for audiolib
  2. attack – the amount of time (in milliseconds) that the attack phase takes to complete
  3. decay – the amount of time (in milliseconds) that the decay phase takes to complete
  4. sustain – the level of volume during the sustain phase (from 0 to 1)  - the default is 1
  5. release – the amount of time it takes for the release phase to complete (in milliseconds)
  6. sustainTime – the amount of time it takes for the sustain phase to complete (in milliseconds).  This param is pretty important though, because if you pass in null, the sustain period is indefinite.  Unless you call into the envelope with a trigger, it will continue being in the sustain phase forever
  7. releaseTime – the amount of time between the release phase and the envelope looping around to the attack phase again

The envelope has 6 states of being (0-indexed inside the code):

  1. Attack Phase
  2. Decay Phase
  3. Sustain Phase
  4. Release Phase
  5. Timed Sustain Phase
  6. Timed Release Phase

To kick off our envelope, you trigger it:

myEnvelope.triggerGate(true);

Now we can start using our envelope.  The usage is a little weird to me, as the Audiolib.js library treats it like a “generator” which seems a bit complicated for what it does.  I just want a stream of numbers, but OK I’ll bite.  I’ll use it with the byte arrays and whatnot, as if it’s an Oscillator.

var buffer = new Float32Array(1);
myEnvelope.append(buffer, 1);

So, I’m just pulling one value at a time from the envelope, and putting it into my “buffer”.  But my buffer only has one value in it at any time.  Like I said, I feel like I’m being forced into using it in more complicated of a way than I need!  Maybe there’s something I’m missing.

Now, every time, I get create an audio data point in my sound, I can multiply the data point by my envelope.  Thus my envelope is applied!

this[this.waveShape]() * buffer[0];

To do this, I overrode the “getMix” function in the Audiolib Oscillator.  But I needed to do other things too.

Since I trigger the envelope with triggerGate, it will cycle through the attack and release to the sustain phase as the envelope is used, automatically.

It gets complicated at the release phase though.  After your computer/laptop keyboard is released, we need to enter the release phase.  But we’re still grabbing sound, because the release phase still produces sound as it fades.  So our Oscillator needs to track that it’s in the release phase (it knows by keeping track of the envelope.state, which is 3 or 5 here for release or timed release).

Then finally when it gets back to state 0, or the cycle begins again, we mark this note as “released”, so our buffer knows that it doesn’t need to pull from it anymore.  We have to be very careful of note pulling from more notes than we need, cause all this music stuff is hard work, and too many notes slows down your CPU and breaks the audio processing.

The above is if the sustain phase goes on as long as you hold the key!  What if it’s a timed sustain – then we need the logic in there to release the key when the envelope is done, rather than when our user releases the key.

Here’s our final Note.js code.  And here’s a controller for keeping track of keys being pressed.

It all comes together in my (Chrome only) demo:

http://www.benfarrell.com/labs/examples/envelopes-12-17/

The demo starts out by not using an envelope at all.  You’ll hear some clicky-ness when you press and release a key.  That’s because you’re hearing the transition between no sound and the abrupt start in the phase of the waveform.  It’s EXACTLY one of the reasons why envelopes are useful – to ease these transitions in and out.

When you turn on the envelope, you can start adjusting parameters to see how the different properties of attack, decay, sustain, and release affect the overall personality of the tone.

Comments:6

Leave a Reply
  1. Reply Greg
    11/12/21

    Great post on ADSR envelopes. That’s awesome that you are blogging about audiolib.js. I am a big fan of audiolib.js too :)

    I have been messing with ADSR envelopes and ran into a problem with how to use them with a dynamic tempo when producing musical tones. I am working with notes that have a fixed duration and am setting the sustainTime for each note. For example, say I set the attack, decay, and release to 50ms which works great for all notes that have a duration greater than 150ms. But how should I handle a very short note like a 64th note that has a total duration of say 100ms? If I leave the ADSR settings the same I hear a clicky-ness after each 64th note because it never reaches the release phase. For now, I dynamically set the attack, decay, and release values to a much smaller value when the note duration is less than 150ms which prevents the clicky-ness but it changes the character of the note. But maybe thats the price you have to pay if you want to support a dynamic tempo and no minimum on note duration.

    You mentioned that envelopes can be used to apply effects. I would love to see a demo of how to do this with audiolib. Perhaps that could be a topic for another post.

  2. Reply admin
    11/12/21

    Thanks! Be warned, I’m self taught on this audio stuff and learning as I go.

    My opinion is that the attack, decay, and sustain time counts toward the length of your note, but the release wouldn’t. So, when you release, even if you’re over the time you wanted, you’d still do the release phase. I’d say this means you can even jump from the attack to the release phase if that’s all the time you’ve allotted. (or maybe the decay to the release phase, I’m not decided). Either way, the attack, decay, release phases are important to the character of your note, and you’ll want to included them to make it all sound like what you want. Ditching phases is up to you and how you want to do things, but the attack and release are pretty important I think, and I don’t think I’d ever want to leave them out, even with the problem you describe

    You’re probably working with some kind of sequencer for this, when you go between notes, right? I really think that if you have this problem, and don’t fix it by fudging things to work by adjusting the envelope time to be less than the note you want to play, then you’ll need to mix your tones. Like you’ll have to mix that second note with the tail end of the first note so you’re basically playing the two notes at once.

    I’ll probably tackle something similar yet, I made an arpeggiator in a previous demo, but I’m looking to apply some enveloping to that. Then I plan to play with the white noise to do some drums, and THEN finally mess around with the effects. I just haven’t got there yet!

  3. Reply Ned
    12/01/23

    Hi,

    Great blog and very interesting as I am playing around with audiolib.js as part of my college course.

    I am new to coding and especially JavaScript so deciphering and following through the code is a challenge. I really enjoyed the envelope post and have been trying to apply effects to this code and emulate a simple softsynth.

    Before seeing your code I had the most basic of tones generated and was able to apply a delay effect to it by simply appending it to the buffer and managed to adjust the delay time using a html slider.

    delay.append(buffer);

    dtime = parseFloat(document.getElementById(“slide”).value);
    delay = audioLib.Delay.createBufferBased(2, dev.sampleRate, slide);

    Using your code I have been unable to apply the delay effect to it. I am hoping that if I can apply the delay effect, I should be able to add other effects and make the sound more interesting.

    My inexperience in coding is holding me back so I am hoping you could give me a few pointers in how to apply this effect to the generated sound.

    Cheers.

    • Reply admin
      12/01/23

      Hey, so I really hadn’t gotten into effects yet. However, the easiest place to apply an effect would be the buffer. I’ve been slowly building out a framework of sorts here, but haven’t really messed with the buffer all that much. I betcha it would be easy (and no different than any other Audiolib code you see) to slide in some effects right after all the other code on the audioCallback function in my Main.js. What we’re doing in this function is just grabbing all the keys and generating/mixing the bytes.

      It seems to me it would make the most sense to put the effect on this final buffer right before it gets output. If you put it anywhere else, you could find yourself putting an effect on each keypress – which would be interesting if it worked, but you’d probably end up pegging the CPU.

      So I’d say start with those bytes right before that get sent in the audioCallback method. Good luck!

  4. Reply Ned
    12/02/17

    Apologies that last post turned out a mess.

    I got some effects applied to the the sounds.

    I created some effects like so:

    fx = new audioLib.Delay(sampleRate, 250, 0.5);

    Or we could have a chain of effects:

    fx = (new audioLib.LP12Filter(sampleRate, 8000, 20)).join(new audioLib.Delay(sampleRate, 960, .7), new audioLib.Distortion(sampleRate));

    This effect is then pushed to the buffer using the pushSample method in the audioCallback method.

    buffer[current + n] = fx.pushSample(sample);

    I have been looking at Using a MIDI keyboard the last few nights to control the synth rather than the qwerty keyboard.
    So far I can the MIDI messages and generate tones by passing the note (A4 for example) into your generator class.

    osc = audioLib.generators.Note(dev.sampleRate,myNote);

    I will now look at trying to apply effects to the MIDI keyboard controlled tones. A second audioCallback method will likely be needed in order to mix and send the sound to the buffer.

    Is MIDI something you have looked at yet?

  5. Reply admin
    12/02/22

    Awesome! You’re making me jealous now because I had to put this down and get going on some other things. I hope to pick this back up again soon. I actually hadn’t looked into MIDI – in fact, I SPECIFICALLY didn’t look into MIDI because I didn’t think it would work in a browser. Are you using Node.js for this or something? If you’re doing this in a browser, I’d love to know how – maybe you’re opening a websocket and using some kind of MIDI server instance? You have a blog? I’d love to read up if you’re posting

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code lang=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Trackbacks:0

Listed below are links to weblogs that reference
The ADSR Envelope with Audiolib.js from Benjamin Farrell
TOP