Chapter 4 · Your First Generators: Euclidean and Spread Rhythms¶
In Chapter 3 you placed every note by hand; here you hand the rhythm-making over to generators — single numbers that bloom into whole patterns — and learn to layer, ratchet, swing, and chain them.
So far a rhythm has been a list you typed out: [0, 4, 8, 12] for the kick,
range(16) for the hats. That’s perfect when you know exactly what you want. But
often you don’t want exact steps — you want a feel: “spread five hits evenly
across the bar,” “make the hats busier as the section builds.” A generator lets
you say that with one number. You give it a count and it works out the steps,
distributing them as evenly as the maths allows. The same idea — describe the
density, let Subsequence fill the grid — runs through this whole chapter.
4.1 Euclidean rhythms from one number¶
The Euclidean rhythm is the workhorse generator. You give it a number of pulses — how many hits to place — and it spreads them as evenly as possible across the bar’s grid. That single idea sits under a startling share of the world’s drum patterns: spread a handful of hits as evenly as a grid allows and you keep landing on rhythms that traditions everywhere already use — West African bell lines, the Cuban tresillo, the four-on-the-floor kick. They are all “N hits, spread evenly.”
See also
Origins. Euclid wrote this algorithm down around 300 BC; in 2003 it was repurposed to evenly space the pulses of a neutron accelerator, and in 2005 Godfried Toussaint showed the same even-spacing patterns underlie traditional rhythms the world over. The full story — and the pedigree of every other generator — is in Appendix F.
The verb is p.euclidean:
p.euclidean(pitch, pulses, velocity=..., duration=...)
pitch— the drum (a name like"kick_1") or a MIDI number, exactly as inhit_steps.pulses— how many hits to place across the bar. This is the one number that does the work.velocity— how hard, the same as everywhere: a single number, or a(low, high)tuple for a per-hit random draw (§1.5).duration— how long each hit rings, in beats (default a tight0.1).
The hits land on the pattern’s grid — sixteen sixteenth-note steps in a 4-beat bar,
just like hit_steps. So pulses=4 puts a hit every four steps, which is exactly
the four-on-the-floor kick you’ve been writing by hand:
import subsequence
import subsequence.constants.instruments.gm_drums as gm_drums
composition = subsequence.Composition(bpm=120)
@composition.pattern(channel=10, beats=4, drum_note_map=gm_drums.GM_DRUM_MAP)
def drums(p):
p.euclidean("kick_1", pulses=4, velocity=100) # = [0, 4, 8, 12] — four on the floor
composition.render(bars=4, filename="euclid.mid")
euclidean("kick_1", pulses=4) produces hits on steps 0, 4, 8, 12 — the very
list you typed in Chapter 1, now derived from a single 4. The pay-off is that
changing the feel is now changing one number. Ask for five pulses instead of
four and Subsequence can’t space them perfectly evenly (five doesn’t divide
sixteen), so it does the next-best thing — the famous slightly-lopsided spread that
underlies a thousand grooves:
@composition.pattern(channel=10, beats=4, drum_note_map=gm_drums.GM_DRUM_MAP)
def drums(p):
p.euclidean("kick_1", pulses=5, velocity=100) # lands on steps 0, 3, 6, 9, 12
composition.render(bars=4, filename="euclid.mid")
A few pulse counts worth knowing by ear, on the standard 16-step bar:
|
Lands on steps |
Sounds like |
|---|---|---|
|
|
three even-ish hits straddling the bar — a loping 3-against-16 cross-rhythm |
|
|
four on the floor |
|
|
five driving hits, the lopsided spread under countless grooves |
|
|
a busy, rolling line good for shakers and hats |
Note
Why “Euclidean”? The spread is computed by the same algorithm Euclid used to
find a greatest common divisor — the most even way to interleave N hits among M
slots. You don’t need the maths to use it; the musical rule of thumb is all you
need: pulses is how many, and the more evenly they can’t divide the bar, the
more interesting the syncopation. Even divisors (2, 4, 8) give plain rhythms; odd
counts (3, 5, 7) give the syncopated ones. The exact spread also depends on the
grid euclidean works over — here the pattern’s sixteen steps — so pulses=3 is
the 3-against-16 spread [0, 5, 10], not the eight-step tresillo [0, 3, 6].
Because it places notes the same way hit_steps does, everything you already know
still applies — humanise with a velocity tuple, stack several euclidean calls to
layer voices, and reach for plain hit_steps wherever a part needs an exact beat
the even spread wouldn’t choose. Here a Euclidean kick drives a busy, humanised
Euclidean hat, while the snare keeps the backbeat by hand — Euclidean’s even
two-pulse spread would land on beats 1 and 3, not the 2 and 4 a backbeat wants:
@composition.pattern(channel=10, beats=4, drum_note_map=gm_drums.GM_DRUM_MAP)
def drums(p):
p.euclidean("kick_1", pulses=4, velocity=105) # steady floor
p.hit_steps("snare_1", [4, 12], velocity=95) # backbeat, placed by hand
p.euclidean("hi_hat_closed", pulses=11, velocity=(55, 80)) # busy, humanised
composition.render(bars=4, filename="euclid.mid")
Tip
The most musical move with Euclidean rhythms is to make the pulse count react to
where you are in the song, using p.cycle or p.bar_cycle from
Chapter 2. pulses = 4 + p.bar_cycle(4).last * 4 plays four hats
for three bars and eight on the turnaround — a build-up written in one line.
Rotating the rhythm: the necklace move. Picture a Euclidean rhythm as beads on a
necklace — the same pattern can begin at a different bead. To rotate one, reach past
the builder to its raw on/off list:
subsequence.sequence_utils.generate_euclidean_sequence(steps, pulses) returns the
1/0 mask, and displace(mask, amount) phase-shifts it, wrapping (positive =
later, negative = earlier):
>>> import subsequence.sequence_utils as utils
>>> kick = utils.generate_euclidean_sequence(8, 3) # 3 pulses over 8 steps
>>> kick
[1, 0, 0, 1, 0, 0, 1, 0]
>>> utils.displace(kick, 1) # the whole necklace, one step later
[0, 1, 0, 0, 1, 0, 0, 1]
Rotating a Euclidean necklace against the downbeat is how its clave-like variants
appear, and phasing two copies a step apart is the classic Steve-Reich move.
displace only reorders the values you give it — distinct from p.rotate
(§12.9), which shifts notes already placed on the grid.
Reference
4.2 Bresenham and weighted spread (bresenham_poly)¶
Euclidean is not the only way to spread N hits evenly. p.bresenham is a close
cousin — same call shape, same idea of distributing pulses across the grid — but
it uses the Bresenham line algorithm (the one screens use to draw a straight
diagonal as stair-steps), which gives a subtly different, often end-weighted
distribution. Where Euclidean tends to put a pulse on the downbeat, Bresenham tends
to push the run toward the end of the bar:
@composition.pattern(channel=10, beats=4, drum_note_map=gm_drums.GM_DRUM_MAP)
def drums(p):
p.bresenham("hi_hat_closed", pulses=5, velocity=(55, 80)) # lands on 3, 6, 9, 12, 15
composition.render(bars=4, filename="bres.mid")
Five Bresenham pulses land on steps 3, 6, 9, 12, 15 — the same even spacing as the
Euclidean five, but shifted so the line ends on the last sixteenth instead of
starting on the downbeat. That trailing-edge feel makes Bresenham a natural choice
for a part that should lead into the next bar — a hat or shaker that pushes the
groove forward — while Euclidean, anchored on beat one, suits the parts that hold it
down.
Generator |
Lands on steps |
Feel |
|---|---|---|
|
|
anchored on the downbeat — holds the groove |
|
|
leans on the last step — pushes into the next bar |
Weighted spread with bresenham_poly¶
The real power tool here is p.bresenham_poly. Instead of one voice and a pulse
count, you hand it several voices at once, each with a density weight, and it
shares the grid out between them — assigning every step to exactly one voice, so
the voices never collide. The result is an interlocking texture, the kind of
woven hand-percussion groove that’s fiddly to place by hand:
p.bresenham_poly(parts={pitch: weight, ...}, velocity=...)
parts— a dictionary mapping each drum to a density weight. Higher weight means more hits. A weight of0.5aims for a hit roughly every other step;0.25, every fourth.velocity— either one number for all voices, or a dictionary giving each voice its own velocity (a voice missing from the dict falls back to the default).
@composition.pattern(channel=10, beats=4, drum_note_map=gm_drums.GM_DRUM_MAP)
def drums(p):
p.bresenham_poly(
parts={"hi_hat_closed": 0.5, "shaker": 0.25, "tambourine": 0.125},
velocity={"hi_hat_closed": 70, "shaker": 55, "tambourine": 45},
)
composition.render(bars=4, filename="poly.mid")
The three voices share the sixteen steps with no overlaps: the hi-hat takes about half, the shaker a quarter, the tambourine an eighth — a single, dovetailed groove out of one call. If the weights add up to less than 1.0, the leftover steps become rests (silence), so you can leave the texture sparse on purpose.
Important
bresenham_poly is for the background weave, not the anchors. Because it
re-shares every step whenever the weights change, a voice’s hits shift position
from bar to bar if you ramp its density — wonderful for evolving hats and shakers,
distracting for a signature clap or cowbell. The reliable recipe: lay the
interlocking texture with bresenham_poly, then place the parts that must stay put
— kick, snare — with plain hit_steps or a fixed-pulse euclidean on top.
@composition.pattern(channel=10, beats=4, drum_note_map=gm_drums.GM_DRUM_MAP)
def drums(p):
# Evolving background weave — these voices may shift bar to bar.
p.bresenham_poly(
parts={"hi_hat_closed": 0.5, "shaker": 0.2},
velocity={"hi_hat_closed": 65, "shaker": 50},
)
# Fixed anchors on top — placed by hand so they never move.
p.hit_steps("kick_1", [0, 8], velocity=110)
p.hit_steps("snare_1", [4, 12], velocity=100)
composition.render(bars=4, filename="poly.mid")
Reference
4.3 Ratchets and rolls¶
A ratchet (or roll, or drum buzz) is the move where a single hit becomes
a rapid burst — tk-tk instead of tk. In Subsequence it’s a transform: you
place your notes first, then call p.ratchet to subdivide them into bursts.
Each note is replaced by subdivisions evenly-spaced sub-hits inside its own time
slot.
p.ratchet(subdivisions, pitch=..., velocity_start=..., velocity_end=...)
subdivisions— how many sub-hits each note becomes (default2).pitch— restrict the ratchet to one drum; leave it off and every note is ratcheted (handy on a melodic line, dangerous on a full kit).velocity_start/velocity_end— multipliers on the original velocity across the burst, so you can shape a crescendo or a decay.
Because it works on notes already placed, the order is: generate the rhythm, then ratchet. Here a Euclidean hat line gets every hit turned into a triplet flutter — note the chaining, which the next section unpacks:
@composition.pattern(channel=10, beats=4, drum_note_map=gm_drums.GM_DRUM_MAP)
def drums(p):
p.euclidean("hi_hat_closed", pulses=8, velocity=70).ratchet(3, pitch="hi_hat_closed")
composition.render(bars=4, filename="ratchet.mid")
The classic use is a crescendo snare roll into a turnaround. Place one snare hit
near the end of a four-bar phrase, then ratchet it into four rising sub-hits so it
swells into the downbeat of the next phrase. velocity_start=0.3, velocity_end=1.0
makes it grow; the shape="ease_in" curve makes the growth accelerate:
@composition.pattern(channel=10, beats=4, drum_note_map=gm_drums.GM_DRUM_MAP)
def drums(p):
p.euclidean("kick_1", pulses=4, velocity=100)
p.hit_steps("snare_1", [4, 12], velocity=95) # backbeat, by hand
p.hit_steps("hi_hat_closed", range(16), velocity=(55, 75))
# On the last bar of every four, swell a roll into the next phrase.
if p.bar_cycle(4).last:
p.hit_steps("snare_1", [14], velocity=100, duration=0.5)
p.ratchet(4, pitch="snare_1", velocity_start=0.3, velocity_end=1.0, shape="ease_in")
composition.render(bars=8, filename="roll.mid")
Warning
Ratchet only what you mean to. Called bare — p.ratchet(2) — it subdivides
every note in the pattern, kick and snare and hats alike, which usually turns a
groove to mush. On a drum kit, almost always pass pitch= to target one voice. On
a single-voice melodic or bass pattern, the bare form is exactly right. And place
ratchet after your notes but before swing (next), so the swing still nudges
the parent hit.
Reference
4.4 Swing and timing feel (not pitch)¶
Everything so far has placed notes on the exact grid. Swing is what takes them off it — by a hair — to give a stiff pattern the pushed, human shuffle of a real drummer. It’s the first timing transform we meet, and the distinction it draws is one to hold onto for the rest of the guide:
Important
Swing is timing, not pitch. It nudges when notes play — sliding every other
grid position a little late — and never touches which notes play. This is the
opposite end of the instrument from snap_to_scale (Chapter 5), which changes a
note’s pitch and never its timing. Two different dials: swing for feel,
snap_to_scale for notes. Keep them straight and you’ll never reach for the wrong
one.
The verb is p.swing:
p.swing(percent, grid=0.25)
percent— the swing amount.50is dead straight (no swing);57is a gentle shuffle (the Ableton default);67is hard triplet swing. The useful range is roughly 50–75.grid— which notes get pushed, as a beat value.0.25swings sixteenth notes (the common choice);0.5swings eighths.
Like the articulation verbs in Chapter 3, swing is a post-placement transform: write the line straight, then swing the whole thing. Here a plain sixteenth-note hat line gets a moderate shuffle:
@composition.pattern(channel=10, beats=4, drum_note_map=gm_drums.GM_DRUM_MAP)
def drums(p):
p.euclidean("kick_1", pulses=4, velocity=100)
p.hit_steps("snare_1", [4, 12], velocity=95) # backbeat, by hand
p.hit_steps("hi_hat_closed", range(16), velocity=(55, 80))
p.swing(57) # gentle 16th-note shuffle over the whole bar
composition.render(bars=4, filename="swing.mid")
Render that straight (delete the p.swing line) and then with it, and the
difference is unmistakable: the off-grid hats lean back, and the loop stops sounding
quantised. Two refinements you’ll reach for:
@composition.pattern(channel=10, beats=4, drum_note_map=gm_drums.GM_DRUM_MAP)
def drums(p):
p.hit_steps("hi_hat_closed", range(16), velocity=70)
p.swing(67, grid=0.25) # hard, triplet-ish shuffle on the sixteenths
composition.render(bars=2, filename="swing.mid")
Tip
Swing is a whole-pattern feel, so apply it once, last, after every note is placed
— including after ratchet, so the rolls ride along with the swing. If only some
patterns should swing (say the hats but not the kick), put p.swing in each pattern
you want to feel it, and leave it out of the ones that should stay locked to the
grid.
Reference
4.5 “Learn one verb, predict the rest”; builder chaining¶
You’ve now met a dozen p. verbs across four chapters, and they all share a shape
on purpose. Once you’ve internalised it, a method you’ve never used before behaves
the way you’d guess — which is the whole design goal.
The placement and transform verbs return the builder. Look back at any of the
signatures: p.euclidean(...) -> PatternBuilder, p.ratchet(...) -> PatternBuilder, p.swing(...) -> PatternBuilder. Each one does its job and hands
p back to you. That means you can chain them — write one verb after another on
the same line, left to right, in the order they apply:
@composition.pattern(channel=10, beats=4, drum_note_map=gm_drums.GM_DRUM_MAP)
def drums(p):
# Generate, then ratchet, then swing — read left to right as a sentence.
p.euclidean("hi_hat_closed", pulses=8, velocity=70).ratchet(2, pitch="hi_hat_closed").swing(57)
composition.render(bars=4, filename="chain.mid")
That one line reads as a recipe: place eight even hats, turn each into a double, then shuffle the lot. Chaining is purely a convenience — it does exactly the same thing as writing the three calls on separate lines, and you should break the chain across lines the moment it stops being readable:
@composition.pattern(channel=10, beats=4, drum_note_map=gm_drums.GM_DRUM_MAP)
def drums(p):
p.euclidean("hi_hat_closed", pulses=8, velocity=70)
p.ratchet(2, pitch="hi_hat_closed")
p.swing(57)
composition.render(bars=4, filename="chain.mid")
The deeper point is the shared shape of the verbs. Once you know it, you can predict an unfamiliar one:
```{list-table} The common shape of a p. verb
- header-rows:
1
- widths:
30 70
Every verb…
…so when you meet a new one
leads with its most musical argument (a placement verb’s
pitch, a transform’s amount)you can guess the argument order before reading the docs
accepts
velocity=as a number or a(low, high)tupleper-note humanising works the same everywhere it’s offered
places notes (
euclidean,hit_steps) or transforms placed notes (ratchet,swing,legato)you know whether to call it first (placement) or last (transform)
returns the builder, so it chains
you can string several together, or not — your call
```{note}
**Placement first, transforms last.** This is the one ordering rule that makes the
chain read correctly. *Placement* verbs (`euclidean`, `bresenham`, `hit_steps`,
`note`, `sequence`) put notes down; *transform* verbs (`ratchet`, `swing`,
`legato`, `detached`, `velocity_shape`) reshape whatever is already there. Put the
placements first and the transforms after, and a chain does what it reads like it
does.
See also
This “predict the rest” idea is the tip of a larger contract the whole API keeps —
the front the chord verbs share, what (low, high) and seed= always mean, which
methods chain and which return data. It’s gathered in one place in
Appendix G: Learn One Verb, Predict the Rest.
4.6 Repeatability (seed= / rng=)¶
The moment a pattern uses randomness — a (low, high) velocity tuple, a probability
that thins hits, a generator that makes a random choice — you face a question:
should it sound the same every time you run it, or fresh every time? Both are
useful, and Subsequence lets you choose, at three levels of scope.
A generator can make random choices in two distinct places, and it pays to know
which is which. The first is the velocity tuple: velocity=(55, 80) is one
independent random draw per note (§1.5) — sixteen hats give sixteen
different velocities. The second is anything the generator itself rolls — most
commonly probability, which thins a rhythm by giving each pulse a chance of
not playing (probability=0.5 drops about half). A 16-pulse Euclidean line at
probability=0.7 keeps a random-but-even ~70% of its hits, fresh each bar.
The whole-piece seed. Pass seed= when you build the Composition and the
entire piece becomes reproducible — every random draw in every pattern (velocity
tuples and generator choices alike) unfolds the same way each run, so the take you
liked is the take you get back:
import subsequence
import subsequence.constants.instruments.gm_drums as gm_drums
composition = subsequence.Composition(bpm=120, seed=42) # whole piece is now repeatable
@composition.pattern(channel=10, beats=4, drum_note_map=gm_drums.GM_DRUM_MAP)
def drums(p):
p.euclidean("kick_1", pulses=4, velocity=100)
p.hit_steps("hi_hat_closed", range(16), velocity=(55, 80)) # same 16 values every run
composition.render(bars=4, filename="seeded.mid")
The per-generator seed. Sometimes you want one generator’s own choices pinned
while the rest of the piece stays free. Every generator takes its own seed= —
a plain integer that fixes that call’s random decisions, regardless of the
composition’s seed. Here the busy hat line is thinned to a fixed pattern of hits
every run, while the shaker beside it is left free to thin differently each time:
@composition.pattern(channel=10, beats=4, drum_note_map=gm_drums.GM_DRUM_MAP)
def drums(p):
# Thinned to the same hits every run — seed=7 pins this generator's choices.
p.euclidean("hi_hat_closed", pulses=16, velocity=70, probability=0.6, seed=7)
# …while this shaker is left free, thinning differently each run.
p.euclidean("shaker", pulses=16, velocity=55, probability=0.4)
composition.render(bars=4, filename="seeded.mid")
Note
A per-generator seed= pins the generator’s own dice — probability thinning
and the like — not the (low, high) velocity draw. Velocity tuples always draw
from the pattern’s shared generator, so to make those repeatable you seed the
Composition (above). In practice the whole-piece seed= is the one you’ll reach
for nine times out of ten; a per-generator seed= is for pinning one part’s
structure (which hits survive a thinning) while the rest stays loose.
The advanced form: rng=. For full control you can hand a generator its own
random.Random object via rng=. This is the power-user escape hatch — it lets
several calls share one stream, or lets you advance the stream yourself. You’ll
rarely need it as a beginner; just know the precedence when more than one is in
play:
Important
Precedence: rng= beats seed= beats the pattern’s own generator. If you pass
rng=, it wins (and Subsequence warns you if you passed seed= too — pass only
one). If you pass seed= alone, that call uses a fresh generator fixed to that
number. If you pass neither, the call draws from the pattern’s own generator, which
is reproducible whenever the composition was given a seed=. So: rng > seed >
composition seed. Reach for the composition seed= to pin a whole piece, a
per-generator seed= to pin one part, and rng= only when you genuinely need to
share or steer the stream.
Two more things worth knowing. Patterns can already vary by p.cycle
(Chapter 2), and that and the seed are independent: a seeded
piece still evolves bar to bar — it just evolves the same way every run. And the
same seed=/rng= controls appear on every generator in the library, not only
the ones in this chapter — another payoff of the shared verb shape from
§4.5.
Tip
The everyday workflow: compose with no seed at all, hitting re-run until a variation
delights you; then drop a seed= on the Composition to lock that variation in
place. We return to this — seeds, locks, and freezing a take — as a production
workflow in a later chapter; here you have the two knobs that matter, the whole-piece
seed and the per-generator one.
You can now grow a whole groove from a handful of density numbers — Euclidean and
Bresenham spreads, interlocking bresenham_poly voices, ratcheted rolls, and a
swing feel — chain the verbs fluently, and make any of it repeatable on demand. In
Chapter 5 we turn the same generative thinking on pitch:
working in scale degrees and keys, and snapping notes to a scale — the pitch-side
counterpart to the timing tools you just learned.