Streams & Markov Chains
The markov() function creates patterns where each cycle’s state depends on the previous state through a transition probability matrix. Markov chains are seekable and deterministic — querying the same cycle always gives the same result.
Creating a Markov Chain
Section titled “Creating a Markov Chain”use "std/generative" { markov };
markov(states, transitions, initial)| Parameter | Type | Description |
|---|---|---|
states | Integer | Number of states in the chain |
transitions | Array of arrays | Transition probability matrix — row i defines the weights for leaving state i |
initial | Integer | Starting state index (0-based) |
markov() outputs state indices (0, 1, 2, …). Use .map() to convert these to actual musical values.
Basic 2-State Chain
Section titled “Basic 2-State Chain”Deterministic alternation between two states:
let alternating = markov( 2, #[ #[0.0, 1.0], // State 0 always goes to state 1 #[1.0, 0.0] // State 1 always goes to state 0 ], 0 // Start at state 0);
let alt_notes = #[C4, G4];let melody = alternating.map(fn(state) { return alt_notes[state];});How It Works
Section titled “How It Works”Each cycle, the chain:
- Hashes the cycle number to get a deterministic random value in [0.0, 1.0)
- Looks up the current state’s row in the transition matrix
- Walks the cumulative probabilities until the hash value is exceeded
- Transitions to that state
Because hash(cycle) is a pure function, the same cycle always produces the same transition. The chain is fully deterministic.
For the 2-state alternating example above, the first few cycles trace like this:
| Cycle | Current state | Row | Hash selects | Next state |
|---|---|---|---|---|
| 0 | 0 | [0.0, 1.0] | state 1 (100%) | 1 |
| 1 | 1 | [1.0, 0.0] | state 0 (100%) | 0 |
| 2 | 0 | [0.0, 1.0] | state 1 (100%) | 1 |
| 3 | 1 | [1.0, 0.0] | state 0 (100%) | 0 |
With probabilistic rows, different hash values land in different cumulative buckets, producing varied but repeatable sequences.
3-State Melody
Section titled “3-State Melody”let three_state = markov( 3, #[ #[0.5, 0.5, 0.0], // State 0: equal chance of 0 or 1 #[0.0, 0.5, 0.5], // State 1: equal chance of 1 or 2 #[0.5, 0.0, 0.5] // State 2: equal chance of 2 or 0 ], 0);
let melody_notes = #[C4, E4, G4];let simple_melody = three_state.map(fn(state) { return melody_notes[state];});The zero entries prevent certain jumps — state 0 can never go directly to state 2, and state 2 can never reach state 1. This creates a directed flow: 0 → 1 → 2 → 0, with occasional self-loops.
Auto-Normalization
Section titled “Auto-Normalization”Rows do not need to sum to 1.0. Resonon normalizes them automatically, so you can use intuitive weights:
let weighted = markov( 3, #[ #[1, 3, 1], // "3" means 3x more likely than "1" #[1, 1, 3], #[3, 1, 1] ], 0);Designing Transition Matrices
Section titled “Designing Transition Matrices”The shape of the transition matrix determines the character of the resulting pattern. Here are common design patterns:
| Pattern | Matrix shape | Musical effect | Use case |
|---|---|---|---|
| Stepwise | High weights on adjacent states, low elsewhere | Smooth, conjunct motion | Melodies, bass lines |
| Hub-and-spoke | One state reachable from all others, others only reachable from the hub | Returns to a tonal center | Chord progressions with a strong tonic |
| Cyclic | Weights form a loop (0→1→2→0) | Predictable rotation with variation | Repeating phrase structures |
| Absorbing | One state has [0, ..., 0, 1] (self-loop only) | Settles permanently on a final value | Cadences, endings, resolution |
| Ergodic | All entries non-zero | Any state reachable from any other | Free exploration, ambient textures |
Favoring Stepwise Motion
Section titled “Favoring Stepwise Motion”Give adjacent states higher weights to create smooth melodic lines. The Scale Degree Walk example below uses this approach — neighbor states get ~0.27, next-neighbors ~0.13, and far states ~0.07.
Creating a Tonal Center
Section titled “Creating a Tonal Center”Make one state the “hub” that most other states transition back to:
// Hub-and-spoke: state 0 (tonic) is the centerlet hub = markov( 4, #[ #[0, 2, 2, 1], // Tonic -> anywhere #[4, 0, 1, 0], // State 1 -> strongly back to tonic #[3, 0, 0, 2], // State 2 -> mostly back to tonic #[5, 1, 0, 0] // State 3 -> very strongly back to tonic ], 0);Avoiding Repetition
Section titled “Avoiding Repetition”Set diagonal entries (self-transitions) to zero or low values to prevent a state from repeating:
#[ #[0, 1, 1], // State 0 never repeats #[1, 0, 1], // State 1 never repeats #[1, 1, 0] // State 2 never repeats]5-State Chord Progression
Section titled “5-State Chord Progression”Model a chord progression as a Markov chain (I, IV, V, vi, ii):
let progression = markov( 5, #[ #[0.0, 0.3, 0.4, 0.3, 0.0], // I -> IV, V, vi #[0.2, 0.0, 0.5, 0.0, 0.3], // IV -> I, V, ii #[0.7, 0.0, 0.0, 0.3, 0.0], // V -> I, vi #[0.0, 0.5, 0.0, 0.0, 0.5], // vi -> IV, ii #[0.0, 0.2, 0.8, 0.0, 0.0] // ii -> IV, V ], 0);
let chords = #[ [C4, E4, G4], // I [F4, A4, C5], // IV [G4, B4, D5], // V [A4, C5, E5], // vi [D4, F4, A4] // ii];
let chord_pattern = progression.map(fn(state) { return chords[state];});The matrix encodes common-practice voice leading tendencies: V resolves strongly to I (0.7), ii is a predominant that leads to V (0.8), and vi moves to subdominant territory (IV or ii). Zeros enforce constraints — I never jumps directly to ii, and V never returns to IV.
Method Chaining
Section titled “Method Chaining”All standard pattern methods work on Markov chain output:
// Transpose up a fifthchord_pattern.transpose(7)
// Double speedsimple_melody.fast(2)
// Layer two Markov patternslet bass = alternating.map(fn(s) { return #[C4, G4][s]; }).transpose(-12);simple_melody.map(fn(s) { return #[C4, E4, G4][s]; }).stack(bass)Absorbing State
Section titled “Absorbing State”A state that transitions only to itself acts as an absorbing state, useful for resolution:
let resolving = markov( 3, #[ #[0.0, 0.7, 0.3], // Start: mostly to state 1 #[0.0, 0.3, 0.7], // Middle: mostly to state 2 #[0.0, 0.0, 1.0] // End: stays forever (absorbing) ], 0);
let resolution_chords = #[ [D4, F4, A4], // ii [G4, B4, D5], // V [C4, E4, G4] // I (absorbing)];
let resolution = resolving.map(fn(state) { return resolution_chords[state];});Scale Degree Walk
Section titled “Scale Degree Walk”Use a transition matrix that favors stepwise motion for natural-sounding melodies:
// Neighbors (~0.27), next-neighbors (~0.13), far states (~0.07)let scale_chain = markov( 7, #[ #[0.06, 0.27, 0.13, 0.07, 0.07, 0.13, 0.27], #[0.27, 0.06, 0.27, 0.13, 0.07, 0.07, 0.13], #[0.13, 0.27, 0.06, 0.27, 0.13, 0.07, 0.07], #[0.07, 0.13, 0.27, 0.06, 0.27, 0.13, 0.07], #[0.07, 0.07, 0.13, 0.27, 0.06, 0.27, 0.13], #[0.13, 0.07, 0.07, 0.13, 0.27, 0.06, 0.27], #[0.27, 0.13, 0.07, 0.07, 0.13, 0.27, 0.06] ], 0);
let scale_notes = #[C4, D4, E4, F4, G4, A4, B4];let scale_melody = scale_chain.map(fn(state) { return scale_notes[state];});This matrix has a Toeplitz structure — each row is a rotation of the same weight pattern. This means every scale degree has the same relative transition probabilities to its neighbors, producing uniform melodic behavior regardless of starting position.
Melodic Fragments
Section titled “Melodic Fragments”Map states to entire patterns instead of single notes:
let phrase_chain = markov( 3, #[ #[0.2, 0.4, 0.4], #[0.5, 0.2, 0.3], #[0.4, 0.4, 0.2] ], 0);
let phrases = #[ [C4 E4 G4 E4], // Arpeggio [G4 F4 E4 D4], // Descending [C4 D4 E4 F4] // Ascending];
let phrase_melody = phrase_chain.map(fn(state) { return phrases[state];});Combining with Other Patterns
Section titled “Combining with Other Patterns”Stack a Markov melody with an Euclidean rhythm:
let rhythm = euclid(3, 8, [C2]);let combined = phrase_melody.stack(rhythm);Debugging with .describe()
Section titled “Debugging with .describe()”The .describe() method shows the pattern type:
alternating.describe()// => "Markov[2 states]"When chained with methods, .describe() shows the full transformation chain:
chord_pattern.fast(2).transpose(7).describe()// => "Transpose(Fast(Markov[5 states], 2), 7)"Seekability
Section titled “Seekability”Markov chains are deterministic. The same cycle always produces the same state, regardless of when you query it:
viz(chord_pattern, 1, 20); // Query cycle 20viz(chord_pattern, 1, 20); // Identical resultThis means you can seek freely without affecting the output. Methods like .slow(), .fast(), and tools like viz() all work correctly because seeking to any cycle reproduces the exact same state sequence.
Performance
Section titled “Performance”Seekability has a cost: to determine the state at cycle N, the chain must replay all N transitions from cycle 0 (O(N) recomputation). Each transition is cheap (a hash and a cumulative-probability walk), and the scheduler checks deadlines between cycles, so extremely long seeks are interrupted gracefully rather than blocking.
Markov Chains vs Other Patterns
Section titled “Markov Chains vs Other Patterns”| Markov chain | Script Pattern | Stream | choose_weighted() | |
|---|---|---|---|---|
| State memory | Yes — next value depends on current state | Yes — multiple named fields | Yes — carries state between cycles | No — stateless selection |
| Control mechanism | Transition matrix | Class with query() method | Callback with mutable state | Weight array |
| Deterministic | Yes | Yes (O(N) replay) | Yes (with seeded rand()) | Yes |
| Seekable | Yes (O(N) replay) | Yes (O(N) replay) | Yes (O(N) replay) | Yes |
| Best use case | Probabilistic sequences with directed flow | Complex multi-field stateful logic | Accumulating or evolving state | Weighted one-off picks |
Musical Examples
Section titled “Musical Examples”Generative Chord Progression
Section titled “Generative Chord Progression”A hub-and-spoke matrix where the tonic (I) is the center of gravity:
let chords_track = MidiTrack(1);
let prog = markov( 4, #[ #[0, 3, 2, 1], // I -> IV (strong), V, vi #[4, 0, 2, 0], // IV -> I (strong), V #[5, 0, 0, 1], // V -> I (very strong), vi #[1, 3, 2, 0] // vi -> I, IV (strong), V ], 0);
let chord_voicings = #[ [C4, E4, G4], // I [F4, A4, C5], // IV [G4, B4, D5], // V [A4, C5, E5] // vi];
let chord_seq = prog.map(fn(state) { return chord_voicings[state];});
chords_track << chord_seq;PLAY;Markov Bass + Euclidean Rhythm
Section titled “Markov Bass + Euclidean Rhythm”A stepwise bass walk layered with an Euclidean kick pattern:
let bass_track = MidiTrack(2);
let bass_chain = markov( 5, #[ #[0, 3, 1, 0, 1], // Favor stepwise up #[2, 0, 3, 1, 0], #[1, 2, 0, 3, 1], #[0, 1, 2, 0, 3], #[3, 0, 0, 2, 0] // Top wraps back to bottom ], 0);
let bass_notes = #[C2, D2, E2, G2, A2];let bass_line = bass_chain.map(fn(state) { return bass_notes[state];});
let kick = euclid(3, 8, [C1]);let combined = bass_line.stack(kick);
bass_track << combined;PLAY;Layered Markov Texture
Section titled “Layered Markov Texture”A fast melody chain and a slow pad chain on separate tracks:
let melody_track = MidiTrack(1);let pad_track = MidiTrack(2);
// Fast melody — 7-state scale walk at double speedlet mel_chain = markov( 7, #[ #[0, 3, 1, 0, 0, 1, 3], #[3, 0, 3, 1, 0, 0, 1], #[1, 3, 0, 3, 1, 0, 0], #[0, 1, 3, 0, 3, 1, 0], #[0, 0, 1, 3, 0, 3, 1], #[1, 0, 0, 1, 3, 0, 3], #[3, 1, 0, 0, 1, 3, 0] ], 0);
let mel_notes = #[C5, D5, E5, F5, G5, A5, B5];let mel = mel_chain.map(fn(state) { return mel_notes[state];}).fast(2);
// Slow pad — 3-state chord progression at half speedlet pad_chain = markov( 3, #[ #[0, 2, 1], #[1, 0, 2], #[2, 1, 0] ], 0);
let pad_chords = #[ [C4, E4, G4], [F4, A4, C5], [G4, B4, D5]];let pad = pad_chain.map(fn(state) { return pad_chords[state];}).slow(2);
melody_track << mel;pad_track << pad;PLAY;See Also
Section titled “See Also”- Streams — Non-matrix stateful patterns
- Script Patterns — Class-based stateful patterns
- Pattern Methods —
.map(),.describe(), and other transformations
The stream() function creates patterns where each cycle’s output depends on the previous cycle’s state. This is ideal for “walking” patterns like random melodic walks, evolving chords, and accumulating transformations.
Creating a Stream
Section titled “Creating a Stream”use "std/generative" { stream };
stream(initial, fn(prev, cycle) { ... })| Parameter | Type | Description |
|---|---|---|
initial | Note, number, array, or pattern | The starting value — returned on cycle 0, then passed as prev to each subsequent call |
callback | Function fn(prev, cycle) | Receives the previous cycle’s output and the current cycle number; returns the next state |
How It Works
Section titled “How It Works”Each cycle, the stream:
- Cycle 0: returns the
initialvalue directly — the callback is not called - Cycle N (N > 0): calls
callback(prev, N)whereprevis the result of cycle N-1 - Converts the returned value to pattern output for that cycle
Because choose() and rand() use the cycle number as a deterministic seed, the same cycle always produces the same result. The stream is fully seekable and deterministic.
For a chromatic walk starting at C4 with prev.transpose(1):
| Cycle | prev | Callback returns | Output |
|---|---|---|---|
| 0 | — | — | C4 (initial) |
| 1 | C4 | C4.transpose(1) | C#4 |
| 2 | C#4 | C#4.transpose(1) | D4 |
| 3 | D4 | D4.transpose(1) | D#4 |
| 4 | D#4 | D#4.transpose(1) | E4 |
Basic: Constant Output
Section titled “Basic: Constant Output”The simplest stream returns its previous value unchanged:
let constant = stream(C4, fn(prev, cycle) { return prev;});Chromatic Walk
Section titled “Chromatic Walk”Use .transpose() on the previous value to walk up or down:
// Ascending chromaticlet chromatic_up = stream(C4, fn(prev, cycle) { return prev.transpose(1);});
// Descending by whole stepslet descending = stream(C5, fn(prev, cycle) { return prev.transpose(-2);});Random Walk
Section titled “Random Walk”Use choose() with the cycle as a seed for deterministic random steps:
let steps = #[-2, -1, 1, 2];
let random_walk = stream(E4, fn(prev, cycle) { let step = choose(cycle, steps); return prev.transpose(step);});Weighted Random Walk
Section titled “Weighted Random Walk”Bias the walk in a direction with choose_weighted():
let up_steps = #[-1, 1, 2, 3];let up_weights = #[1, 2, 2, 1];
let upward_walk = stream(C4, fn(prev, cycle) { let step = choose_weighted(cycle, up_steps, up_weights); return prev.transpose(step);});Cycle-Dependent Logic
Section titled “Cycle-Dependent Logic”Use the cycle number to introduce periodic behavior:
let varied_walk = stream(G4, fn(prev, cycle) { if (cycle % 2 == 0) { return prev.transpose(12); // Jump up an octave } return prev.transpose(-1); // Otherwise step down});Bounded Walk
Section titled “Bounded Walk”Keep notes within a range by checking bounds:
let lower = C4;let upper = C5;let bound_steps = #[-3, -1, 1, 3];
let bounded = stream(C4, fn(prev, cycle) { let step = choose(cycle, bound_steps); let next = prev.transpose(step); if (next > upper) { return prev.transpose(-3); } if (next < lower) { return prev.transpose(3); } return next;});Probability with rand()
Section titled “Probability with rand()”Use rand() for probability-based decisions:
let prob_walk = stream(F4, fn(prev, cycle) { if (rand(cycle) < 0.7) { return prev.transpose(1); // 70% chance up } return prev.transpose(-1); // 30% chance down});State Types
Section titled “State Types”Streams accept different types as the initial value. Each type carries state differently:
| State type | Initial example | How state evolves | Musical use |
|---|---|---|---|
| Note | C4 | .transpose(), arithmetic | Melodic walks, bass lines |
| Number | 0 | Arithmetic, modulo | Index into arrays, counters |
| Array | #[C4, E4, G4] | .transpose(), element replacement | Evolving chords, voicings |
| Pattern | [C4 E4 G4 E4] | .transpose(), pattern methods | Shifting sequences |
Number as State
Section titled “Number as State”Use a number as state and map it to musical values:
let scale_degrees = #[C4, D4, E4, F4, G4, A4, B4];
let index_walk = stream(0, fn(prev, cycle) { let step = choose(cycle, #[-1, 0, 1]); return clamp(prev + step, 0, 6);});
let scale_melody = index_walk.map(fn(idx) { return scale_degrees[idx];});Array as State
Section titled “Array as State”Pass an array to create an evolving chord:
let evolving_chord = stream(#[C4, E4, G4], fn(prev, cycle) { return prev.transpose(2); // Shifts up a whole step each cycle});Pattern as State
Section titled “Pattern as State”Pass a pattern as the initial state. The pattern transforms itself each cycle:
let evolving_pattern = stream([C4 E4 G4 E4], fn(prev, cycle) { return prev.transpose(1); // Shifts up chromatically each cycle});Scale Run
Section titled “Scale Run”Walk through scale degrees using an index as state and an array lookup:
let major_scale = #[C4, D4, E4, F4, G4, A4, B4, C5, D5, E5, F5, G5];
let scale_run = stream(0, fn(prev, cycle) { let direction = choose(cycle, #[-1, 1, 1]); // Biased upward let next = prev + direction; // Wrap around within the scale if (next >= 12) { return 0; } if (next < 0) { return 11; } return next;});
let melody = scale_run.map(fn(idx) { return major_scale[idx];});The state is just an integer index — the musical mapping happens in .map(). This separates the walk logic from the note selection, making both easier to tweak independently.
Design Patterns
Section titled “Design Patterns”Common stream patterns and when to use them:
| Pattern | Technique | Musical effect | Use case |
|---|---|---|---|
| Linear walk | Fixed .transpose() | Steady ascending/descending motion | Scale runs, chromatic lines |
| Random walk | choose(cycle, steps) | Exploratory, unpredictable melody | Ambient, generative leads |
| Bounded walk | Range check + bounce | Contained exploration within register | Bass lines, backing parts |
| Convergent | Probability biased toward target | Gradually approaches a note | Tension-to-resolution phrases |
| Periodic reset | cycle % N logic | Repeating phrases with variation | Riff-based patterns |
| Accumulating | Array state with transformations | Evolving chords/voicings | Harmonic progression |
Convergent Walk
Section titled “Convergent Walk”Bias the walk toward a target note for tension-resolution phrasing:
let target = G4;
let converging = stream(C4, fn(prev, cycle) { if (prev < target) { // Below target: 80% chance up, 20% chance down if (rand(cycle) < 0.8) { return prev.transpose(1); } return prev.transpose(-1); } if (prev > target) { // Above target: 80% chance down, 20% chance up if (rand(cycle) < 0.8) { return prev.transpose(-1); } return prev.transpose(1); } return prev; // At target, stay});Periodic Reset
Section titled “Periodic Reset”Reset state periodically to create repeating phrases with variation:
let reset_walk = stream(C4, fn(prev, cycle) { if (cycle % 8 == 0) { return C4; // Reset every 8 cycles } let step = choose(cycle, #[-2, -1, 1, 2]); return prev.transpose(step);});Mapping Stream Output
Section titled “Mapping Stream Output”Use .map() to transform the notes a stream produces:
let descending = stream(C5, fn(prev, cycle) { return prev.transpose(-2);});
// Notes below E4 jump up an octavelet jumped = descending.map(fn(note) { if (note < E4) { return note + 12; } return note;});Method Chaining
Section titled “Method Chaining”All standard pattern methods work on streams:
// Transpose the stream outputdescending.transpose(12)
// Double speeddescending.fast(2)
// Layer two streamslet bass_steps = #[-1, 0, 1];let bass_walk = stream(C3, fn(prev, cycle) { return prev.transpose(choose(cycle, bass_steps));});descending.stack(bass_walk)Combining Streams
Section titled “Combining Streams”Stacking
Section titled “Stacking”Layer multiple streams on the same track for independent melodic lines:
let melody_walk = stream(E5, fn(prev, cycle) { let step = choose(cycle, #[-2, -1, 1, 2]); return prev.transpose(step);});
let bass_walk = stream(C3, fn(prev, cycle) { let step = choose(cycle * 7, #[-1, 0, 1]); return prev.transpose(step);});
let layered = melody_walk.stack(bass_walk);Alternating with .cat()
Section titled “Alternating with .cat()”Alternate between streams cycle by cycle:
let ascending = stream(C4, fn(prev, cycle) { return prev.transpose(2);});
let descending = stream(C5, fn(prev, cycle) { return prev.transpose(-2);});
let alternating = ascending.cat(descending);Stream with Other Pattern Types
Section titled “Stream with Other Pattern Types”Combine a stream with any other pattern type:
let rhythm = euclid(3, 8, [C2]);let walk = stream(G4, fn(prev, cycle) { return prev.transpose(choose(cycle, #[-1, 1]));});
let combined = walk.stack(rhythm);Debugging with .describe()
Section titled “Debugging with .describe()”The .describe() method shows the pattern type:
random_walk.describe()// => "Stream"When chained with methods, .describe() shows the full transformation chain:
random_walk.fast(2).transpose(7).describe()// => "Transpose(Fast(Stream, 2), 7)"Seekability
Section titled “Seekability”Streams are seekable and deterministic. Querying the same cycle always produces the same result:
let seek_steps = #[-1, 0, 1];let seekable = stream(A4, fn(prev, cycle) { return prev.transpose(choose(cycle, seek_steps));});
viz(seekable, 1, 10); // Query cycle 10viz(seekable, 1, 10); // Identical resultThis means you can seek freely without affecting the output. Methods like .slow(), .fast(), and tools like viz() all work correctly because seeking to any cycle reproduces the exact same state sequence.
Performance
Section titled “Performance”Seekability has a cost: to determine the state at cycle N, the stream must replay all N callbacks from cycle 0 (O(N) recomputation). Each callback is cheap, and the scheduler checks deadlines between cycles, so extremely long seeks are interrupted gracefully rather than blocking.
Streams vs Other Patterns
Section titled “Streams vs Other Patterns”| Stream | Script Pattern | Markov chain | choose_weighted() | |
|---|---|---|---|---|
| State memory | Yes — single value | Yes — multiple named fields | Yes — next value depends on current state | No — stateless selection |
| Control mechanism | Callback with prev | Class with query() method | Transition matrix | Weight array |
| Deterministic | Yes (with seeded rand()) | Yes (O(N) replay) | Yes | Yes |
| Seekable | Yes (O(N) replay) | Yes (O(N) replay) | Yes (O(N) replay) | Yes |
| Best use case | Accumulating or evolving a single value | Complex multi-field stateful logic | Probabilistic sequences with directed flow | Weighted one-off picks |
Musical Examples
Section titled “Musical Examples”Melodic Random Walk
Section titled “Melodic Random Walk”A bounded random walk mapped to a pentatonic scale:
let melody_track = MidiTrack(1);
let pentatonic = #[C4, D4, E4, G4, A4, C5, D5, E5];
let walk = stream(3, fn(prev, cycle) { let step = choose(cycle, #[-2, -1, 1, 2]); let next = prev + step; if (next >= 8) { return prev - 1; } if (next < 0) { return prev + 1; } return next;});
let mel = walk.map(fn(idx) { return pentatonic[idx];}).fast(2);
melody_track << mel;PLAY;Evolving Chord Voicing
Section titled “Evolving Chord Voicing”An array-state stream that shifts chord voicings, layered with an Euclidean rhythm:
let chord_track = MidiTrack(1);let rhythm_track = MidiTrack(2);
let voicing = stream(#[C4, E4, G4, B4], fn(prev, cycle) { if (cycle % 4 == 0) { return #[C4, E4, G4, B4]; // Reset every 4 cycles } let shift = choose(cycle, #[1, 2, 3]); return prev.transpose(shift);});
let kick = euclid(5, 8, [C2]);
chord_track << voicing;rhythm_track << kick;PLAY;Layered Stream Texture
Section titled “Layered Stream Texture”A fast melody stream and a slow bass stream on separate tracks:
let melody_track = MidiTrack(1);let bass_track = MidiTrack(2);
// Fast melody — random walk at double speedlet mel_steps = #[-2, -1, 1, 2, 3];let mel = stream(E5, fn(prev, cycle) { let step = choose(cycle, mel_steps); let next = prev.transpose(step); if (next > B5) { return prev.transpose(-3); } if (next < C4) { return prev.transpose(3); } return next;}).fast(2);
// Slow bass — stepwise walk at half speedlet bass = stream(C3, fn(prev, cycle) { let step = choose(cycle * 7, #[-1, 0, 1]); return prev.transpose(step);}).slow(2);
melody_track << mel;bass_track << bass;PLAY;See Also
Section titled “See Also”- Markov Chains — Matrix-based stateful patterns
- Script Patterns — Class-based patterns with multiple state fields
- Pattern Methods —
.map(),.describe(), and other transformations