Microtiming
This content is for v0.7. Switch to the latest version for up-to-date documentation.
Microtiming combinators shift individual events in a pattern forward or backward in time, adding groove and human feel without changing the notes themselves. All amounts are step-relative: they represent a fraction of the event’s step size, so the same value produces proportionally the same feel regardless of pattern density.
Original note durations are preserved — notes are moved in time, not shortened or lengthened.
.nudge(offsets)
Section titled “.nudge(offsets)”Apply per-step timing offsets to pattern events. Each offset is a fraction of the event’s step size (0.0 = no shift, positive = forward, negative = backward). Offsets cycle through the array for each 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 |
The offsets array wraps around if the pattern has more events than offsets:
// Two offsets applied to 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. Notes may overlap slightly.
Patterned offsets
Section titled “Patterned offsets”Offsets can come from a pattern, changing per cycle:
// Alternating offset arrays: one cycle nudged, next cycle straight[bd sd bd sd].nudge(<[0 0.25 0 -0.1] [0 0 0 0]>).swing(amount)
Section titled “.swing(amount)”Classic swing feel. Even-indexed events stay on the grid, odd-indexed events shift forward by amount as a fraction of their step size.
[bd sd bd sd].swing(0.25) // moderate swingTypical swing amounts:
| Amount | Feel |
|---|---|
| 0.15 | Subtle shuffle |
| 0.25 | Moderate swing |
| 0.33 | Triplet feel (classic MPC) |
| 0.5 | Maximum / dotted feel |
Because amounts are step-relative, swing(0.33) produces the same triplet feel whether the pattern has 4 events or 16 events.
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 swing amount can vary per cycle:
// Moderate swing on one cycle, triplet on the next[bd sd bd sd].swing(<0.25 0.33>).humanize(amount)
Section titled “.humanize(amount)”Deterministic random timing variation. Each event receives a unique offset derived from a hash of its start time, mapped to the range [-amount, +amount] as a fraction of the event’s step size.
[bd sd bd sd].humanize(0.1) // +/- 10% of step jitterKey properties:
- Deterministic: the same pattern always produces the same offsets. No randomness between playback cycles.
- Unique per event: each event’s offset is different, derived from its position.
- Bounded: offsets never exceed
[-amount, +amount]of the event’s step size. - Density-invariant: the same amount produces proportionally the same jitter for 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 stepThe offsets are unique per event — derived from a hash of each event’s start time. This means the variation is always the same on every playback, but each step gets a different offset.
Recommended amounts:
| 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”The humanize amount can vary per cycle:
// Tight on one cycle, loose on the next[bd sd bd sd].humanize(<0.05 0.2>)Combining Methods
Section titled “Combining Methods”Microtiming combinators can be chained. Each combinator sees the already-shifted events from the previous one.
Order matters
Section titled “Order matters”Apply structural timing first, then randomness last:
.nudge()or.swing()— set the groove skeleton.humanize()— add variation on top
// Recommended: swing first, then humanize[bd sd bd sd].swing(0.25).humanize(0.05)
// Also valid: custom groove with humanize[bd sd bd sd].nudge([0.0, 0.15, 0.0, -0.08]).humanize(0.05)Applying humanize before swing means the swing offsets land on already-jittered positions, which can produce less predictable groove structures.
Combination recipes
Section titled “Combination recipes”| 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 |
.nudge([0 0.1]) | Gentle push on every other beat (like mild swing) |
Combined timing diagram
Section titled “Combined timing diagram”Original: | bd | sd | bd | sd | 0 0.25 0.5 0.75 1.0
After .swing(0.25): | bd | sd | bd | sd | 0 0.3125 0.5 0.8125 1.0
After .humanize(0.05): | bd | sd | bd | sd | 0 0.31 0.51 0.81 1.0 ← ← ← Each event jittered ±5% of step from its swung positionEdge Cases
Section titled “Edge Cases”- Boundary filtering: Events shifted outside the cycle
[0, 1)are dropped. The pattern engine only keeps events whose start time falls within the cycle. - Negative nudge on first event: An event at position
0.0nudged by a negative offset moves before the cycle start and is filtered out. - Zero / empty passthrough:
.nudge([]),.nudge([0, 0, 0]),.swing(0.0), and.humanize(0.0)all pass events through unchanged with no performance overhead. - Duration preservation: Notes keep their original duration after shifting. A shifted note may extend past the cycle boundary — it sustains at its full length.
- Overlaps: Small overlaps between notes from timing shifts are allowed and sound natural.
Musical Examples
Section titled “Musical Examples”Swing Groove
Section titled “Swing Groove”use "std/instruments" { Sampler, Kit };
let drums = AudioTrack("drums");drums.load_instrument(Sampler(Kit("cr78")));drums << [bd sd bd sd].swing(0.25);Humanized Hi-hats
Section titled “Humanized Hi-hats”let hats = AudioTrack("hats");hats.load_instrument(Sampler(Kit("cr78")));hats << [hh*16].humanize(0.1);Custom Groove with Nudge
Section titled “Custom Groove with Nudge”For full control over the timing of each step:
// Push the snare late, pull the last kick earlydrums << [bd sd bd sd].nudge([0.0, 0.15, 0.0, -0.08]);Layered Microtiming
Section titled “Layered Microtiming”Different layers can have independent microtiming:
let kicks = AudioTrack("kicks");kicks.load_instrument(Sampler(Kit("cr78")));kicks << [bd*4].humanize(0.05);
let hats = AudioTrack("hats");hats.load_instrument(Sampler(Kit("cr78")));hats << [hh*8].swing(0.2).humanize(0.05);
let snares = AudioTrack("snares");snares.load_instrument(Sampler(Kit("cr78")));snares << [_ sd _ sd].nudge([0.0, 0.1]);See Also
Section titled “See Also”- Effects — audio processing chains
- Routing — sends, submixing, routing introspection
- Signals & Automation — continuous modulation
- Samplers — loading instruments