Beats &
rhythm
The synth lesson was about frequency — wobbles per second, fast enough to hear as pitch. This one is about time itself: the slow grid of when things happen. Same idea — events on a clock — just slowed down until you feel it as a beat. Build one. Hear it loop.
One steady heartbeat.
Tap your foot to any song and you'll find it: a steady, even throb underneath everything. That throb is the beat — the pulse you'd clap to, the thing a conductor's hand marks. It is the most basic fact of rhythm: a regular event, repeating in time.
Notice how little it takes. We haven't said anything about pitch, melody, or which drum — only when. Rhythm is the art of arranging events in time, and the beat is the ruler we measure that time against. Everything in this lesson is a way of placing events relative to this one steady pulse.
So before any complexity, let's pin the idea down precisely: how fast is the pulse, and how do we cut the space between pulses into the grid a drum machine plays on?
Tempo and BPM (seconds per beat), the beat grid and its subdivisions, note durations, swing as a timing offset, polyrhythm as a least-common-multiple, and Euclidean rhythms that spread hits evenly — all built on a real Web Audio lookahead scheduler, ending in a step sequencer you actually program and hear loop.
How fast is "fast"? Count the seconds.
Musicians measure tempo in beats per minute (BPM) — literally how many pulses land in sixty seconds. A slow ballad sits around 70 BPM; house music lives near 120–128; drum-and-bass races past 170. BPM is just frequency, but counted in beats per minute instead of cycles per second, because a beat at 2 Hz is easier to say as "120 BPM" than "2 hertz."
The number we actually build everything from is the flip side: how many seconds is one beat? Sixty seconds, shared among the beats:
Let's make it concrete at the most common dance tempo, 120 BPM, and walk the whole chain of durations by hand:
60 / 120 = 0.5 s. Two beats every second. That's the foot-tap.4 × 0.5 = 2.0 s. A standard "1, 2, 3, 4" loop is two seconds long.0.5 / 2 = 0.25 s. The "&" you say between counts.0.5 / 4 = 0.125 s. This is the cell width on a 16-step drum machine.That last number is the one to remember: at 120 BPM, a 16th note is 0.125 seconds — 125 milliseconds. A 16-step bar laid out as 16th notes is exactly 16 × 0.125 = 2.0 s, which checks out against the one-bar number above. The grid is the bar, sliced into sixteen equal slots.
Here is that slicing drawn out — one bar, four beats, sixteen 16th-note cells. The numbered cells are the beats you'd tap; the rest are the in-between grid lines a sequencer snaps notes to:
Sixteen equal slots = one bar at 0.125 s each (120 BPM). The orange cells are the four beats; everything else is a subdivision of a beat.
Mathematician: tempo is a frequency, f_beat = BPM/60 Hz, and a period T = 60/BPM seconds — the same reciprocal you met for pitch, just a thousand times slower. Musician: it's the tempo marking and the foot-tap; the grid is the bar lines and beat lines you read off the page. Artist: it's an even lattice of moments — a ruler laid across time — and every rhythm you'll make is a pattern of which lattice points light up.
const bpm = 120; const secPerBeat = 60 / bpm; // 0.5 s — one quarter note const sixteenth = secPerBeat / 4; // 0.125 s — one grid cell const barLength = secPerBeat * 4; // 2.0 s — one bar of 4/4 // the start time of grid cell i, counting from the bar's downbeat: const cellTime = i => i * sixteenth; // cell 4 = 0.5 s = beat 2
Cut the beat in half. Then half again.
A beat is not the smallest thing — it's a container you can divide. Cut a quarter note in two and you get eighth notes; cut those in two and you get sixteenths. Every note duration in common music is just a beat scaled by a power of two:
The 4/d factor is "how many of these fit in one beat, inverted." A quarter note (d=4) gives 4/4 = 1 beat; an eighth (d=8) gives 4/8 = ½ beat; a sixteenth (d=16) gives ¼ beat. Work the full table at 120 BPM:
| Note | Beats | Calculation (120 BPM) | Duration | Per bar |
|---|---|---|---|---|
| Whole | 4 | 0.5 × 4 | 2.000 s | 1 |
| Half | 2 | 0.5 × 2 | 1.000 s | 2 |
| Quarter (the beat) | 1 | 0.5 × 1 | 0.500 s | 4 |
| Eighth | ½ | 0.5 × 0.5 | 0.250 s | 8 |
| Sixteenth (grid cell) | ¼ | 0.5 × 0.25 | 0.125 s | 16 |
Drummers count these out loud, and the words are a map of the grid. One beat split into four 16ths is spoken "1 e & a": the "1" is the beat, the "&" is the eighth-note halfway point, and "e" and "a" are the two sixteenths between. So the sixteen cells of a bar are: 1 e & a · 2 e & a · 3 e & a · 4 e & a. That's the ruler in the sequencer you'll play in a moment.
One more subdivision worth knowing, because it doesn't fit the power-of-two ladder: the triplet, which crams three evenly into the space of two. An eighth-note triplet packs three notes into one beat:
0.5 s. An eighth-note triplet divides it into 3 → 0.5 / 3 ≈ 0.1667 s per note.0.25 s): the triplet note is shorter, so three triplets and two straight eighths both fill the same beat — three against two. Hold that thought; it returns as polyrhythm.// d = denominator of the note value (4, 8, 16…); triplet ⇒ multiply by 2/3 const noteDur = (bpm, d, triplet = false) => { const base = (60 / bpm) * (4 / d); return triplet ? base * 2 / 3 : base; }; noteDur(120, 16); // 0.125 — a 16th noteDur(120, 8, true); // 0.1667 — an 8th triplet
import numpy as np fs, bpm = 44100, 120 sixteenth = (60 / bpm) / 4 # 0.125 s samples_per_cell = round(sixteenth * fs) # 5512 samples per grid cell # a one-bar (16-cell) click track: a tick wherever the pattern is 1 pattern = [1,0,0,0, 1,0,0,0, 1,0,0,0, 1,0,0,0] # 4-on-the-floor bar = np.zeros(samples_per_cell * len(pattern)) click = np.exp(-np.arange(600) / 120) * np.sin(2*np.pi*1000*np.arange(600)/fs) for i, hit in enumerate(pattern): if hit: s = i * samples_per_cell bar[s:s+len(click)] += click # drop a percussive click at this cell
Nudge the off-beats late. Now it grooves.
A perfectly even grid sounds mechanical — a machine, not a musician. The single most important trick for making a rhythm feel human is swing: delay every off-beat subdivision slightly, so the pairs go "long–short, long–short" instead of "even–even."
Picture a beat split into two eighth notes — an on-beat (the "1") and an off-beat (the "&"). Straight, each gets half the beat. With swing, the on-beat steals time and the off-beat arrives late. We measure swing as the fraction of a subdivision the off-beat is pushed back:
Work it at 120 BPM with a moderate s = 0.5 on a 16th-note grid (cell = 0.125 s):
0.5 × 0.125 = 0.0625 s — about 62 milliseconds late.0.125 s instead plays at 0.125 + 0.0625 = 0.1875 s.0.25 s total — only the split inside it changed, from 50/50 to a long-short shuffle.That asymmetry — long then short, repeating — is the entire feel of a shuffle, a boom-bap drum loop, a swung hi-hat. At s = 0.66 you reach the classic triplet swing (the off-beat lands on the last third of the beat). The sequencer below has a swing slider; turn it up and the hats start to lope.
Mathematician: swing is a time-warp of the grid — a per-subdivision phase offset, alternating sign on even/odd cells, that preserves the bar length. Musician: it's groove, shuffle, the difference between a robot and a drummer who breathes. Artist: it's the lattice gone slightly elastic — the even moments stretched into a galloping long-short pulse you feel in your body before you can name it.
Three against two. When do they meet?
So far one grid has ruled everything. A polyrhythm runs two different grids at once: one part divides the bar into 3 even hits while another divides the same span into 2. Neither is "off" — they're two honest pulses sharing one stretch of time, and the tension between them is the groove.
The key question is: when do the two patterns line up again? They both started together on the downbeat — when does that coincidence return? Whenever both grids hit a common moment, which is the least common multiple of their step sizes. The cleanest way to see it: lay both rhythms on one fine grid that each divides evenly.
For 3-against-2 over one beat, the finest grid that holds both is LCM(2, 3) = 6 sub-slots. The "2" part hits every 6/2 = 3 slots; the "3" part hits every 6/3 = 2 slots. Work out exactly where each lands, at 120 BPM where one beat = 0.5 s and one of the 6 sub-slots = 0.5/6 ≈ 0.0833 s:
0 and 3 → times 0.000 s and 0.250 s (the beat, then halfway).0, 2, 4 → times 0.000 s, 0.167 s, 0.333 s (even thirds).The same logic scales: 4-against-3 needs LCM(3,4)=12 slots before it resolves, so it feels more restless and takes longer to "come home." Polyrhythm is arithmetic you can dance to.
const gcd = (a, b) => b ? gcd(b, a % b) : a; const lcm = (a, b) => a * b / gcd(a, b); function polyHits(a, b, beatSec) { // a-against-b over one beat const slots = lcm(a, b); // 3-against-2 → 6 const dt = beatSec / slots; // seconds per sub-slot const A = [], B = []; for (let i = 0; i < a; i++) A.push(i * (slots / a) * dt); // the "3" for (let j = 0; j < b; j++) B.push(j * (slots / b) * dt); // the "2" return { A, B }; // hit times in seconds; they share only t=0 }
Spread k hits as evenly as possible over n steps.
Here is a small miracle. Ask a simple question — "how do I place 3 hits as evenly as I can across 8 steps?" — and the answer turns out to be a huge fraction of the world's most famous rhythms. The method is the same one Euclid used 2,300 years ago to find a greatest common divisor. Godfried Toussaint noticed it generates rhythms, and named them Euclidean rhythms, written E(k, n): k onsets spread maximally evenly over n pulses.
The intuition: if 8 doesn't divide evenly by 3, you can't space the hits at a constant integer gap. Euclid's trick is to make the gaps as equal as integers allow — some 3-apart, some 2-apart, distributed as smoothly as possible. Let's grind out E(3, 8) by the bucket method: walk 8 steps, add 3 to a counter each step, and place a hit whenever it rolls past 8:
0 → 3. Below 8, no hit. .i when floor(i·k/n) increases. Take k=3, n=8: the values of floor(i·3/8) for i=0…7 are 0,0,0,1,1,1,2,2.E(3,8), the tresillo — the heartbeat of Cuban son, Brazilian, and half of pop music.That one pattern, x..x..x., is everywhere. And the family is vast: take E(2,5) and be honest about computing it — floor(i·2/5) for i=0…4 is 0,0,0,1,1, stepping up at i=0 and i=3, giving x..x., a Greek/Balkan 5-beat. E(5,8) is a syncopated bell pattern; E(4,16) is plain four-on-the-floor. Change two numbers, get a different culture's groove.
| E(k, n) | Onset steps | Pattern | Known as |
|---|---|---|---|
| E(3, 8) | 0, 3, 6 | x..x..x. | tresillo / Cuban son |
| E(2, 5) | 0, 3 | x..x. | Greek / Balkan 5 |
| E(5, 8) | 0, 2, 4, 5, 7 | x.x.xx.x | syncopated bell |
| E(4, 16) | 0, 4, 8, 12 | x...x...x...x... | four-on-the-floor |
Mathematician: E(k,n) is the digitized line of slope k/n — Bresenham's line algorithm, the same one that draws a diagonal on a pixel grid — and it falls out of Euclid's GCD recursion. Musician: it's the clave, the tresillo, the bell pattern — the rhythmic DNA of dozens of traditions. Artist: it's perfect rotational symmetry attempted on an integer ring; the hits want to be evenly spaced like points on a circle, and the pattern is the closest the grid will allow.
function euclid(k, n) { const pat = []; let bucket = 0; for (let i = 0; i < n; i++) { bucket += k; if (bucket >= n) { bucket -= n; pat.push(1); } // a hit rolls out else pat.push(0); } return pat; // euclid(3,8) → [1,0,0,1,0,0,1,0] → x..x..x. }
import numpy as np def euclid(k, n): # a hit wherever floor(i*k/n) increments — the digitized line of slope k/n idx = (np.arange(n) * k) // n hits = np.zeros(n, int) hits[0] = 1 hits[1:] = (np.diff(idx) > 0).astype(int) return hits euclid(3, 8) # array([1, 0, 0, 1, 0, 0, 1, 0]) → x..x..x.
How a drum machine keeps perfect time.
You now know where every hit goes in seconds. The last engineering question is brutal: how do you fire those hits on time? The naive idea — a setTimeout for each note — fails badly. JavaScript timers drift by tens of milliseconds, the main thread stutters during layout, and at 0.125 s per cell those errors are audible as sloppy, lurching timing.
The professional solution is the lookahead scheduler. The audio clock — ctx.currentTime — is rock-solid and sample-accurate, but you can't poll it continuously. So you split the job: a coarse, jittery JS timer wakes up often (every ~25 ms) and asks "what hits fall in the next little window?", then schedules those at precise audio-clock times. The jitter never reaches your ears, because every note is pinned to ctx.currentTime + offset, not to when the timer happened to fire.
Each percussive hit is itself a tiny synth voice — exactly the envelopes from the synth lesson, but very short: a fast attack and a quick exponential decay on an oscillator or a noise burst. A kick is a sine sliding 150→50 Hz; a hat is a 40 ms high-passed noise tick. No samples — the same "the math is the sound" engine, now making drums.
const lookahead = 0.12; // schedule up to 120 ms into the future let nextTime = ctx.currentTime, step = 0; function scheduler() { // fire every hit whose time falls inside the lookahead window while (nextTime < ctx.currentTime + lookahead) { scheduleStep(step, nextTime); // place this cell's drums at an exact time nextTime += (60 / bpm) / 4; // advance one 16th note step = (step + 1) % 16; } } setInterval(scheduler, 25); // coarse timer — only decides WHAT, not WHEN function click(when) { // a percussive voice scheduled at an exact time const o = ctx.createOscillator(), g = ctx.createGain(); o.frequency.setValueAtTime(1000, when); g.gain.setValueAtTime(0.6, when); g.gain.exponentialRampToValueAtTime(0.0001, when + 0.05); // 50 ms decay o.connect(g); g.connect(ctx.destination); o.start(when); o.stop(when + 0.06); }
The sequencer below uses exactly this loop. When it schedules cell i for time t, it remembers the pair (i, t). Every animation frame it asks the audio clock "has t passed yet?" and lights the column only then — so the moving playhead you see is locked to the sound you hear, not to the wall clock. Sight and sound are the same event.
Now program it. Hear it loop.
Everything converges here. Tap cells to place kick, snare, and hat on the 16-step grid — that's the beat grid of Chapter 1. Press play and the lookahead scheduler of Chapter 6 fires each cell at its exact audio-clock time, while the playhead column rides along, synced to the sound. Push BPM to feel 60/BPM change the spacing; push swing to nudge the off-beats late (Chapter 3); press Euclidean fill to spread hits evenly with E(k,16) (Chapter 5). It starts on a four-on-the-floor so you hear a beat the instant you press play.
Music: you built a drum loop by hand and it grooves. Math: every cell's time is i · (60/BPM)/4, swung off-beats are +s·cell, the Euclidean fill is the slope-k/n line — pure arithmetic, made audible. Art: the lit lattice is the rhythm; the playhead is time itself sweeping the grid. And it composes: drive a pitched synth voice from this same clock — one note per active cell — and the drum grid becomes a bassline, the rhythm and the pitch fused into a track. That is a song's skeleton.
You've met both halves now. The synth lesson gave you what a sound is — a pitch, a timbre, an envelope. This one gave you when — the grid, the groove, the scheduler that fires it. Put a melody on this clock and you have arrangement; layer several and you have a track. Everything past here — basslines, chord progressions, song structure — is patterns of what placed on patterns of when.
Back to Reverbs →A rhythm is nothing but a list of when — and a clock honest enough to keep it.
Xavier Serra et al., Audio Signal Processing for Music Applications (Stanford / UPF) — sampling, time, and rhythm on the signal grid. · Ableton, Learning Music — beats, tempo, and the step-sequencer grammar. · Godfried Toussaint, "The Euclidean Algorithm Generates Traditional Musical Rhythms" (2005). · Chris Wilson, "A Tale of Two Clocks" — the Web Audio lookahead scheduler. · MDN, Web Audio API.