Tutorial 06. Envelopes and an improved keyboard

Now that we have the basics of volume control out of the way, let's use it to improve the keyboard we made in tutorial 3 with an envelope.


An ADSR envelope is one of the most common envelopes in the world of synthesizers. It describes a sound that starts at silence, fades in to a maximum volume, decays down to a sustained volume and then fades out to silence. Mozzi has a great ADSR envelope implemented for us to use. It is an 8-bit envelope that can be used to control gain in the same way we did in tutorial 6.

  1. Start by making a new sketch and copying code from tutorial 3
  2. Let's create an envelope that we can use to control the volume of our notes.
  3. ADSR<CONTROL_RATE, AUDIO_RATE> my_envelope;

    The Mozzi ADSR object allows you to configure how often it is updated (ie. as a control rate signal or an audio rate signal).

    • The first argument of the ADSR template specifies how often we want to call the update() function, which calculate will a new rough gain value for the envelope. This is typically done in updateControl() so we set this to CONTROL_RATE.
    • The second argument specifies how often we want to call the next()function, which interpolates between the values calculated in update() at a higher speed to smooth out the envelope. We are going to do this this in updateAudio() so we set this to AUDIO_RATE.

    NOTE THAT YOU MUST ALWAYS CALL BOTH update() and next() if you want the envelope to function as expected.If you wanted, you could do both of these in updateAudio() which would result in a smoother envelope at the expense of some computational efficiency.

  4. In setup() we want to set the attack and sustain levels of our envelope. These values can be any number between 0 and 255. We'll set the attack level to a maximum volume of 255 and the decay level to 200 (a bit quieter than max volume).

    my_envelope.setADLevels(255, 200);

  5. Now let's use the potentiometers to control the attack and release times so we can adjust the envelope on the fly.

    my_envelope.setTimes(meap.pot_vals[0], 100, meap.max_sustain, meap.pot_vals[1]);

    The setTimes function has four inputs that will set the amount of time in milliseconds the envelope spends in the attack, decay, sustain and release phases respectively. We'll use the raw potentiometer readings to set the attack and release values to vary between instantaneous and ~4 seconds long each, and set the decay time to a fairly quick 100ms. The Mozzi ADSR object also allows you to set the sustain time, which will cause the envelope to automatically turn off after a certain amount of time has passed. In this case, we want the envelope to sustain until we release the touch pad that triggered it so we want to set the sustain time to an arbitrarily large number. You could manually set it to a number like 100000 (100 seconds), but the meap library includes a constant, meap.max_sustain, which is set to the largest possible number that the sustain time could be, so we'll use this.

  6. Now, let's tell the envelope when to turn on or off!

    We want to start the envelope whenever a pad is pressed and end the envelope whenever the pad is released. At the top of the updateTouch() function is a section that will execute whenever any pad is pressed or released so we can avoid adding the same code to the case statement for each individual pad. Edit it as follows:

    if (pressed) {  // Any pad pressed
      my_envelope.noteOn();
    } else {  // Any pad released
      my_envelope.noteOff();
    }

  7. Now we just need to retrieve values from the envelope to control the volume of our oscillator. Add the following line to updateControl()

    my_envelope.update();

    This will tell the envelope to calculate its next rough value and store it internally.

    Next we need to grab the interpolated value in updateAudio() and use it!

    int out_sample = my_sine.next() * my_envelope.next() >> 8;
    return StereoOutput::fromNBit(16, out_sample, out_sample);

    Multiplying our sine by this 8-bit envelope will give us a 24-bit output so we bit-shift it back down by 8 bits to give us a 16-bit output.

  8. Upload the code and try playing the keyboard. A note should sustain as long as you keep your finger on a pad, and the fade in/out of the note should respond to the position of the two potentiometers.This keyboard is still a little glitchy. If you try to play notes that overlap with each other, they can trigger each other's envelopes to turn off. A solution would be to make this keyboard polyphonic. The simplest approach to this would be to create a separate oscillator and envelope for each touch pad. This is left as an excercise for the reader!

FULL CODE HERE