Reverbs · Play track

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.

Scroll to begin
00 the pulse

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?

what this lesson covers

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.

01 tempo & the grid

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:

seconds per beat = 60 / BPM a minute has 60 seconds; divide them among the beats that fit in it

Let's make it concrete at the most common dance tempo, 120 BPM, and walk the whole chain of durations by hand:

One beat (a quarter note) at 120 BPM → 60 / 120 = 0.5 s. Two beats every second. That's the foot-tap.
One bar of 4 beats → 4 × 0.5 = 2.0 s. A standard "1, 2, 3, 4" loop is two seconds long.
One eighth note = half a beat → 0.5 / 2 = 0.25 s. The "&" you say between counts.
One sixteenth note = a quarter of a beat → 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:

16ths

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.

one object, three languages

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.

JavaScript · the grid as durations
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
02 subdivisions & durations

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:

duration = (60 / BPM) · (4 / d) d = the note value: 4 = quarter, 8 = eighth, 16 = sixteenth, 2 = half, 1 = whole

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:

NoteBeatsCalculation (120 BPM)DurationPer bar
Whole40.5 × 42.000 s1
Half20.5 × 21.000 s2
Quarter (the beat)10.5 × 10.500 s4
Eighth½0.5 × 0.50.250 s8
Sixteenth (grid cell)¼0.5 × 0.250.125 s16

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:

One beat at 120 BPM is 0.5 s. An eighth-note triplet divides it into 3 → 0.5 / 3 ≈ 0.1667 s per note.
Compare a straight eighth (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.
JavaScript · any note value to seconds
// 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
Python · NumPy · place hits on a sample grid
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
03 swing

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:

toff = tgrid + s · (60 / BPM) / 4 s = swing amount (0 = straight, ~0.66 = hard shuffle). The on-beat moves earlier by the same amount.

Work it at 120 BPM with a moderate s = 0.5 on a 16th-note grid (cell = 0.125 s):

The off-beat's nudge is 0.5 × 0.125 = 0.0625 s — about 62 milliseconds late.
So an off-beat 16th that should land at 0.125 s instead plays at 0.125 + 0.0625 = 0.1875 s.
To keep the bar in time, the preceding on-beat is pulled earlier by the same 0.0625 s. The pair still spans 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.

one object, three languages

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.

04 polyrhythm

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:

The "2" pulse hits slots 0 and 3 → times 0.000 s and 0.250 s (the beat, then halfway).
The "3" pulse hits slots 0, 2, 4 → times 0.000 s, 0.167 s, 0.333 s (even thirds).
They coincide only at slot 0 — the downbeat — then drift apart and don't meet again until the next beat. That lone shared hit, surrounded by near-misses, is the 3-against-2 feel: "pass-the-god-damn-but-ter," as drummers say it.
period of the combined pattern = LCM(a, b) · (one sub-slot) two pulses a and b realign every LCM(a,b) sub-slots; for 3-against-2 that's the whole beat

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.

JavaScript · two pulses on one LCM grid
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
}
05 euclidean rhythms

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:

Step 0: counter 0 → 3. Below 8, no hit. .
Wait — we place a hit when the counter first reaches or exceeds a multiple boundary. Cleaner to track it as: hit at step 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.
A hit lands wherever that number steps up: at i=0 (start), i=3 (0→1), i=6 (1→2). So the onsets are steps 0, 3, 6.
Write it out: x..x..x. — three hits, gaps of 3, 3, 2. That's 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 stepsPatternKnown as
E(3, 8)0, 3, 6x..x..x.tresillo / Cuban son
E(2, 5)0, 3x..x.Greek / Balkan 5
E(5, 8)0, 2, 4, 5, 7x.x.xx.xsyncopated bell
E(4, 16)0, 4, 8, 12x...x...x...x...four-on-the-floor
one object, three languages

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.

JavaScript · Euclidean rhythm (the bucket method)
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.
}
Python · NumPy
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.
06 the scheduler

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.

while ( nextNoteTime < ctx.currentTime + lookahead ) → schedule it, advance the JS timer only decides WHAT to schedule; the audio clock decides exactly WHEN it sounds

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.

JavaScript · Web Audio · the lookahead scheduler
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);
}
why the visual playhead can be trusted

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.

make a beat

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.

110 BPMswing 0%
slowfast
straightshuffle

Tap a cell to toggle a drum (it auditions as you place it). The shaded blocks are the four beats; "1 e & a" across the top is the 16th-note ruler. Drag BPM and swing while it loops.

this is the merge — rhythm meets the synth

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.

Sources go deeper

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.