Microtiming
A pattern on the grid is precise, but precision isn’t always what you want. Real drummers push and pull against the beat — that’s where groove lives. Microtiming methods nudge individual events forward or backward in time without touching the notes themselves, the way a DAW’s groove templates or “humanize” button shift hits a few milliseconds off the grid.
Every amount here is step-relative: it’s a fraction of the event’s own step, not an absolute time. So the same value gives you proportionally the same feel whether the pattern has four steps or sixteen. And notes keep their original length — they move in time, they don’t stretch or shrink.
Here’s the payoff up front — a backbeat with a moderate swing:
use "std/instruments" { Sampler, Kit };
let drums = AudioTrack("drums");drums.load_instrument(Sampler(Kit("CR-78")));
drums << [bd sd bd sd].swing(0.25);
PLAY;The three methods below — .nudge(), .swing(), and .humanize() — cover
everything from hand-placed grooves to deterministic human jitter.
.nudge(offsets)
Section titled “.nudge(offsets)”.nudge() gives you per-step control: each offset is a fraction of that event’s
step (0.0 = on the grid, positive = later, negative = earlier). The offsets cycle
through the array, one per event.
[bd sd bd sd].nudge([0.0, 0.25, 0.0, -0.1]);| Offset | Meaning |
|---|---|
0.0 | No shift |
0.25 | 25% of step forward |
-0.1 | 10% of step backward |
0.5 | Half a step forward |
If the pattern has more events than offsets, the array wraps around:
// Two offsets across four events: [0, shift, 0, shift][bd sd bd sd].nudge([0.0, 0.1]);Timing diagram
Section titled “Timing diagram”Offsets: 0.0 +0.25 0.0 -0.1
Original: | bd | sd | bd | sd | 0 0.25 0.5 0.75 1.0
Nudged: | bd | sd | bd | sd | 0 0.3125 0.5 0.725 1.0 ^^ ^^ shifted +25% shifted -10% of step of stepEach event keeps its original duration after shifting, so notes may overlap slightly — which is exactly what you want for a natural feel.
Patterned offsets
Section titled “Patterned offsets”The offsets can come from a pattern, so the groove changes from cycle to cycle:
// One cycle nudged, the next cycle straight[bd sd bd sd].nudge(<[0 0.25 0 -0.1] [0 0 0 0]>);.swing(amount)
Section titled “.swing(amount)”.swing() is the classic shuffle: even-indexed events stay put, odd-indexed
events shift later by amount (a fraction of their step). One number, instant
groove.
[bd sd bd sd].swing(0.25); // moderate swing| Amount | Feel |
|---|---|
| 0.15 | Subtle shuffle |
| 0.25 | Moderate swing |
| 0.33 | Triplet feel (classic MPC) |
| 0.5 | Maximum / dotted feel |
Because the amount is step-relative, swing(0.33) lands the same triplet feel
whether your pattern has 4 steps or 16.
Timing diagram
Section titled “Timing diagram”Original: | bd | sd | bd | sd | 0 0.25 0.5 0.75 1.0
Moderate (0.25): | bd | sd | bd | sd | 0 0.3125 0.5 0.8125 1.0
Triplet (0.33): | bd | sd | bd | sd | 0 0.3325 0.5 0.8325 1.0Patterned amount
Section titled “Patterned amount”The amount can vary per cycle too:
// Moderate swing on one cycle, triplet on the next[bd sd bd sd].swing(<0.25 0.33>);.humanize(amount)
Section titled “.humanize(amount)”.humanize() scatters each event by a small random offset in the range
[-amount, +amount] of its step. Unlike a real humanize button, this one is
deterministic — the offset for each event comes from a hash of its start
time, so it’s different for every step but identical on every playback.
[bd sd bd sd].humanize(0.1); // +/- 10% of step jitterThat gives you the best of both worlds:
- Deterministic — the same pattern always jitters the same way. No drift between cycles, nothing to surprise you mid-set.
- Unique per event — each step gets its own offset, derived from its position.
- Bounded — offsets never exceed
[-amount, +amount]of the step. - Density-invariant — the same amount feels proportionally the same at any pattern density.
Timing diagram
Section titled “Timing diagram”Original: | bd | sd | bd | sd | 0 0.25 0.5 0.75 1.0
Humanized: | bd | sd | bd | sd |(amount=0.1) 0 0.24 0.51 0.53 0.74 1.0 ← → → ← Each event scattered independently within ±10% of step| Amount | Feel |
|---|---|
| 0.05 | Barely perceptible, just off-grid |
| 0.1 | Subtle human feel |
| 0.15 | Noticeable but musical |
| 0.25 | Loose, relaxed timing |
Patterned amount
Section titled “Patterned amount”// Tight on one cycle, loose on the next[bd sd bd sd].humanize(<0.05 0.2>);Combining methods
Section titled “Combining methods”These methods chain, and each one sees the already-shifted events from the previous one. The rule of thumb: set the groove skeleton first, add randomness last.
.nudge()or.swing()— lock in the groove structure.humanize()— sprinkle variation on top
// Recommended: swing first, then humanize[bd sd bd sd].swing(0.25).humanize(0.05);
// A custom groove with a little human feel[bd sd bd sd].nudge([0.0, 0.15, 0.0, -0.08]).humanize(0.05);Humanizing before swinging means the swing offsets land on already-jittered positions, which muddies the groove structure — so keep humanize at the end.
| Combination | Use case |
|---|---|
.swing(0.25) | Clean shuffle feel |
.humanize(0.1) | Subtle imperfection, no groove change |
.swing(0.25).humanize(0.05) | Shuffle with human feel |
.nudge([...]).humanize(0.05) | Custom groove with variation |
Edge cases
Section titled “Edge cases”- Boundary clamping: an event shifted outside the cycle
[0, 1)snaps to the cycle boundary rather than disappearing — pushed before the start, it lands at0.0; pushed past the end, it lands at the last valid position. No events are ever silently dropped. - Zero / empty passthrough:
.nudge([]),.swing(0.0), and.humanize(0.0)pass events through unchanged with no overhead. - Duration preserved: notes keep their full length after shifting; a shifted note may sustain past the cycle boundary.
- Overlaps: small overlaps from timing shifts are allowed and sound natural.
Layered groove
Section titled “Layered groove”Different layers can carry independent microtiming — straight kicks, swung-and- humanized hats, a nudged snare:
use "std/instruments" { Sampler, Kit };
let kit = Sampler(Kit("CR-78"));
let kicks = AudioTrack("kicks");kicks.load_instrument(kit);kicks << [bd*4].humanize(0.05);
let hats = AudioTrack("hats");hats.load_instrument(kit);hats << [hh*8].swing(0.2).humanize(0.05);
let snares = AudioTrack("snares");snares.load_instrument(kit);snares << [_ sd _ sd].nudge([0.0, 0.1]);
PLAY;See Also
Section titled “See Also”- Transforming Patterns — speed, pitch, and order
- Samplers — loading instruments and kits
- Effects — shaping the sound after the groove
- Signals & Automation — continuous modulation
Next Steps
Section titled “Next Steps”With timing under control, let patterns generate themselves.
- Generative Patterns — randomness, streams, and stateful sequences