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.
- Start by making a new sketch and copying code from tutorial 3
- Let's create an envelope that we can use to control the volume of our notes.
-
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 inupdateControl()so we set this toCONTROL_RATE. -
The second argument specifies how often we want to call the
next()function, which interpolates between the values calculated inupdate()at a higher speed to smooth out the envelope. We are going to do this this inupdateAudio()so we set this to AUDIO_RATE. - 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); - 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. - 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 thecasestatement for each individual pad. Edit it as follows:if (pressed) { // Any pad pressed my_envelope.noteOn(); } else { // Any pad released my_envelope.noteOff(); } - 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.
- 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!
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).
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.
