Making Music with Ruby and Sonic Pi

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
end

Hit Run. You'll hear a simple arpeggio looping forever.

Here's what's happening:

  • use_bpm 120 sets the tempo to 120 beats per minute
  • live_loop creates a named loop that keeps running until you hit Stop
  • play :C4 plays middle C using the default synthesizer
  • sleep 0.5 waits 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
end

The 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
end

Now 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
end

The 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
end

Effects 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
end

A 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 combine
  • notes.choose picks a random note from that scale
  • rrand(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
end

Now 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
end

The 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:

  1. Start with a simple drum loop
  2. Hit Run
  3. While it plays, add a bass line in a new live_loop
  4. Hit Run again — the bass joins in without stopping the drums
  5. 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
end

Six 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

  1. Download Sonic Pi from sonic-pi.net
  2. Paste the examples from this article
  3. Change the notes, synths, and timing
  4. Read the built-in tutorial (Help > Tutorial) — it's excellent
  5. 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.