Skip to content

Pattern Basics

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

Patterns are the core music type in Resonon. They describe sequences of notes, rests, and samples that play over time.

A pattern is enclosed in square brackets [...] and contains musical elements:

[C4 E4 G4] // Three notes in sequence
[C4 _ E4 _] // Notes with rests (underscore)
[bd sd bd sd] // Drum samples (when using a sampler)

Patterns fit into cycles. A cycle is the fundamental time unit in Resonon — patterns repeat every cycle. Cycle duration is controlled by BPM and beats per cycle:

setbpm(120); // 120 BPM (default: 4 beats per cycle)
setbpm(140, 4); // 140 BPM, 4 beats per cycle

Elements within a pattern divide the cycle evenly:

[C4] // One note fills the whole cycle
[C4 D4] // Each note gets 1/2 of the cycle
[C4 D4 E4 F4] // Each note gets 1/4 of the cycle

The more elements in a pattern, the faster they play:

PatternElementsDuration per element
[C4]1Full cycle
[C4 D4]21/2 cycle each
[C4 D4 E4]31/3 cycle each
[C4 D4 E4 F4]41/4 cycle each

Cycle duration depends on BPM and how many beats you assign per cycle. The relationship is:

cps = bpm / 60 / beats_per_cycle
cycle_duration = 1 / cps
BPMBeats/CycleCycle DurationCPS
6044.0 s0.25
12042.0 s0.5
12084.0 s0.25
1404~1.71 s~0.58
1408~3.43 s~0.29

Use the second argument to setbpm when your patterns have more (or fewer) than 4 beats:

setbpm(120, 8); // 8-beat cycles at 120 BPM (4 seconds per cycle)

Use _ (underscore) for silent steps:

[C4 _ E4 _] // Play, rest, play, rest
[bd _ _ sd] // Kick on 1, snare on 4
[_ _ _ sd] // Snare on beat 4 only

To hear a pattern, send it to a track and call PLAY:

// MIDI track
let melody = MidiTrack(1);
melody << [C4 E4 G4 E4];
PLAY;
// Audio track with samples
use "std/instruments" { Sampler, Kit };
let drums = AudioTrack("drums");
drums.load_instrument(Sampler(Kit("cr78")));
drums << [bd sd bd sd];
PLAY;

The << operator sends a pattern to a track.

Patterns repeat indefinitely until you stop them with PAUSE:

let t = MidiTrack(1);
t << [C4 D4 E4 F4];
PLAY; // Pattern loops forever
// ...
PAUSE; // Stop playback

Numbers in patterns are interpreted as MIDI note values (0-127):

[60 62 64 65] // Same as [C4 D4 E4 F4]
[C4 62 E4 67] // Mix note names and numbers freely

This is especially useful for drum programming with General MIDI note numbers:

// GM drum map: 36=kick, 38=snare, 42=closed hh
[36 42 38 42]

The syntax you use determines the pattern type:

SyntaxTypeBehavior
[C4 D4 E4]SequenceElements divide the cycle proportionally
[C4, E4, G4]StackAll elements play simultaneously
<C4 D4 E4>AlternatingOne element per cycle, rotating
[bd sd hh]Sample patternSame as Sequence, but for audio samples

Sequence is the most common type. Elements share the cycle by equal weight (adjustable with @ — see Compact Notation).

Stack plays all elements at the same time, creating chords or layered rhythms. See Nested & Parallel Patterns for details.

Alternating selects one element per cycle and rotates through them. See Nested & Parallel Patterns for details.

Sample patterns behave identically to Sequences, but when sent to an AudioTrack with a sampler loaded, identifiers like bd and sd resolve as sample names instead of note names.

You can also create patterns with constructor functions:

// Sequence() builds a sequential pattern (same as [] syntax)
Sequence(C4, E4, G4)
// Stack() builds a simultaneous pattern (all notes at once)
Stack(C4, E4, G4)
// Alternating() builds a rotating pattern (same as <> syntax)
Alternating(C4, E4, G4)
// All three accept an array argument
let notes = #[C4, D4, E4, F4, G4];
Sequence(notes)
Stack(notes)
Alternating(notes)

Patterns are lazy — they compute events on demand, not upfront. Think of a pattern as a recipe, not a recording.

Three key properties:

  • On-demand: the scheduler queries one cycle at a time, only computing what it needs right now
  • Stateless: each query is independent — you can ask for any cycle in any order
  • Infinite by default: patterns repeat forever without storing infinite data

This has a practical consequence: transformations like .fast(2) and .slow(2) adjust the query window, not the data. Chaining many transformations has zero performance cost because nothing is computed until the scheduler asks for events.