Skip to content

Pattern Methods

This content is for v0.7. Switch to the latest version for up-to-date documentation.

Patterns support methods for transformation and manipulation. Methods return new patterns (patterns are immutable), enabling powerful method chaining.

CategoryMethodsPurpose
Speed.fast(), .slow()Control playback rate
Transposition.transpose()Shift pitch by semitones
Reordering.reverse(), .rotate()Change element order
Combination.cat() / .concat(), .stack()Join or layer patterns
Degradation.degrade()Randomly drop notes
Euclidean.euclid()Euclidean rhythm distribution
Microtiming.nudge(), .swing(), .humanize()Per-step timing offsets
Indexing.at()Access individual elements
Mapping.map(), .map_with_timing()Transform each note
Iteration.iter()Create event iterator
Query.length(), .describe()Inspect patterns

Make the pattern play multiple times per cycle:

[C4 D4 E4].fast(2) // Pattern plays 2x per cycle
[C4 D4 E4].fast(4) // Pattern plays 4x per cycle

Make the pattern span multiple cycles:

[C4 D4 E4].slow(2) // Pattern spans 2 cycles
[C4 D4 E4].slow(4) // Pattern spans 4 cycles

These affect the whole pattern’s duration, not individual elements.

For a 3-note pattern [C4 D4 E4]:

MethodEvents per cycleDuration per event
.fast(2)61/6 cycle
.fast(0.5)1.51/1.5 cycle
.slow(2)1.51/1.5 cycle
.slow(4)0.751/0.75 cycle
// Verify with .describe()
PRINT [C4 D4 E4].fast(2).describe()
// => "Fast(2, Sequence[3])"

Shift all notes by semitones:

let pat = [C4 E4 G4];
pat.transpose(12) // One octave up: [C5 E5 G5]
pat.transpose(-12) // One octave down: [C3 E3 G3]
pat.transpose(7) // Up a fifth: [G4 B4 D5]

Rests pass through unchanged — they have no pitch to transpose.

SemitonesInterval
1Minor 2nd
2Major 2nd
3Minor 3rd
4Major 3rd
5Perfect 4th
7Perfect 5th
12Octave

Chaining transposition with other methods:

// Harmonize a melody in parallel thirds
let melody = [C4 D4 E4 F4];
melody.stack(melody.transpose(4))

Reverse the order of elements:

let pat = [C4 D4 E4 F4];
pat.reverse() // [F4 E4 D4 C4]
// Palindrome pattern: forward then backward
let base = [C4 D4 E4 G4];
base.cat(base.reverse())

Shift elements left (positive) or right (negative):

let pat = [C4 D4 E4 F4];
pat.rotate(1) // [D4 E4 F4 C4] — shift left by 1
pat.rotate(2) // [E4 F4 C4 D4] — shift left by 2
pat.rotate(-1) // [F4 C4 D4 E4] — shift right by 1

Rotation results for [C4 D4 E4 F4]:

RotationResult
0[C4 D4 E4 F4]
1[D4 E4 F4 C4]
-1[F4 C4 D4 E4]
2[E4 F4 C4 D4]
// Cycle through all rotations of a pattern
let pat = [C4 E4 G4 B4];
let r0 = pat;
let r1 = pat.rotate(1);
let r2 = pat.rotate(2);
let r3 = pat.rotate(3);

Join patterns end-to-end:

let pat = [C4 D4];
pat.cat([E4 F4]) // [C4 D4 E4 F4] — sequential
pat.concat([E4 F4]) // Same as .cat()
// Build a longer phrase from fragments
[C4 D4].cat([E4 F4]).cat([G4 A4])

Play patterns simultaneously:

let melody = [C4 E4 G4];
melody.stack([G3 B3 D4]) // Both play at the same time
// Three-layer stack
[C4 E4 G4].stack([E3 G3 B3]).stack([C3*3])

Randomly drop notes from a pattern:

let pat = [C4 D4 E4 F4 G4 A4 B4 C5];
pat.degrade(0.25) // Light — 25% chance to drop each note
pat.degrade(0.5) // Medium
pat.degrade(0.75) // Heavy

Useful for humanizing hi-hats:

