Building Textured Algorithmic Music
A step-by-step guide to Roxanne Harris' approach to creating layered, algorithmic compositions in Sonic Pi
Understanding the Approach
Before we dive into code, understand this key concept: The processing is your composition, not the samples. You're designing systems that transform sound, and the samples are just raw material feeding through your creative algorithms.
Start Simple
Just play a sample to hear what we're working with:
sample :loop_safari
What's happening: You're hearing the raw sample in its entirety. This is your source material.
Add Structure and Timing
Add a metronome and live loop structure:
use_bpm 80
live_loop :met do
sleep 1
end
live_loop :f1, sync: :met do
sample :loop_safari
sleep 1
end
What's happening:
use_bpm 80
sets the tempo:met
is your metronome keeping timesync: :met
makes sure everything stays in timesleep 1
plays the sample every beat
Listen: You now have a structured, timed repetition.
Add Euclidean Rhythms
Control when the sample plays using mathematical patterns:
use_bpm 80
live_loop :met do
sleep 1
end
live_loop :f1, sync: :met do
tick
sample :loop_safari,
on: spread(4,16).look # 4 hits spread across 16 steps
sleep 1
end
What's happening:
tick
advances through a pattern each loopspread(4,16)
creates a euclidean rhythm - 4 hits evenly distributed across 16 stepson:
only plays the sample when the pattern is true
Listen: Instead of every beat, you hear 4 strategically spaced hits across a 16-beat cycle.
Visual tip: Try swapping to a shorter sample like :drum_bass_hard
to hear the pattern more clearly, or count along to hear the spacing.
Add Granular Slicing
Now let's slice the sample and play different parts:
use_bpm 80
live_loop :met do
sleep 1
end
live_loop :f1, sync: :met do
tick
sample :loop_safari,
start: line(0, 1, steps: 8).look, # slice the sample into 8 parts
beat_stretch: 16, # stretch sample to fit 16 beats
on: spread(4,16).look
sleep 1
end
What's happening:
start: line(0, 1, steps: 8).look
creates 8 start points from 0% to 100% through the samplebeat_stretch: 16
stretches the sample to exactly 16 beats long- Each hit now plays a different slice of the sample
Listen: You're hearing different textural chunks of the sample, creating variation within the pattern.
Speed Up the Action
Let's hear this at a more musical pace:
use_bpm 80
live_loop :met do
sleep 1
end
live_loop :f1, sync: :met do
tick
sample :loop_safari,
start: line(0, 1, steps: 8).look,
beat_stretch: 16,
on: spread(4,16).look
sleep 0.25 # 4 times faster
end
What's happening: sleep 0.25
means the loop runs every quarter beat, so you cycle through patterns much faster.
Listen: The same pattern but at a more musical pace.
Try switching to :loop_garzul
or another sample to hear how the same process creates different textures.
Control Sample Envelope
Add envelope controls to shape how samples fade in and out:
use_bpm 80
live_loop :met do
sleep 1
end
live_loop :f1, sync: :met do
tick
sample :loop_safari,
start: line(0, 1, steps: 8).look,
amp: 1.5, # louder
sustain: 0, # no sustain phase
release: 0.125 * 3, # 3x the sleep time for overlap
beat_stretch: 16,
on: spread(4,16).look
sleep 0.125 # even faster
end
What's happening:
amp: 1.5
makes it loudersustain: 0
means no held portion - samples start fading immediatelyrelease: 0.125 * 3
creates a fade-out that's 3x longer than our sleep time- This creates overlapping samples that blend together smoothly
Listen: Instead of choppy individual hits, you get flowing, overlapping textures.
Using bt()
Function
Alternative way to calculate timing relationships:
use_bpm 80
live_loop :met do
sleep 1
end
live_loop :f1, sync: :met do
tick
sample :loop_safari,
start: line(0, 1, steps: 8).look,
amp: 1.5,
sustain: 0,
release: bt(0.125)*3, # bt() converts to beat time
beat_stretch: 16,
on: spread(4,16).look
sleep 0.125
end
What's happening: bt(0.125)*3
is another way to express timing in beat terms. Functionally the same as the previous step.
Add Shuffle for Controlled Chaos
Randomize the order of sample slices:
use_bpm 80
live_loop :met do
sleep 1
end
live_loop :f1, sync: :met do
tick
sample :loop_safari,
start: line(0, 1, steps: 8).shuffle.look, # randomize slice order
amp: 1.5,
sustain: 0,
release: bt(0.125)*3,
beat_stretch: 16,
on: spread(4,16).look
sleep 0.125
end
What's happening: .shuffle
randomizes the order of the 8 sample slices, but keeps the same 8 values. It's controlled chaos - familiar elements in unexpected order.
Listen: More unpredictable but still coherent textures.
Shuffle the Rhythm (Warning: This Gets Chaotic!)
Now let's shuffle when things happen too:
use_bpm 80
live_loop :met do
sleep 1
end
live_loop :f1, sync: :met do
tick
sample :loop_safari,
start: line(0, 1, steps: 8).shuffle.look,
amp: 1.5,
sustain: 0,
release: bt(0.125)*3,
beat_stretch: 16,
on: spread(4,16).shuffle.look # shuffle the timing too
sleep 0.125
end
What's happening: Now both the sample slices AND the timing pattern are shuffled. This creates maximum unpredictability.
Listen: This sounds quite chaotic! The randomized timing loses the musical flow. This shows why we need better randomness control...
Introduce Seeded Randomness - The Solution
Replace chaotic shuffling with controlled, organic randomness:
use_bpm 80
live_loop :met do
sleep 1
end
live_loop :f1, sync: :met do
tick
sample :loop_safari,
t: (use_random_source :perlin; use_random_seed 0), # controlled randomness
start: line(0, 1, steps: 8).shuffle.look,
amp: 1.5,
sustain: 0,
release: bt(0.125)*3,
beat_stretch: 16,
on: spread(4,16).shuffle.look
sleep 0.125
end
What's happening:
use_random_source :perlin
uses smooth, natural-feeling randomness (like terrain generation)use_random_seed 0
makes it repeatable every time you run the codet:
is a placeholder parameter - it doesn't affect sound but sets up the random state
Experiment: Try cycling through different random sources - :white
, :pink
, :perlin
- and hear how they feel different. Perlin tends to sound most musical.
Apply Randomness to Timing
Use the seeded randomness to control when samples play:
use_bpm 80
live_loop :met do
sleep 1
end
live_loop :f1, sync: :met do
tick
sample :loop_safari,
t: (use_random_source :perlin; use_random_seed 0),
start: line(0, 1, steps: 8).shuffle.look,
amp: 1.5,
sustain: 0,
release: bt(0.125)*3,
beat_stretch: 16,
t: (use_random_source :perlin; use_random_seed 0), # apply randomness to timing
on: spread(4,16).shuffle.look
sleep 0.125
end
What's happening: We're applying the same randomness to timing decisions. The rhythm becomes more organic while staying musical.
Note: This still sounds a bit boring and predictable - we need more syncopation...
Add Syncopation
Change to a more interesting rhythm pattern:
use_bpm 80
live_loop :met do
sleep 1
end
live_loop :f1, sync: :met do
tick
sample :loop_safari,
t: (use_random_source :perlin; use_random_seed 0),
start: line(0, 1, steps: 8).shuffle.look,
amp: 1.5,
sustain: 0,
release: bt(0.125)*3,
beat_stretch: 16,
t: (use_random_source :perlin; use_random_seed 0),
on: spread(7,16).shuffle.look # 7 hits instead of 4 creates syncopation
sleep 0.125
end
What's happening: spread(7,16)
creates 7 hits across 16 steps. This creates natural syncopation - a rhythm that feels like it's dancing around the beat rather than sitting squarely on it.
Listen: Much more alive and rhythmically interesting!
Layer in a Simple Kick Drum
Start building layers using the same approach:
use_bpm 80
live_loop :met do
sleep 1
end
live_loop :f1, sync: :met do
tick
sample :loop_safari,
t: (use_random_source :perlin; use_random_seed 0),
start: line(0, 1, steps: 8).shuffle.look,
amp: 1.5,
sustain: 0,
release: bt(0.125)*3,
beat_stretch: 16,
t: (use_random_source :perlin; use_random_seed 0),
on: spread(7,16).shuffle.look
# Add a simple kick pattern
sample :bd_fat, on: spread(1, 4).look
sleep 0.125
end
What's happening: We add a kick drum playing once every 4 steps - a simple, steady foundation under our textural loop.
Switch to Better Kick and Add Processing
Upgrade the kick drum with better sound and processing:
use_bpm 80
live_loop :met do
sleep 1
end
live_loop :f1, sync: :met do
tick
sample :loop_safari,
t: (use_random_source :perlin; use_random_seed 0),
start: line(0, 1, steps: 8).shuffle.look,
amp: 1.5,
sustain: 0,
release: bt(0.125)*3,
beat_stretch: 16,
t: (use_random_source :perlin; use_random_seed 0),
on: spread(7,16).shuffle.look
sample :bd_808, # punchier 808-style kick
on: spread(1, 4).look,
compress: 1, # heavy compression for punch
pre_amp: 5 # boost before compression
sleep 0.125
end
What's happening:
:bd_808
is a punchier kick drum samplecompress: 1
adds heavy compression for that modern punchy soundpre_amp: 5
boosts the signal before compression, creating more aggressive compression
Make Kick Rhythm More Interesting
Give the kick a more complex pattern:
use_bpm 80
live_loop :met do
sleep 1
end
live_loop :f1, sync: :met do
tick
sample :loop_safari,
t: (use_random_source :perlin; use_random_seed 0),
start: line(0, 1, steps: 8).shuffle.look,
amp: 1.5,
sustain: 0,
release: bt(0.125)*3,
beat_stretch: 16,
t: (use_random_source :perlin; use_random_seed 0),
on: spread(7,16).shuffle.look
sample :bd_808,
on: spread(3, 8).look, # 3 hits across 8 steps - more interesting pattern
compress: 1,
pre_amp: 5
sleep 0.125
end
What's happening: spread(3, 8)
creates 3 kick hits across 8 steps, giving us a more complex but still musical kick pattern.
Add Randomness to Kick
Apply the same randomness approach to the kick drum:
use_bpm 80
live_loop :met do
sleep 1
end
live_loop :f1, sync: :met do
tick
sample :loop_safari,
t: (use_random_source :perlin; use_random_seed 0),
start: line(0, 1, steps: 8).shuffle.look,
amp: 1.5,
sustain: 0,
release: bt(0.125)*3,
beat_stretch: 16,
t: (use_random_source :perlin; use_random_seed 0),
on: spread(7,16).shuffle.look
sample :bd_808,
t: (use_random_source :dark_pink; use_random_seed look), # different random source
on: spread(3,8).shuffle.look, # add shuffle for variation
compress: 1,
pre_amp: 5
sleep 0.125
end
What's happening:
use_random_source :dark_pink
gives the kick a different randomness character than the main loopuse_random_seed look
means the randomness changes as the tick advances, creating evolution.shuffle
on the kick pattern adds more variation
Add Hi-Hats (Quiet at First)
Layer in hi-hats - you might not hear them clearly at first:
use_bpm 80
live_loop :met do
sleep 1
end
live_loop :f1, sync: :met do
tick
sample :loop_safari,
t: (use_random_source :perlin; use_random_seed 0),
start: line(0, 1, steps: 8).shuffle.look,
amp: 1.5,
sustain: 0,
release: bt(0.125)*3,
beat_stretch: 16,
t: (use_random_source :perlin; use_random_seed 0),
on: spread(7,16).shuffle.look
sample :bd_808,
t: (use_random_source :dark_pink; use_random_seed look),
on: spread(3,8).shuffle.look,
compress: 1,
pre_amp: 5
# Add hi-hats - might be hard to hear at first
sample :hat_bdu,
t: (use_random_source :light_pink; use_random_seed look), # third random source
on: spread(4,8).shuffle.look, # different pattern from kick
lpf: 1, # low-pass filter (we'll develop this more)
amp: 0.75 # quieter than other elements
sleep 0.125
end
What's happening:
:light_pink
gives hi-hats their own randomness characterspread(4,8)
creates a different pattern than the kicklpf: 1
applies low-pass filtering (very subtle here)amp: 0.75
keeps them in the background
Note: The hi-hats might be hard to hear in the mix - this is normal. We'll make them more prominent next.
Dynamic Hi-Hat Filtering
Make the hi-hats more interesting with evolving filter effects:
use_bpm 80
live_loop :met do
sleep 1
end
live_loop :f1, sync: :met do
tick
sample :loop_safari,
t: (use_random_source :perlin; use_random_seed 0),
start: line(0, 1, steps: 8).shuffle.look,
amp: 1.5,
sustain: 0,
release: bt(0.125)*3,
beat_stretch: 16,
t: (use_random_source :perlin; use_random_seed 0),
on: spread(7,16).shuffle.look
sample :bd_808,
t: (use_random_source :dark_pink; use_random_seed look),
on: spread(3,8).shuffle.look,
compress: 1,
pre_amp: 5
sample :hat_bdu,
t: (use_random_source :light_pink; use_random_seed look),
on: spread(4,8).shuffle.look,
t: (use_random_source :light_pink; use_random_seed look % (16*4)), # longer cycle
lpf: line(110,118, steps: 4).shuffle.look, # evolving filter
amp: 0.75
sleep 0.125
end
What's happening:
look % (16*4)
creates a longer 64-step cycle for some parameterslpf: line(110,118, steps: 4).shuffle.look
creates a low-pass filter that moves between 110Hz and 118Hz- The filter frequency changes in shuffled order, creating evolving timbral changes
- The modulo operation means some changes happen on longer cycles than others
Listen: The hi-hats now have a subtle but evolving character as the filter frequency changes.
Final Result: Complete Textured Composition
You now have a complete three-layer algorithmic composition:
Complete Code Structure:
use_bpm 80
live_loop :met do
sleep 1
end
live_loop :f1, sync: :met do
tick
s = sample :loop_safari,
t: (use_random_source :perlin; use_random_seed 0),
start: line(0,1, steps: 8).shuffle.look,
amp: 1.5, sustain: 0, release: bt(0.125)*3,
beat_stretch: 16,
t: (use_random_source :perlin; use_random_seed 0),
on: spread(7,16).shuffle.look
sample :bd_808,
t: (use_random_source :dark_pink; use_random_seed look),
on: spread(3,8).shuffle.look,
compress: 1, pre_amp: 5
sample :hat_cab,
t: (use_random_source :light_pink; use_random_seed look),
on: spread(4,8).shuffle.look,
t: (use_random_source :light_pink; use_random_seed look % (16*4)),
lpf: line(110, 118, steps: 4).shuffle.look,
amp: 0.80
puts s.args
sleep 0.125
end
Next Steps: Experimentation
Try These Experiments:
- Swap samples: Try different source materials with the same processing
- Adjust spread patterns: Experiment with different euclidean rhythms
- Change random sources: Each source (
:perlin
,:pink
,:white
, etc.) has different character - Modify timing relationships: Try different sleep values or release times
- Add effects: Experiment with
reverb:
,echo:
,distort:
parameters
Remember: You're not making music with samples - you're making music with processes that transform samples into something entirely new.
Related Topics
WIP