I've been coding in Ruby for years. Then I discovered you can make actual music with it. Not metaphorical music. Real sounds coming out of your speakers. Sonic Pi turns Ruby into a musical instrument, and the learning curve is surprisingly gentle if you already know the language.
What is Sonic Pi?
Sonic Pi is a free, open-source music creation tool built on top of Ruby. Dr. Sam Aaron created it at Cambridge University, originally as a way to teach programming to kids through music. It has since grown into a serious tool used by professional musicians for live coding performances.
Under the hood, Sonic Pi uses SuperCollider as its audio engine. You write Ruby-like code in the built-in editor, hit Run, and sound comes out. It ships with a huge library of synthesizers, audio samples, and effects — all controllable through code.
Download it from sonic-pi.net. Works on Mac, Windows, and Linux. The installer is straightforward — no dependencies to manage, no gem installs. Everything is bundled. If you are interested in packaging your own Ruby tools, check out building your first Ruby gem.
Your First Melody
Open Sonic Pi and paste this:
use_bpm 120
live_loop :melody do
play :C4
sleep 0.5
play :E4
sleep 0.5
play :G4
sleep 0.5
play :B4
sleep 0.5
endHit Run. You'll hear a simple arpeggio looping forever.
Here's what's happening:
use_bpm 120sets the tempo to 120 beats per minutelive_loopcreates a named loop that keeps running until you hit Stopplay :C4plays middle C using the default synthesizersleep 0.5waits half a beat before the next note
That's it. Four notes, looping. You just made music with Ruby.
One thing to notice: sleep in Sonic Pi doesn't work like sleep in regular Ruby. It's measured in beats, not seconds. At 120 BPM, sleep 0.5 is a quarter of a second. Change use_bpm to 60 and that same sleep 0.5 becomes half a second.
Choosing Synths and Shaping Sound
The default synth is :beep, which sounds pretty basic. Sonic Pi includes dozens of built-in synthesizers. Switch between them with use_synth:
use_bpm 120
live_loop :melody do
use_synth :prophet
play :C4, attack: 0.1, sustain: 0.2, release: 0.3
sleep 0.5
play :E4, attack: 0.1, sustain: 0.2, release: 0.3
sleep 0.5
play :G4, attack: 0.1, sustain: 0.2, release: 0.3
sleep 0.5
play :B4, attack: 0.1, sustain: 0.2, release: 0.3
sleep 0.5
endThe attack, sustain, and release parameters control the envelope — how the note fades in, holds, and fades out. These three values shape the character of every note:
- attack — time in beats for the sound to reach full volume
- sustain — time the note holds at full volume
- release — time for the sound to fade to silence
Try :tb303 for acid bass, :dark_ambience for pads, or :pluck for guitar-like tones. You can list all available synths in the Sonic Pi help panel under Synths.
Adding Depth with Multiple Loops
Let's layer a bass line underneath the melody:
use_bpm 120
live_loop :melody do
use_synth :pluck
play :C4
sleep 0.5
play :E4
sleep 0.5
play :G4
sleep 0.5
play :B4
sleep 0.5
end
live_loop :bass do
use_synth :tb303
play :C2, release: 2, cutoff: 70
sleep 2
endNow you have two loops running concurrently. The bass hits every 2 beats while the melody keeps going. Each live_loop runs in its own thread — Sonic Pi handles the concurrency for you.
The cutoff parameter controls a low-pass filter. Lower values give a darker, more muted sound. Higher values (up to 130) let more brightness through. This is one of the most useful parameters for shaping tone.
You can add as many loops as your CPU can handle. Here's a drum pattern layered on top:
use_bpm 120
live_loop :drums do
sample :bd_haus
sleep 0.5
sample :sn_dub
sleep 0.5
end
live_loop :hihat do
sample :drum_cymbal_closed, amp: 0.5
sleep 0.25
end
live_loop :melody do
use_synth :pluck
play :C4
sleep 0.5
play :E4
sleep 0.5
play :G4
sleep 0.5
play :B4
sleep 0.5
end
live_loop :bass do
use_synth :tb303
play :C2, release: 2, cutoff: 70
sleep 2
endThe sample command plays pre-recorded audio files. Sonic Pi ships with hundreds of samples — kicks, snares, hi-hats, ambient textures, and more. The amp parameter controls volume (1.0 is default).
Using Effects
Sonic Pi has a built-in effects chain. Wrap any code in a with_fx block to apply reverb, distortion, echo, and more:
use_bpm 100
live_loop :ambient do
use_synth :dark_ambience
with_fx :reverb, room: 0.8, mix: 0.7 do
with_fx :echo, phase: 0.75, decay: 4, mix: 0.5 do
play choose(scale(:E3, :minor_pentatonic)), amp: 0.7, attack: 1, sustain: 2, release: 2
sleep 2
end
end
endEffects can be nested. In this example, each note first passes through echo, then through reverb. The mix parameter on each effect controls how much of the effect you hear versus the dry signal. A mix of 0 means no effect; 1.0 means fully wet.
Some useful effects to experiment with:
:reverb— room simulation, great for ambient textures:echo— repeating delay:distortion— gritty overdrive:lpf— low-pass filter, cuts high frequencies:flanger— sweeping, jet-like sound
Randomness and Generative Music
Want something less predictable? Sonic Pi gives you deterministic randomness through choose and rrand:
use_bpm 120
live_loop :generative do
use_synth :prophet
notes = scale(:C4, :minor_pentatonic)
play notes.choose, amp: rrand(0.4, 0.8), release: rrand(0.2, 0.6)
sleep [0.25, 0.25, 0.5].choose
endA few things are happening here:
scale(:C4, :minor_pentatonic)returns an array of notes in C minor pentatonic — a scale that always sounds good no matter which notes you combinenotes.choosepicks a random note from that scalerrand(0.4, 0.8)returns a random float between 0.4 and 0.8 for dynamic variation- The sleep duration is also randomized, giving the rhythm an organic feel
This style of composing with higher-order functions and immutable data echoes functional programming principles in Ruby. Here's the interesting part: Sonic Pi's randomness is deterministic by default. Every time you hit Run, you get the same sequence of "random" numbers. This means your generative composition is reproducible. Use use_random_seed to get different sequences:
use_bpm 120
live_loop :generative do
use_random_seed Time.now.to_i
use_synth :pluck
notes = scale(:A3, :blues_minor)
16.times do
play notes.choose, amp: rrand(0.3, 0.9)
sleep 0.25
end
endNow each 4-beat phrase is truly different every time.
Playing Chords and Rings
You can play multiple notes simultaneously with play_chord, and use rings for repeating patterns:
use_bpm 90
live_loop :chords do
use_synth :prophet
progression = [chord(:C4, :major7),
chord(:A3, :minor7),
chord(:F3, :major7),
chord(:G3, :dominant7)]
progression.each do |c|
play_chord c, attack: 0.2, sustain: 0.8, release: 1.0
sleep 2
end
end
live_loop :arpeggio do
use_synth :pluck
pattern = (ring :C4, :E4, :G4, :B4, :G4, :E4)
play pattern.tick, release: 0.3
sleep 0.25
endThe ring data structure is circular — when .tick reaches the end, it wraps back to the beginning. This is perfect for repeating melodic patterns. The .tick method advances through the ring one element at a time on each loop iteration.
Live Coding: Changing Music in Real Time
The real power of live_loop is that you can modify code while the music plays. Hit Run again after making changes, and Sonic Pi hot-swaps your loops at the next cycle boundary. No glitches, no restarts.
Try this workflow:
- Start with a simple drum loop
- Hit Run
- While it plays, add a bass line in a new
live_loop - Hit Run again — the bass joins in without stopping the drums
- Keep adding layers
This is how live coding performers work on stage. They start with silence and build up a track in real time in front of an audience.
A Complete Track
Here's a more complete example that puts everything together:
use_bpm 110
live_loop :kick do
sample :bd_haus, amp: 1.2
sleep 0.5
end
live_loop :snare do
sleep 0.5
sample :sn_dub, amp: 0.8
sleep 0.5
end
live_loop :hihat do
sample :drum_cymbal_closed, amp: rrand(0.2, 0.5)
sleep 0.25
end
live_loop :bass do
use_synth :tb303
pattern = (ring :C2, :C2, :Eb2, :F2, :G2, :F2, :Eb2, :C2)
play pattern.tick, release: 0.3, cutoff: rrand(60, 90), amp: 0.8
sleep 0.5
end
live_loop :pad do
use_synth :dark_ambience
with_fx :reverb, room: 0.9 do
play_chord chord(:C3, :minor7), attack: 2, sustain: 4, release: 2, amp: 0.3
sleep 8
end
end
live_loop :lead do
use_synth :prophet
with_fx :echo, phase: 0.375, decay: 3, mix: 0.4 do
notes = scale(:C4, :minor_pentatonic)
play notes.choose, amp: rrand(0.3, 0.6), release: rrand(0.2, 0.5)
sleep [0.25, 0.5, 0.25].choose
end
endSix concurrent loops: kick, snare, hi-hat, bass, pad, and a generative lead melody. Copy this into Sonic Pi, hit Run, and you have a full electronic track.
What You Can Build From Here
Once you get the basics, the possibilities open up:
- Algorithmic compositions — Let the code decide what to play next based on rules you define. If you enjoy creative coding, you might also like building games with Ruby and Gosu
- Live coding performances — Build tracks on stage, in front of an audience
- Generative ambient music — Background sounds that evolve and never repeat exactly
- MIDI integration — Control external hardware synths from Sonic Pi with
midi_note_on - OSC communication — Send and receive Open Sound Control messages to connect with other music software
I started with simple loops. Now I use it for background music while working. The best part is the feedback loop — change a number, hit Run, and the music shifts instantly. No compile step, no build process. Just Ruby and sound.
Getting Started
- Download Sonic Pi from sonic-pi.net
- Paste the examples from this article
- Change the notes, synths, and timing
- Read the built-in tutorial (Help > Tutorial) — it's excellent
- Explore the Synths and FX panels to see what's available
You don't need to know music theory. The pentatonic scale trick (used in several examples above) guarantees that random note choices will always sound musical. You don't need expensive software either. Sonic Pi is free, and it ships everything you need.
Give it 20 minutes. Start with the first example, then keep adding layers. Before you know it, you'll have something that actually sounds good — and you built it with Ruby.