[C4*8].degrade(0.15) // Straight 8ths with occasional drops
// Degrade combined with speed for glitchy textures
[C4 D4 E4 F4].fast(4).degrade(0.4)

Apply Euclidean rhythm distribution to a pattern:

[C4].euclid(3, 8) // 3 hits spread over 8 steps
[C4].euclid(5, 8) // 5 hits over 8 steps
[C4].euclid(3, 8, 1) // With rotation offset

See Euclidean Rhythms for more details.

Microtiming methods shift individual events forward or backward in time without changing notes. All amounts are step-relative (fraction of the event’s step size), so the same value works regardless of pattern density. Original note durations are preserved.

Apply per-step timing offsets. Each offset is a fraction of the event’s step size (0.0 = no shift, positive = forward, negative = backward):

[bd sd bd sd].nudge([0.0, 0.25, 0.0, -0.1])
OffsetMeaning
0.0No shift
0.2525% of step forward
-0.110% of step backward

The offsets array cycles 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])

Events that shift outside the cycle boundary are filtered out.

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 swing
AmountFeel
0.15Subtle shuffle
0.25Moderate swing
0.33Triplet feel (classic MPC)
0.5Maximum / dotted feel

Deterministic random timing variation. Each event receives a unique offset derived from a hash of its start time, mapped to [-amount, +amount] of the event’s step size.

[bd sd bd sd].humanize(0.1) // +/- 10% of step jitter

Key properties:

  • Deterministic: same pattern always produces the same offsets
  • Unique per event: each event’s offset differs, derived from its position
  • Bounded: offsets never exceed [-amount, +amount] of step size
AmountFeel
0.05Barely perceptible
0.1Subtle human feel
0.15Noticeable but musical
0.25Loose, relaxed timing

Microtiming methods chain naturally:

// Swing for groove structure, then humanize for variation
[bd sd bd sd].swing(0.25).humanize(0.05)

See Microtiming for full groove recipes and layered examples.

Access individual elements by index:

let pat = [C4 D4 E4 F4];
pat.at(0) // First element as a pattern
pat[2] // Third element (same as .at(2))

Works on all pattern types:

let chord = [C4, E4, G4, B4];
chord[0] // Root
let alt = <C4 E4 G4>;
alt[1] // Second alternative

Indexing can be chained with other methods:

[C4, E4, G4].at(0).transpose(12) // Root up one octave

Transform each note in a pattern with a function.

The callback receives each MIDI pitch as a number:

let melody = [C4 D4 E4 F4 G4 A4 B4 C5];
// Melodic inversion around C4
let pivot = 60;
let inverted = melody.map(fn(note) {
return pivot - (note - pivot);
});
// Octave fold — constrain notes into C4-B4
let folded = melody.map(fn(note) {
return 60 + (note % 12);
});

The callback can return different types:

// Number: replaces the note
pat.map(fn(note) { return note + 12; });
// Array: produces stacked (simultaneous) notes
let chords = #[#[C4, E4, G4], #[D4, Gb4, A4]];
[0 1].map(fn(s) { return chords[s]; });
// Pattern: replaces each note with a sub-pattern
pat.map(fn(note) { return [C4 E4 G4 C5]; });

The callback receives (note, start, duration) where start is the position within the cycle (0.0 to 1.0):

let pattern = [C4 D4 E4 F4 G4 A4 B4 C5];
// Silence the second half of each cycle
let silence = [_];
let first_half = pattern.map_with_timing(fn(note, start, dur) {
if start >= 0.5 {
return silence;
}
return note;
});
// Alternating octaves by position
let alternating = pattern.map_with_timing(fn(note, start, dur) {
let index = start * 8;
if index % 2 < 1 {
return note;
}
return note + 12;
});

Create an infinite iterator over pattern events:

let pat = [C4 D4 E4];
let events = pat.iter().take(8).collect(); // Always use .take()!

See Iterators for full details.

Get a human-readable description of a pattern’s structure:

PRINT [C4 D4 E4].describe()
// => "Sequence[3]"
PRINT [C4, E4, G4].describe()
// => "Stack[Sequence[1], Sequence[1], Sequence[1]]"
PRINT <C4 E4 G4>.describe()
// => "Alternating[3]"

