Lately, I've gotten back to writing Sonic Pi music and thought I'd infect others with its joy and magic. So I dug up a song I wrote with it, an atmospheric remix of Hotel California by The Eagles. This is a tutorial walking through how, in 55 lines of code, Sonic Pi helped me make this:
Along the way, I'll cover some Sonic Pi basics to show what it's capable of. But there's a built-in tutorial if you download the program I highly recommend. Mehackit also has notes on a great Sonic Pi workshop that's worth reading.
Quick note: Writing musical code both highly technical and subjective. You may not agree with the code I wrote has the effect I aimed for, and that's fine. There are lots of ways to get there, more ways to disagree, and even more to learn from each other.
So let's begin! Sadly I can't embed Sonic Pi snippets with their sound outputs, so feel free to download the app and play the snippets as I go!
I decided on Hotel California as my inspiration for two reasons:
- It's awesome
- It can be played with two basic chord progressions
There's plenty of music sites that break down the chords behind the song, but I went with Ultimate Guitar for its readability. First, there are the chords used for verses.
Then the chords for the chorus.
It's easy to translate these into something Sonic Pi can understand. It has built-in functions for both arrays and chords:
chord. You can see them in action below.
opening_ring = (ring chord(:a, :minor), chord(:e, :major7), chord(:g, :major), chord(:d, :major), chord(:f, :major), chord(:c, :major), chord(:d, :minor), chord(:e, :minor)) chorus_ring = (ring chord(:f, :major), chord(:c, :major), chord(:e, :major7), chord(:a, :minor), chord(:f, :major), chord(:c, :major), chord(:d, :minor), chord(:e, :major))
ring is Sonic Pi's version of an array with added functions for playing sounds.
chord takes the chord's name and gives you a ring of the notes that make it up. Once you have these rings, you can play those notes any way you want - all at once, one at a time, or at random. You can also grab notes by scales or octaves for even more sounds. But notes gathered based on any of these groups will almost always sound good together.
After some internal debate, I settled on the opening chords for the song's main melody. I planned to start simple and code things by ear (get it?) until I got a creepy, atmospheric feel.
So to start, I played each chord in a loop. Sonic Pi has two tools to make that easy:
live_loop, which runs all code, sounds, and logic in it infinitely. It runs in tandem with other
live_loops, making it great for layering melodies.
tick, which is a
ringfunction that plays each note in the sequence. Each time
tickis called, it plays the next note in a repeated loop.
live_loop :opening do play opening_ring.tick sleep 1 end
This tells Sonic Pi to play each chord, wait for a second, then play the next. It's a start, but something atmospheric should be slower. So first I increased the rest between each chord.
live_loop :opening do play opening_ring.tick sleep 3 end
This helps, but now there's too much silence. Stretching each chord over those three seconds would add some more tension and creepiness. Which is possible!
live_loop :opening do play opening_ring.tick, attack: 1, sustain: 1, release: 1.50 sleep 3 end
These properties likely seem weird but are pretty straightforward:
attackis the time from silence to full volume.
sustainis the time at full volume.
releaseis the time from full volume to silence.
The respective default values for these are
0, 0, 1, so my changes stretch the chord quite a bit. It sounds more like a gentle howling in the wind then someone banging keyboard notes. This is good, but still sounds too plan. I want to layer on something else for extra texture.
I'll spare you the experimenting and cut to the end. Before I explain, try to guess what this code does.
live_loop :opening_deep do play opening_ring.tick.tick, attack: 0.5, release: 1 sleep 1 end
You may remember that
opening_ring is a ring of chords, and each
chord returns a ring of notes. So
opening_ring.tick.tick is ticking through each note of each chord, one at a time and not all at once. In music theory, this is a "broken chord." So the result of these two loops is the full and broken chords playing at once.
live_loop :opening do play opening_ring.tick, attack: 1, sustain: 1, release: 1.50 sleep 3 end live_loop :opening_deep do play opening_ring.tick.tick, attack: 0.5, release: 1 sleep 1 end
The result is a simple yet haunting melody, with the notes grouped in a way that still works.
I have the opening chords for the melody, but still, want to use the chorus chords. The melody is already the music's main focus, so I used these chords for some ambiance. To get this effect, I used two other Sonic Pi tools - synths and sound effects.
Synths are the sounds Sonic Pi uses to executed sounds composed from notes and chords. So far I've been using the default
beep synth since it fit with what I wanted. But there are dozens to choose from, from pianos to chip noises to pulses to whatever. In this case, the
hollow synth fits perfectly. It's described as "a hollow breathy sound constructed from random noise." That's great for a fog-like effect, and can be set within specific loops.
live_loop :ambience do use_synth :hollow play chorus_ring.tick, amp: 0.15, attack: 1, sustain: 3, release: 0 sleep 3 end end
Sound effects are filters you can run anything through. This includes changing echoes, pitches, frequencies, sample rates, and more. I don't understand them all but understand a good amount. I went with two for the ambiance loop:
gverb for a spacious and outdoorsy feel, and
flanger for a whooshing wind effect.
live_loop :ambience do use_synth :hollow with_fx :gverb do with_fx :flanger do play chorus_ring.tick, amp: 0.15, attack: 1, sustain: 3, release: 0 sleep 3 end end end
The result is a distant, echoing effect that feels vaguely disturbing but keeps the sing's spirit.
These three loops do a lot, but they're relatively short and can get stale. So adding a few samples keeps things interesting.
Samples are pre-recorded sound bites that you can't control with notes and chords, but can still be modified with code. Sonic Pi has many included, and you can easily add more! So I took some pre-existing ones and mixed in some needed effects.
The first sample is
glitch_bass_g. As the name implies, it's a glitchy base sound suited for background beats. Not built for creepy songs, but a few tweaks can fix that. The first is adjusting its
rate is the speed a sample plays at, letting you slow it down or speed it up. You can set it to a negative value to play clips backward, which is what I did. I used another tool,
rrand, for generating random numbers between two values. Now I can slow the sample down, play it backward, and add variety to each play. I can also adjust the
amp property to make it louder.
live_loop :disturbance do sample :glitch_bass_g, amp: 2.25, rate: rrand(-0.1, -0.4), release: 6 sleep 6 end
This all creates a disquieting rumble, but I wanted it to sound stranger. Thankfully you can also apply effects to samples. So I ran it through the
octaver filter. This changes the sound to a mix of different pitches, which adds a distorted and disquieting effect.
live_loop :disturbance do with_fx :octaver do sample :glitch_bass_g, amp: 2.25, rate: rrand(-0.1, -0.4), release: 6 end sleep 6 end
Lastly, I don't want this effect playing constantly or it'd overshadow everything else. So I used the
one_in function to randomly limit how much it plays. For example,
one_in(3) only returns
true 33% of the time. Use this with a conditional and the sample will randomly play that amount less.
live_loop :disturbance do if one_in(3) with_fx :octaver do sample :glitch_bass_g, amp: 2.25, rate: rrand(-0.1, -0.4), release: 6 end end sleep 6 end
The final sound effect I wanted was something like people howling. Sonic Pi actually (and kind of creepily) has a sample called
ambi_choir, one of some people yelling. It's so close I didn't even need a filter, just some property tweaks and another conditional to quiet it down.
live_loop :shout do if one_in(5) sample :ambi_choir, amp: 1.25, rate: 0.5, decay: 2 end sleep 3 end
These two samples add small surprises throughout that are still consistent with the tone.
With that, my Sonic Pi composition is complete! You can read all the song's code here.
I like how this song came out, but this is a relatively simple Sonic Pi project. It's some fancy sound loops timed around each other with some chords and randomness mixed in. Sonic Pi has a tutorial section of increasingly complex examples, with more loop logic and even song phases. Others have composed entire song albums with it. This example is only scratching the surface.
All this shows how Sonic Pi offers both ease of use and lots of depth to explore, so it's fun for new and veteran coders. I plan to make more compositions in the future, and hope you all will too!