Skip to content

Transforming Patterns

Once you have a pattern, you rarely leave it alone. You speed it up, flip it around, transpose it, layer it against itself. In Resonon you do this with methods.fast(), .transpose(), .reverse(), and friends — and you chain them together to build a phrase one transformation at a time.

Every method returns a new pattern and leaves the original untouched. Think of it like non-destructive editing in a DAW: the source clip never changes, you just stack edits on top. That means you can derive a dozen variations from one motif without ever clobbering it.

Here’s where we’re headed — a four-note motif, flipped and doubled, played through a melodic sampler:

use "std/instruments" { SamplerMelodic, Kit };
let lead = AudioTrack("lead");
lead.load_instrument(SamplerMelodic(Kit("keys")));
lead << [C4 E4 G4 C5].reverse().fast(2);
PLAY;

Reading the chain left to right: start with the arpeggio [C4 E4 G4 C5], .reverse() it so it descends, then .fast(2) so the whole thing plays twice per cycle. The rest of this chapter walks through the transforms you’ll reach for most.

.fast(n) plays the pattern n times per cycle; .slow(n) stretches it across n cycles. They’re inverses — .slow(2) is exactly .fast(0.5).

let pat = [C4 D4 E4];
pat.fast(2); // plays twice per cycle
pat.slow(2); // spans two cycles

These change the pattern’s overall timing, not the order or pitch of its notes. They’re the method form of the * and / modifiers from mini-notation[C4 D4 E4].fast(2) and [[C4 D4 E4]*2] produce the same thing.

.transpose(semitones) shifts every note by a number of semitones — positive up, negative down. Rests have no pitch, so they pass through untouched.

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

A handy trick: stack a pattern against a transposed copy of itself to harmonize it. Up four semitones is a major third:

let melody = [C4 D4 E4 F4];
melody.stack(melody.transpose(4));

.reverse() plays the notes back to front. .rotate(n) shifts them left by n (negative shifts right), wrapping around the ends.

let pat = [C4 D4 E4 F4];
pat.reverse(); // [F4 E4 D4 C4]
pat.rotate(1); // [D4 E4 F4 C4]
pat.rotate(-1); // [F4 C4 D4 E4]

Rotation wraps with modulo, so on a 4-note pattern .rotate(4) is the original and .rotate(5) equals .rotate(1). A reversed copy concatenated onto the original gives you a palindrome:

let base = [C4 D4 E4 G4];
base.cat(base.reverse());

.cat() joins patterns end to end (sequential); .stack() layers them so they sound at the same time (simultaneous). .concat() is an alias for .cat().

let a = [C4 D4];
a.cat([E4 F4]); // [C4 D4 E4 F4] — one after the other
a.stack([G3 B3]); // both play together

.cat() is the method form of writing patterns in sequence; .stack() is the method form of the comma in [C4, E4, G4]. Both chain, so you can build up layers:

[C4 E4 G4].stack([E3 G3 B3]).stack([C3*3]);

.degrade(probability) randomly drops notes, where probability is the chance each note is removed. It’s perfect for loosening up a busy part — a stream of hi-hats with the occasional gap:

[C4*8].degrade(0.15); // straight 8ths, ~15% of hits dropped

.map(fn) runs a function over every note’s MIDI number and uses what you return in its place. This is the escape hatch — anything you can compute, you can apply per note.

let melody = [C4 D4 E4 F4 G4];
// Octave fold — wrap everything into the C4–B4 range
melody.map(fn(note) {
return 60 + (note % 12);
});

Return a number to replace the note, an array to turn it into a chord, or a pattern to substitute a whole sub-sequence. For timing-aware transforms, .map_with_timing(fn) hands your callback (note, start, duration) where start is the note’s position in the cycle from 0.0 to 1.0:

let pat = [C4 D4 E4 F4];
// Lift the second half of the cycle up an octave
pat.map_with_timing(fn(note, start, dur) {
if start >= 0.5 { return note + 12; }
return note;
});

When a chain doesn’t sound right, .describe() shows you exactly what you built, and .length() counts the events in one cycle:

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 F4].length();
// => 4

Because each method wraps the result of the previous one, the order of a chain changes the outcome:

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

In the first, you reverse the whole three-note sequence; in the second, you reverse just [C4 D4] and then append E4. When a chain surprises you, .describe() it and read inside-out to see what happened.

Transforms reshape what plays. Next, shape exactly when it plays.

  • Microtiming — swing, nudge, and humanize for groove