Skip to content

Generate a Progression with a Markov Chain

markov(states, matrix, start) moves between numbered states. Row i of the matrix holds the chances of going from state i to each state, and each row sums to 1. The chain takes the same path on every playback.

The chain outputs state indices, so map them to notes or chords. A small script pattern does that:

use "std/instruments" { SamplerMelodic, Kit };
use "std/generative" { markov };
class Mapped {
let chain;
let notes;
fn new(chain, notes) {
this.chain = chain;
this.notes = notes;
}
fn query(cycle) {
return this.notes[this.chain.query(cycle)];
}
}
let three_state = markov(
3,
#[
#[0.5, 0.5, 0.0],
#[0.0, 0.5, 0.5],
#[0.5, 0.0, 0.5]
],
0
);
let lead = AudioTrack("lead");
lead.set_instrument(SamplerMelodic(Kit("keys")));
lead << Mapped(three_state, #[C4, E4, G4]);
PLAY;

Set its weight to zero. In the chain above, state 0 never jumps straight to state 2, and state 2 never reaches state 1. Zeros encode the rules of your harmony — for example, to stop a V chord from going directly to a ii.

Don’t make a whole row zero unless you want the chain to stay stuck in that state.

Give the home state a row that points only to itself. Once the chain reaches it, it stays:

use "std/generative" { markov };
let resolving = markov(
3,
#[
#[0.0, 0.7, 0.3], // start: mostly move toward state 1
#[0.0, 0.3, 0.7], // middle: mostly move toward state 2
#[0.0, 0.0, 1.0] // end: stay
],
0
);
print(resolving.query(10)); // settled on state 2