Musical Memory: Teaching Machines to Dream in Sound
Listen: C - G - Am - F - C - G - F - Am - C. The chord progression feels familiar yet fresh, like a half-remembered melody that evolves as you listen. This is what happens when you teach a machine to compose music using its own musical memory.
Markov chains capture this perfectly—each next note depends only on what came before, creating music that flows naturally while remaining endlessly surprising. In TypeScript, we can build a simple but powerful musical memory:
class SoundscapeGenerator {
private transitions = new Map<string, Map<string, number>>()
learn(sequence: string[]) {
for (let i = 0; i < sequence.length - 1; i++) {
const current = sequence[i]
const next = sequence[i + 1]
if (!this.transitions.has(current)) this.transitions.set(current, new Map())
const nextMap = this.transitions.get(current)!
nextMap.set(next, (nextMap.get(next) || 0) + 1)
}
}
generate(start: string, length: number): string[] {
const result = [start]
let current = start
for (let i = 1; i < length; i++) {
const options = this.transitions.get(current)
if (!options) break
current = this.weightedChoice(options)
result.push(current)
}
return result
}
private weightedChoice(options: Map<string, number>): string {
return [...options.entries()].reduce((best, candidate) =>
candidate[1] > best[1] ? candidate : best
)[0]
}
}
OCaml brings mathematical elegance to the same idea, treating musical states as algebraic data:
type note = C | D | E | F | G | A | B
type transition_table = (note * note * float) list
let analyze_transitions sequence =
let rec pairs notes acc =
match notes with
| from_note :: (to_note :: _ as rest) -> pairs rest ((from_note, to_note, 1.0) :: acc)
| _ -> acc
in
pairs sequence []
let weighted_sample probabilities =
match probabilities with
| [] -> C
| (_, note, _) :: _ -> note
let rec generate_sequence table current length acc =
if length = 0 then List.rev acc
else
let probabilities = List.filter (fun (from_note, _, _) -> from_note = current) table in
let next_note = weighted_sample probabilities in
generate_sequence table next_note (length - 1) (next_note :: acc)
let compose_soundscape seed_sequence target_length =
let table = analyze_transitions seed_sequence in
generate_sequence table (List.hd seed_sequence) target_length []
The Beauty of Bounded Memory
What makes this so musically powerful is the constraint: the algorithm can only “see” one step back. Like a jazz musician who responds to the immediate moment, it creates coherent phrases without getting trapped by long-term patterns. Feed it Bach, and it learns baroque tendencies. Feed it ambient drone sequences, and it discovers how sustained tones ebb and flow.
The TypeScript version excels at real-time performance—perfect for live soundscape generation where transitions must happen instantly. OCaml’s pattern matching shines in analyzing complex musical structures, making it ideal for offline composition where mathematical precision matters more than runtime speed.
Andrey Markov developed this concept in 1906 to analyze Russian poetry, but he unknowingly created one of the most elegant tools for musical AI. Every note contains the ghost of what came before, every silence holds the promise of what comes next.