Last episode we built a little machine that writes tunes - fresh melodies, always in key, leaning on a chord progression underneath. And I ended, as I keep doing lately, on a small confession: every note our machine played was a tidy, even eighth-note, marching in a straight line like little soldiers. We solved which notes to play and completely dodged when. Well, today we give the tune a heartbeat. Today we do rhythm, and honestly it's the part that took me the longest to respect - I spent years thinking melody was the hard bit and rhythm was just "the beat underneath". That's exactly backwards. Rhythm is where music becomes alive. Allez, let's make some grooves :-).
Here's the thing I want you to feel before any code: the same eight notes, played dead-straight, sound like a ringtone. Played with a bit of swing, a couple of gaps, an accent in the right place - they sound like a song. Nothing about the pitches changed. All of it is timing. So if last episode was about taming pitch with constraints, this one is about sculpting time, and it turns out you can do an astonishing amount with a handful of very simple ideas. Let me show you the parts.
Everything rhythmic in Tone.js hangs off one object - Tone.Transport. It's the master clock, the conductor. It has a tempo (BPM), it has a position, it can start and stop, and crucially it lets you schedule events in musical time ("play this on every quarter note") instead of raw seconds. That last bit is the whole reason it exists, and it's genuinly lovely to work with.
// the Transport is the master musical clock. everything syncs to it.
Tone.Transport.bpm.value = 120; // 120 beats per minute
Tone.Transport.timeSignature = 4; // 4 beats per bar (4/4 time)
// you never work in seconds - you work in musical notation:
// "4n" = a quarter note (one beat) "8n" = eighth note (half a beat)
// "16n" = sixteenth (quarter beat) "1n" = a whole bar
// "2n" = half note "8t" = eighth-note triplet
The magic of musical time is that it scales with tempo. If you schedule something on every "8n" and then change the BPM, the timing follows automatically - you never recompute anything. Change one number and the whole piece speeds up or slows down, perfectly locked. Try doing that with raw setTimeout and you'll cry.
// schedule a repeating event in musical time - it tracks tempo automatically.
const synth = new Tone.MembraneSynth().toDestination(); // a punchy drum-ish synth
const kick = new Tone.Loop((time) => {
synth.triggerAttackRelease("C1", "8n", time); // a kick on every beat
}, "4n"); // "4n" = fire once per quarter note
kick.start(0); // schedule it to begin at the very start
Tone.Transport.start(); // and the clock rolls (behind a click, always!)
One important habit, the same one from the last two episodes: that time argument the loop hands you is sacred. Always pass it straight into triggerAttackRelease. It's the exact, sample-accurate moment the note should sound, computed ahead of time by the audio engine. If you ignore it and just trigger "now", your rhythm will wobble and drift, because JavaScript timing is sloppy. The Transport is precise; trust its clock, not the browser's.
Now the workhorse of all electronic rhythm - the step sequencer. Picture a grid: rows are instruments (kick, snare, hi-hat), columns are steps in time (usually 16, one bar of sixteenth notes). Each cell is on or off. That's it. That grid is a drum machine, the same idea behind every 808 and every beat you've ever nodded your head to.
// a step sequencer = a grid. rows are instruments, columns are 16 time-steps.
// 1 = hit, 0 = rest. this is a classic four-on-the-floor house beat.
const pattern = {
kick: [1,0,0,0, 1,0,0,0, 1,0,0,0, 1,0,0,0], // every 4th step - the pulse
snare: [0,0,0,0, 1,0,0,0, 0,0,0,0, 1,0,0,0], // on beats 2 and 4 - the backbeat
hat: [1,0,1,0, 1,0,1,0, 1,0,1,0, 1,0,1,0], // every other step - the shimmer
};
To play the grid, we walk one step at a time with a loop on "16n", keep a counter, and for each instrument check if its cell at the current step is a 1. If so, hit it. The whole drum machine is one loop and a modulo.
// three cheap drum voices built from synthesis (episode 119 - it's all noise & sines!)
const drum = {
kick: new Tone.MembraneSynth().toDestination(),
snare: new Tone.NoiseSynth({ envelope: { attack: 0.001, decay: 0.2, sustain: 0 } }).toDestination(),
hat: new Tone.MetalSynth({ envelope: { attack: 0.001, decay: 0.05, sustain: 0 } }).toDestination(),
};
let step = 0;
const seq = new Tone.Loop((time) => {
const s = step % 16; // which column are we on?
if (pattern.kick[s]) drum.kick.triggerAttackRelease("C1", "8n", time);
if (pattern.snare[s]) drum.snare.triggerAttackRelease("8n", time);
if (pattern.hat[s]) drum.hat.triggerAttackRelease("32n", time);
step++;
}, "16n"); // advance one step every sixteenth note
seq.start(0);
That's a complete, playable drum machine in about fifteen lines. Flip any 0 to a 1 and you've written a new beat. This is the foundational instrument of a whole genre of music, and it's just an array of ones and zeros walked by a modulo. See where this is going? Once rhythm is data, we can generate it.
Here's my absolute favourite trick in this whole episode, and it's a proper "how is this not more famous" moment. Say you want to spread 5 hits as evenly as possible across 16 steps. You could place them by hand, but there's an algorithm that does it perfectly for any number of hits over any number of steps - and it turns out the patterns it produces are the traditional rhythms of half the planet. Cuban, Brazilian, West African, Balkan - all of them fall out of one little function. It's called the Euclidean rhythm, because it's built on the same "spread things evenly" logic as Euclid's ancient algorithm for greatest common divisors.
// Euclidean rhythm: distribute `hits` as evenly as possible across `steps`.
// this one function generates traditional rhythms from all over the world.
function euclid(hits, steps) {
const pattern = [];
let bucket = 0;
for (let i = 0; i < steps; i++) {
bucket += hits;
if (bucket >= steps) {
bucket -= steps;
pattern.push(1); // a hit lands here
} else {
pattern.push(0); // a rest
}
}
return pattern;
}
That "bucket" trick (a running accumulator that spills over) is the same idea as drawing a straight line on a pixel grid - you're deciding, at each step, whether enough has piled up to justify a mark. And the rhythms it produces are shockingly musical. Let me show you a few famous ones so you believe me.
// E(hits, steps) - some of these are literally named world rhythms.
euclid(4, 16); // [1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0] four-on-the-floor
euclid(3, 8); // [1,0,0,1,0,0,1,0] the Cuban tresillo
euclid(5, 8); // [1,0,1,1,0,1,1,0] the Cuban cinquillo
euclid(5, 16); // [1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,0] Bossa Nova-ish
euclid(7, 16); // a busy, rolling West-African bell pattern
Feed any of those into the step sequencer from before as an instrument's row, and it instantly grooves. What I love is that you now have a dial for rhythm - change hits from 3 to 4 to 5 and the feel shifts from sparse to driving, all evenly spaced, never clumsy. It's the rhythmic equivalent of last episode's scale: a constraint that makes "wrong" almost impossible. You could build an entire generative drummer just by picking random (hits, steps) pairs.
Now we stack. What happens if you run two rhythms of different lengths at the same time? Say a pattern that repeats every 3 steps against one that repeats every 4. They line up on step 1, then drift apart, then slowly crawl back into alignment 12 steps later (3 x 4). That drifting-and-realigning is a polyrhythm, and it is genuinly hypnotic - it's the shimmer you hear in a lot of African drumming, in Steve Reich, in a ton of electronic music.
// polyrhythm: two loops of DIFFERENT lengths running at once. they phase against
// each other and only realign every (lengthA x lengthB) steps. mesmerising.
const three = euclid(3, 12); // a 3-feel
const four = euclid(4, 12); // a 4-feel, same grid length so they share a clock
// but the classic trick is different SPEEDS on the same span. "3 against 4":
const loopA = new Tone.Loop((t) => drum.hat.triggerAttackRelease("32n", t), "4n").start(0); // 4 per bar
const loopB = new Tone.Loop((t) => drum.snare.triggerAttackRelease("16n", t), "4t").start(0); // 3 per bar (triplets)
The "4t" there is the key - the t means triplet, so "4t" fits three events into the space of two quarter notes, giving you the 3-against-4 cross-rhythm without any manual bookkeeping. Your ear can't quite lock onto either grid, so it keeps sliding between them, and that tension is the whole appeal. This connects straight back to episode 13, by the way, where we met periodic functions in trigonometry - a polyrhythm is just two periods with a large least-common-multiple, exactly like two sine waves of different frequencies beating against each other.
Here's the one that changed how I hear everything. A dead-straight sequencer sounds mechanical because every step is perfectly even. Real players don't do that - they play every second note a hair late, and that tiny delay is what we call swing. It's the single biggest difference between "programmed" and "played". And it's almost nothing in code: you just nudge the odd-numbered steps slightly later.
// swing: delay every ODD 16th-note step by a fraction of a step.
// 0 = dead straight (robotic), ~0.5-0.6 = classic swing/shuffle feel.
const SWING = 0.55;
const swungSeq = new Tone.Loop((time) => {
const s = step % 16;
const isOffbeat = s % 2 === 1; // the "and" between beats
const stepDur = Tone.Time("16n").toSeconds();
const delay = isOffbeat ? stepDur * (SWING - 0.5) : 0; // push offbeats late
const t = time + delay;
if (pattern.hat[s]) drum.hat.triggerAttackRelease("32n", t);
step++;
}, "16n");
Tone.js actually has this built right into the Transport, which is even easier - you set a swing amount and a subdivision and it applies it globally to everything scheduled.
// the built-in way: Transport handles swing for ALL scheduled events at once.
Tone.Transport.swing = 0.55; // 0 straight -> 1 maximum shuffle
Tone.Transport.swingSubdivision = "16n"; // which grid gets the swing feel
Play a beat straight, then dial swing up to 0.55, and I promise you'll grin. Suddenly it breathes. That little bit of lateness is your ear reading "a human made this". It's the exact same lesson as the melody episode - perfectly even is perfectly dead, and a sprinkle of controlled irregularity is what reads as alive. Makes sense, right?
Two more dials that take a beat from "loop" to "performance". The first is probability: instead of a step being a hard 1 or 0, give it a chance of firing. A hi-hat at 80% probability hits most of the time but occassionally drops a note, and that little unpredictability keeps the ear interested over a long stretch without ever sounding random.
// probability triggers: each step has a CHANCE to fire, not a certainty.
// this adds life over long loops - the beat never quite repeats identically.
const hatProb = [0.9,0.3,0.8,0.4, 0.9,0.3,0.8,0.5, 0.9,0.3,0.8,0.4, 0.9,0.3,0.8,0.6];
const probSeq = new Tone.Loop((time) => {
const s = step % 16;
if (Math.random() < hatProb[s]) { // roll the dice for this step
drum.hat.triggerAttackRelease("32n", time);
}
step++;
}, "16n");
The second is accent - not whether a note plays but how hard. In real drumming some hits are loud and some are ghost-notes, and that dynamic shape is half the groove. We control it with velocity (the third argument to triggerAttackRelease), emphasising the downbeats and softening the rest.
// accents: vary the VELOCITY (loudness) per step. loud downbeats, soft ghost-notes.
const accents = [1.0,0.4,0.6,0.4, 0.9,0.4,0.6,0.4, 1.0,0.4,0.6,0.4, 0.9,0.4,0.7,0.5];
const accentSeq = new Tone.Loop((time) => {
const s = step % 16;
if (pattern.hat[s]) {
const vel = accents[s]; // 0..1 loudness for this hit
drum.hat.triggerAttackRelease("32n", time, vel);
}
step++;
}, "16n");
Stack these together - Euclidean base pattern, a touch of swing, probability on the hats, accents on the downbeats - and a flat grid of ones and zeros turns into something that genuinly sounds like a drummer with taste. None of these ideas is complicated on its own; the richness comes from layering them, exactly like we've been layering everything since episode 1.
One more musical idea worth stealing. Real drummers don't play the same bar forever - every fourth or eighth bar they play a fill, a busier little flourish that signals "a new section is coming". You can generate these algorithmically: count the bars, and on every fourth one, swap the normal pattern for a denser one.
// fills: every 4th bar, replace the pattern with something busier to mark the turn.
let barCount = 0;
const fillSeq = new Tone.Loop((time) => {
const s = step % 16;
const isFillBar = (barCount % 4) === 3; // the 4th bar of every group
if (isFillBar && s >= 12) {
// last 4 steps of the fill bar: rapid snare roll
drum.snare.triggerAttackRelease("16n", time, 0.6 + (s - 12) * 0.1);
} else if (pattern.snare[s]) {
drum.snare.triggerAttackRelease("8n", time, 0.8);
}
step++;
if (s === 15) barCount++; // count a bar each time we wrap
}, "16n");
That's a tiny amount of code for a musically huge effect - fills are how a track breathes in and out of its sections, and now yours can generate its own. This is also our first taste of structure over time, of a piece that changes as it goes rather than looping forever, which is a thread we'll pull much harder soon.
We're visual people here, so let's draw the beat. The classic linear grid is fine, but a circular sequencer is prettier and, for polyrhythm, far more revealing - you lay the steps around a clock face and sweep a hand around it. Because a rhythm is periodic, a circle is its natural home (there's episode 13 again - going round a circle is a periodic function).
// draw a rhythm as a ring of dots; light up the one we're currently on.
function drawRing(p, pattern, radius, currentStep) {
const n = pattern.length;
for (let i = 0; i < n; i++) {
const angle = (i / n) * p.TWO_PI - p.HALF_PI; // start at 12 o'clock
const x = p.width / 2 + p.cos(angle) * radius;
const y = p.height / 2 + p.sin(angle) * radius;
const isHit = pattern[i] === 1;
const isNow = i === currentStep;
p.fill(isNow ? p.color(255, 80, 80) : (isHit ? 255 : 60));
p.circle(x, y, isHit ? 22 : 10); // hits are big dots, rests small
}
}
Put two of those rings concentrically - one for a 3-hit Euclidean pattern, one for a 5-hit - each with its own sweeping hand, and you can literally watch the polyrhythm phase. The moment both hands point at a lit dot together is the moment your ear hears the beats collide. Seeing it and hearing it line up is one of those little "oh!" moments that makes this stuff so addictive.
And this is the real prize, the reason a creative coder cares about all this and not just a musician: once the rhythm is running on the Transport, every hit is an event you can hook a visual onto. A kick flashes the screen, a snare bursts a cloud of particles, a hi-hat throws a little spark. The beat you built becomes the heartbeat of the animation.
// each drum hit fires a visual event. the rhythm IS the animation's pulse.
let flash = 0;
const bursts = [];
const visualSeq = new Tone.Loop((time) => {
const s = step % 16;
// schedule the VISUAL to fire at the same audio-accurate moment:
Tone.Draw.schedule(() => {
if (pattern.kick[s]) flash = 1; // kick -> screen flash
if (pattern.snare[s]) bursts.push({ x: 200, y: 200, r: 0 }); // snare -> particle burst
}, time);
if (pattern.kick[s]) drum.kick.triggerAttackRelease("C1", "8n", time);
if (pattern.snare[s]) drum.snare.triggerAttackRelease("8n", time);
step++;
}, "16n");
That Tone.Draw.schedule is the important bit - it hands your visual code the same precise timestamp the audio uses, so the flash lands exactly with the kick instead of a frame or two late. Sound and picture locked together, both driven off one clock. That tight sync between what you hear and what you see is a whole craft of its own, and it's very much where this little audio arc is heading next.
Right, let's tie the whole toolbox into one satisfying build. The brief: a drum machine running two Euclidean rhythms as a polyrhythm - a kick on E(4,16) and a snare on E(3,8) - plus a steady hi-hat, all with a bit of swing, and visualised as two concentric rotating rings so you can see the polyrhythm breathe.
// THE POLYRHYTHMIC DRUM MACHINE. two Euclidean tracks + hats, swung, visualised.
const kickPat = euclid(4, 16); // evenly spread 4 kicks
const snarePat = euclid(3, 8); // 3 snares over 8 - a different length = polyrhythm
Tone.Transport.bpm.value = 96;
Tone.Transport.swing = 0.5;
Tone.Transport.swingSubdivision = "16n";
Two independent step counters, because the two patterns are different lengths - the kick wraps every 16, the snare every 8, and that mismatch is exactly what makes it a polyrhythm.
// two counters because the patterns are different lengths - that's the poly bit.
let kStep = 0, sStep = 0;
const machine = new Tone.Loop((time) => {
const k = kStep % 16, s = sStep % 8;
if (kickPat[k]) drum.kick.triggerAttackRelease("C1", "8n", time, 1.0);
if (snarePat[s]) drum.snare.triggerAttackRelease("8n", time, 0.8);
drum.hat.triggerAttackRelease("32n", time, k % 2 ? 0.3 : 0.6); // accented hats
kStep++; sStep++;
}, "16n").start(0);
Then the p5 draw loop paints the two rings, using the live counters so the sweeping hands match what you're hearing, and you get a picture that pulses exactly with the sound.
// p5 side: two concentric rings, each showing its pattern + current position.
function sketchDraw(p) {
p.background(15);
drawRing(p, kickPat, 160, kStep % 16); // outer ring = kick
drawRing(p, snarePat, 100, sStep % 8); // inner ring = snare
// when both current dots are hits, the beats collide - flash a faint halo
if (kickPat[kStep % 16] && snarePat[sStep % 8]) {
p.noFill(); p.stroke(255, 120); p.circle(p.width/2, p.height/2, 380);
}
}
Wire it behind a "click to start" button (never forget Tone.start() - we learned that the hard way two episodes back), and you've got a machine that grooves and a picture that grooves with it, both off the same clock. Sit and watch the two rings drift and realign - that slow phasing is the polyrhythm, made visible.
Push it when you've got an evening: add probability to the hats so the beat never quite repeats, throw a snare fill on every fourth bar, let the mouse-Y set the BPM so you can speed the whole thing up and down, or swap E(4,16) for E(5,16) and feel the groove tilt. It's your rhythm box now, and it'll never play the exact same bar twice :-).
Tone.Transport) is the master musical clock. You schedule everything in musical time ("8n", "16n", "4t"), never raw seconds - so changing the BPM re-times the whole piece automatically. Always pass the loop's time argument straight into your triggers for sample-accurate timingeuclid(hits, steps) spreads hits as evenly as possible with a little spill-over accumulator, and out fall the traditional rhythms of half the world (tresillo, cinquillo, bossa). One function, a hundred grooves, and a single dial (hits) for sparse-to-drivingTone.Draw.schedule, so kick-flashes and snare-bursts land locked to the beat. The rhythm becomes the heartbeat of the animationSo that's rhythm - the part I used to underrate and now think is where music actually lives. You can build a drum machine, generate world rhythms from one algorithm, stack them into hypnotic polyrhythms, and make the whole thing swing and breathe and drive your visuals. That's a genuinly powerful place to be standing: you've now got both halves, the what (melody and harmony) and the when (rhythm), and they run off the same clock.
Which raises the obvious next question, doesn't it? We've been making things that pulse and hit and groove - punchy, rhythmic, driving. But some of the most beautiful generative music does the exact opposite: it drifts. No beat, no grid, just slow evolving washes of sound that never quite repeat, the kind of thing you could leave running for an hour and never get bored of. Building that needs a completely different mindset from the tidy grids we drew today, and it's one of my favourite corners of this whole world. Build your polyrhythmic drum machine first - genuinly sit and watch the rings phase until it clicks - and then come back, because next time we let the grid dissolve :-).
Sallukes! Thanks for reading.
X