Sonic Pi: Envelopes
This post continues from the last one, which covered the basics of making sound with Sonic Pi. In addition to setting an individual sound's basic volume, panning, and synth, Sonic Pi allows us to fine tune the duration and amplitude of a sound with an ADSR envelope.
All audible sounds start and end in silence and contain some non-silent part in between. Envelopes allow you to control the length and volume of the non-silent parts of the sound. The most common envelope for doing this is the ADSR envelope, which is the envelope that Sonic Pi enables you to use. ADSR is an acronym for the four phases of a sonic envelope: Attack, Decay, Sustain, and Release.
- Attack: the amount of time it takes your sound to go from
0
(silent) toattack_level
; this is the start of the note - Decay: the amount of time it takes your sound to go from
attack_level
todecay_level
- Sustain: the amount of time it takes your sound to go from
decay_level
tosustain_level
- Release: the amount of time it takes your sound to go from
sustain_level
to0
(silent); this is the end of the note
Attack Phase
You can think of the attack phase as controlling how the sound of a note starts. You can control the attack phase duration using the attack
opt like so:
play 60, attack: 0
Given no other opts, attack
is the amount of time it takes your sound to go from 0
(silent) to the amp
value (1
by default).
attack
's default value is 0
, which means it moves from 0
amplitude to 1
immediately with no fade in. This also means that play 60
is the same as play 60, attack: 0
.
You can give the sound a longer fade in by increasing the attack
opt's value:
play 60, attack: 2
You give the sound a shorter fade in by decreasing the attack
opt's value:
play 60, attack: 0.5
You can also pass an optional attack Level with the attack_level
opt, which is the amplitude that your sound attacks toward, or the sound's volume at the end of the attack phase. This will typically be between 0
and 1
, and represents that portion of the sound's overall amp
opt value at the end of the attack phase.
For example, if amp
's value is 1
and attack_level
's value is 0.5
, then the attack phase will fade to 50% of the overall note's volume:
play 60, amp: 1, attack: 2, attack_level: 0.5
You'll notice that after the 2 beat attack phase, the amplitude jumps up to 1
. We'll sort out how to deal with that jump later in this post.
attack_level
's default value is 1
, or 100% of the note's amplitude.
Just to recap, attack is the amount of time it takes your sound to go from 0
(silent) to attack_level
, starting the sound.
Decay Phase
The decay phase is the second portion of the sound, which comes after the attack phase. You control the duration of this phase using the decay
opt.
play 60, decay: 0
This sets the amount of time between the attack phase and either the sustain phase or release phase (depending on whether you include sustain
; more on that later).
decay
's default value is 0
, which means it moves from the attack phase to either the sustain phase or the release phase immediately. This also means that play 60
is the same as play 60, decay: 0
.
You can give the sound a longer decay phase by increasing the decay
opt's value:
play 60, decay: 2
You can give the sound a shorter decay phase by decreasing the decay
opt's value:
play 60, decay: 0.5
You can also pass an optional decay Level with the decay_level
opt, which is the amplitude that your sound decays toward, or the sound's volume at the end of the decay phase. This will typically be between 0
and 1
, and represents that portion of the sound's overall amp
opt value at the end of the decay phase.
For example, if amp
's value is 1
and decay_level
's value is 0.5
, then the decay phase will fade to 50% of the overall note's volume:
play 60, decay: 1, decay_level: 0.5
Now the sound is a little bit strange; it almost sounds like the same note playing twice, even though we're only playing a single note. That's fine for now, the next phase will help us fix that.
decay_level
's default value is the value of sustain_level
. Again, we haven't gotten there yet, but if sustain_level
's value is 0.8
, then decay_level
's default value is 0.8
. sustain_level
's default value is 1
, or 100% of the note's amplitude. If sustain_level
and decay_level
are left to their default values, then they both default to 1
, or 100% of the note's amplitude.
Before moving on, it's also important to note that all of the phase opts we're discussing here can be combined to offer you complete control of the envelope. You can put attack
, attack_level
, decay
, and decay_level
together to control how the note starts:
play 60, amp: 1, attack: 1.2, attack_level: 0.8, decay: 0.8, decay_level: 0.5
To recap this section, decay is the amount of time it takes your sound to go from attack_level
to decay_level
. decay_level
is probably the most optional of the envelope opts covered in this post, but is available to give a full range of control over your note.
Sustain Phase
The sustain phase is the third portion of the sound, which comes after the decay phase. You control the duration of this phase using the sustain
opt.
play 60, sustain: 0
This sets the amount of time between the decay phase and the release phase. Given no other opts, this is the time for which the sound is maintained at full amplitude between the attack and release phases.
sustain
's default value is 0
, which means it moves from the decay phase to the release phase immediately. This also means that play 60
is the same as play 60, sustain: 0
.
You can give the sound a longer sustain phase by increasing the sustain
opt's value:
play 60, sustain: 2
You can give the sound a shorter sustain phase by decreasing the sustain
opt's value:
play 60, sustain: 0.5
You can also pass an optional sustain Level with the sustain_level
opt, which is the amplitude that your sound sustains toward, or the sound's volume at the end of the sustain phase. This will typically be between 0
and 1
, and represents that portion of the sound's overall amp
opt value at the end of the sustain phase.
For example, if amp
's value is 1
and sustain_level
's value is 0.5
, then the sustain phase will fade to 50% of the overall note's volume:
play 60, sustain: 1, sustain_level: 0.5
You'll also notice that, unlike when we adjusted the attack_level
and decay_level
there's no weird jump or repeating of the note when we adjust the sustain_level
; that's because the note's amplitude always fades to the sustain_level
's value before fading to 0
in the release phase.
To fix our issues from earlier, we can combine the sustain phase opts with the attack phase opts:
## compare this:play 60, amp: 1, attack: 2, attack_level: 0.5sleep 5
## with this:play 60, amp: 1, attack: 2, attack_level: 0.5, sustain: 1, sustain_level: 0.8
The second note here is a great deal smoother than the first note, but we can still do better. Let's add in the decay phase to really fix it:
## compare this:play 60, amp: 1, attack: 2, attack_level: 0.5sleep 5
## with this:play 60, amp: 1, attack: 2, attack_level: 0.5, sustain: 1, sustain_level: 0.8sleep 5
## and now this:play 60, amp: 1, attack: 2, attack_level: 0.5, decay: 0.8, decay_level: 0.8, sustain: 1, sustain_level: 0.8
Much smoother! But also longer. This leads us to an important note: that as you add on attack
, decay
, and sustain
duration opts, those add to the length of the note.
A default note represents a single beat. At 60 bpm, it plays for one second; or more accurately, releases for 1 beat...more on that in the Release Phase section.
play 60
If we add attack: 1
, it attacks for 1 beat; decay: 1
decays for 1 beat; sustain: 1
sustains for 1 beat:
play 60, attack: 1, decay: 1, sustain: 1
To recap this section, sustain is the amount of time it takes your sound to go from decay_level
to sustain_level
.
Release Phase
The last phase in the ADSR envelope is the release phase, which controls how a note ends. All synths have a default release
value of 1
, which means that by default they have a duration of 1 beat.
play 60, release: 1
play 60
is therefore the same as play 60, release: 1
.
You give the sound a longer duration by increasing the release
opt's value:
play 60, release: 2
Or you can give the sound a shorter duration by decreasing the release
opt's value:
play 60, release: 0.5
Since release
ends the sound at 0
amplitude (silence), there is no release_level
opt available.
You can combine release
with attack
in various ways:
play 64, attack: 0.7, release: 3sleep 2.5
play 58, attack: 3, release: 0.7sleep 5.2
play 65, attack: 0.5, release: 0.5
You can also set both attack
and release
to 0
and just use sustain
to have absolutely no fade in or fade out to the sound:
play 60, attack: 0, sustain: 3, release: 0
However, release: 0
can produce clicks in the audio and it's often better to use a very small value, such as 0.2
:
play 60, attack: 0, sustain: 3, release: 0.2
To recap this section, release is the amount of time it takes your sound to go from sustain_level
to 0
(silent), ending the sound.
All Defaults
Here is a scale that starts with no opts at all, but gradually adds each opt (at its default value) that we've covered both in this post and in Sonic Pi: Basics:
play 60sleep 2
play 62, amp: 1sleep 2
play 64, amp: 1, pan: 0, attack: 0sleep 2
play 65, amp: 1, pan: 0, attack: 0, attack_level: 1sleep 2
play 67, amp: 1, pan: 0, attack: 0, attack_level: 1, decay: 0sleep 2
play 69, amp: 1, pan: 0, attack: 0, attack_level: 1, decay: 0, decay_level: 1sleep 2
play 71, amp: 1, pan: 0, attack: 0, attack_level: 1, decay: 0, decay_level: 1, sustain: 0sleep 2
play 72, amp: 1, pan: 0, attack: 0, attack_level: 1, decay: 0, decay_level: 1, sustain: 0, sustain_level: 1sleep 2
play 74, amp: 1, pan: 0, attack: 0, attack_level: 1, decay: 0, decay_level: 1, sustain: 0, sustain_level: 1, release: 1
By default:
attack
is0
attack_level
is1
decay
is0
decay_level
is the same assustain_level
sustain
is0
sustain_level
is1
release
is1
Put It All Together
A scale with nothing but the default values isn't any fun though. To do something more interesting, I'm going to play with the first part of the melody from Radiohead's song, Idioteque.
As an aside, this song has a pretty interesting history that's very intertwined with computer music synthesis and composition. Check out the Recording section of the song's Wikipedia article if you're curious.
Anyway, let's start with the basic notes:
play 58sleep 1.2
play 62sleep 1.2
play 70sleep 1.2
play 67
To give it a little bit of character, I'll add in some amplitude so that the volume builds toward the third note and then decrescendos out:
play 58, amp: 0.62sleep 1.2
play 62, amp: 0.76sleep 1.2
play 70, amp: 1sleep 1.2
play 67, amp: 0.69
Now I want to adjust the attack
for each note so they can fade in. I like the idea of increasing the attack
duration parallel to the amplitude, but I'll actually drop the attack
to its lowest point on the third note to throw a bit of a change in:
play 58, amp: 0.62, attack: 0.6sleep 1.2
play 62, amp: 0.76, attack: 0.8sleep 1.2
play 70, amp: 1, attack: 0.433sleep 1.2
play 67, amp: 0.69, attack: 0.6
Now I'll play with the attack_level
. I'll just have that decrescendo all the way through the phrase:
play 58, amp: 0.62, attack: 0.6, attack_level: 1sleep 1.2
play 62, amp: 0.76, attack: 0.8, attack_level: 0.9sleep 1.2
play 70, amp: 1, attack: 0.433, attack_level: 0.8sleep 1.2
play 67, amp: 0.69, attack: 0.6, attack_level: 0.7
I don't want to mess too much with the decay phase, so I'll set that pretty much the same for all four notes, but I'll make the decay
duration a little bit shorter on the fourth note:
play 58, amp: 0.62, attack: 0.6, attack_level: 1, decay: 0.8, decay_level: 0.75sleep 1.2
play 62, amp: 0.76, attack: 0.8, attack_level: 0.9, decay: 0.8, decay_level: 0.75sleep 1.2
play 70, amp: 1, attack: 0.433, attack_level: 0.8, decay: 0.8, decay_level: 0.75sleep 1.2
play 67, amp: 0.69, attack: 0.6, attack_level: 0.7, decay: 0.6, decay_level: 0.75
Now I'll do the sustain phase, and for the most part this will mirror each note's amplitude:
play 58, amp: 0.62, attack: 0.6, attack_level: 1, decay: 0.8, decay_level: 0.75, sustain: 0.56, sustain_level: 0.8sleep 1.2
play 62, amp: 0.76, attack: 0.8, attack_level: 0.9, decay: 0.8, decay_level: 0.75, sustain: 0.43, sustain_level: 0.9sleep 1.2
play 70, amp: 1, attack: 0.433, attack_level: 0.8, decay: 0.8, decay_level: 0.75, sustain: 0.67, sustain_level: 1sleep 1.2
play 67, amp: 0.69, attack: 0.6, attack_level: 0.7, decay: 0.6, decay_level: 0.75, sustain: 0.4, sustain_level: 0.7
Finally, I'll set the duration for each note to fade out with decay
. Here's my final result!
play 58, amp: 0.62, attack: 0.6, attack_level: 1, decay: 0.8, decay_level: 0.75, sustain: 0.56, sustain_level: 0.8, release: 1.2sleep 1.2
play 62, amp: 0.76, attack: 0.8, attack_level: 0.9, decay: 0.8, decay_level: 0.75, sustain: 0.43, sustain_level: 0.9, release: 0.8sleep 1.2
play 70, amp: 1, attack: 0.433, attack_level: 0.8, decay: 0.8, decay_level: 0.75, sustain: 0.67, sustain_level: 1, release: 0.6sleep 1.2
play 67, amp: 0.69, attack: 0.6, attack_level: 0.7, decay: 0.6, decay_level: 0.75, sustain: 0.4, sustain_level: 0.7, release: 1.2
I hope you enjoyed this post; I'm certainly having a lot of fun toying around in Sonic Pi and plan to keep doing so for a little while. Obviously there's way, way more going on with the original song's melody, but this is a fun starting place. It would be great to adjust the synth
or panning
, or add some chords and bass notes in. If you're so inspired, I urge you to do so. Otherwise, I'll see you in the next post.