.describe() nests, showing the full transformation pipeline:

PRINT [C4 D4 E4].fast(2).describe()
// => "Fast(2, Sequence[3])"
PRINT [C4 D4 E4].reverse().transpose(7).describe()
// => "Transpose(7, Reverse(Sequence[3]))"
PRINT [C4 D4 E4].fast(2).degrade(0.3).describe()
// => "Degrade(0.3, Fast(2, Sequence[3]))"
PRINT [C4 D4].swing(0.25).describe()
// => "Swing(0.25, Sequence[2])"
PRINT [C4 D4].humanize(0.1).describe()
// => "Humanize(0.1, Sequence[2])"
PRINT [C4 D4].nudge([0.0, 0.05]).describe()
// => "Nudge([0.0, 0.05], Sequence[2])"

Use PRINT to inspect patterns at any point in a chain:

let base = [C4 D4 E4 F4];
PRINT base.describe()
// => "Sequence[4]"
let transformed = base.reverse().fast(2);
PRINT transformed.describe()
// => "Fast(2, Reverse(Sequence[4]))"
PRINT transformed.length()
// => 4

Count the number of elements in a pattern:

let pat = [C4 D4 E4 F4];
pat.length() // 4

Methods return new patterns, enabling chaining:

[C4 D4 E4]
.reverse()
.transpose(12)
.fast(2)
// Result: plays reversed+transposed pattern 2x per cycle

The order of operations affects the result:

[C4 D4].concat([E4]).reverse() // [E4 D4 C4]
[C4 D4].reverse().concat([E4]) // [D4 C4 E4]

How order changes the output for [C4 D4 E4]:

ChainResult
.reverse().fast(2)Reversed pattern, played 2x per cycle
.fast(2).reverse()Sped-up pattern, then reversed
.transpose(12).degrade(0.5)Transposed first, then notes dropped
.degrade(0.5).transpose(12)Notes dropped first, survivors transposed
// Swung hi-hats with occasional drops
[C4*8].degrade(0.15).swing(0.2)
// Rising arpeggio that speeds up
[C4 E4 G4 C5].transpose(12).fast(2)
// Groove-locked bass pattern
[C3 _ C3 G2].swing(0.25).humanize(0.05)
// Evolving chord sequence
let chord = [C4, E4, G4];
chord.rotate(1).transpose(5).slow(2)
MethodEquivalent compact notationEffect
.fast(2)[C4 D4]*2Double playback speed
.fast(0.5)[C4 D4]/2Half playback speed
.slow(2)[C4 D4]/2Half playback speed
.slow(0.5)[C4 D4]*2Double playback speed
MethodControlDeterministicUse case
.nudge(offsets)Per-step arrayYesCustom groove templates
.swing(amount)Single valueYesClassic swing feel
.humanize(amount)Single valueYes (hash-based)Natural timing variation
MethodChanges notes?Changes timing?Changes order?
.transpose()YesNoNo
.reverse()NoNoYes
.rotate()NoNoYes
.fast() / .slow()NoYesNo
.degrade()Yes (drops)NoNo
.nudge() / .swing() / .humanize()NoYesNo
.map()YesNoNo
let base = [C4 E4 G4];
let patterns = Array(
base,
base.transpose(5), // F major
base.transpose(7), // G major
base.transpose(5) // Back to F
);
let kick = [C4 _ C4 _].swing(0.25);
let hat = [C4*8].degrade(0.15).humanize(0.015);
let snare = [_ C4 _ C4].nudge([0.0, 0.04]);
fn arpeggio(base) {
return base.concat(base.reverse());
}
let cArp = arpeggio([C4 E4 G4]);
// Build a 4-bar phrase with method chaining
let motif = [C4 D4 E4 G4];
let phrase = motif
.cat(motif.transpose(5)) // Add F major variation
.cat(motif.rotate(2)) // Add rotated variation
.cat(motif.reverse()) // End with retrograde
.slow(4); // Spread over 4 cycles
// Add groove
let grooved = phrase.swing(0.2).humanize(0.05);