← back to the live engine

How Agentic Soundtracks generates music, step by step

No samples, no licensed tracks, no AI audio model, no API. A game-building agent can call the same tiny contract - seed + vibe in, soundtrack out. Two systems share one idea: a song is just a small program. This is exactly what runs in Bandlings and in 170+ shipped games.

The 30-second version

1 · Your text becomes a number: the game's name is hashed into a 32-bit genome.
2 · Slices of that number pick the key, the chords, the melody shape and the tempo.
3 · A scheduler walks a 16-step grid and spawns one oscillator per note. No files, no samples, no AI model.
4 · In Bandlings the board IS the band: tier = instrument, merging a monster upgrades the song.

The long version, if anyone asks
Part 1 - the live engine (what you hear on the home page and in Bandlings)

01Identity becomes a seed

The engine hashes the game's identity (its title and path, or whatever you type into the box) with an FNV-1a-style hash into a 32-bit number. Same text in, same number out, forever - which is why the same seed always plays the same song, on any device.

var h = 2166136261;
for (var i = 0; i < key.length; i++) {
  h ^= key.charCodeAt(i);
  h = (h * 16777619) >>> 0;
}   // "Doom" -> one specific 32-bit genome

02The seed becomes a musical genome

Independent bit-slices of that one number make the musical decisions, so two games never make the same combination of choices:

root      = roots[(seed >>> 5)  % 4]   // key (each preset has 4 keys)
prog      = PROGS[(seed >>> 9)  % 8]   // chord progression pool
contour   = SHAPE[(seed >>> 23) % 6]   // melody shape pool
bpm       = base * (0.92 + slice(seed >>> 13) * 0.16)  // +/-8% tempo
startBar  = (seed >>> 17) % prog.length

Progressions are written as scale degrees (I-vi-IV-V, vi-IV-I-V, jazz turnaround, ...), so the same progression automatically colors major or minor depending on the preset's scale.

03A preset picks the instrument kit and mood

Six presets define scale (major/minor), oscillator waveforms, drums on/off, loudness and arp density: cozy, synth, arcade, calm, idle, tense. When you type a game name on the home page, a small agent maps keywords to a preset ("Doom" → tense: E-minor scale, sawtooth leads, square-wave bass, driving kick). The text itself is the seed, so describing the game IS composing its soundtrack.

04A lookahead scheduler walks the bar grid

One WebAudio AudioContext. A 30ms timer keeps about 250ms of audio scheduled ahead (the standard look-ahead pattern, immune to frame drops). Each bar is 16 sixteenth-steps:

step 0:  pad chord (triad stacked in-scale) + bass root + kick
step 8:  bass + kick again (beat 3)
every arp step: lead note walks the chord FOLLOWING THE CONTOUR,
                jumping an octave in the bar's second half
odd steps: hi-hat = 35ms burst of white noise through a 7kHz highpass

05Every note is an oscillator born on schedule

There are no audio files anywhere. Each note is an OscillatorNode → gain envelope (exponential attack/release) → optional lowpass → master gain, created at its scheduled time and destroyed when it ends. The kick is a sine that pitch-glides 130Hz→48Hz in 160ms. Open devtools, filter Network by media: zero requests.

06Bandlings: gameplay IS the arrangement

In Bandlings the seed is per-player and the orchestration is literally the board state, on one shared 8-bar loop:

tier 1 monsters  -> percussion (each layers its own rhythm mask)
tier 2           -> bass (same-tier monsters rotate bar by bar)
tier 3           -> chords / arpeggio
tier 4           -> lead melody (contour with rests)
tier 5           -> choir (sustained detuned pair, every 2 bars)

Hatch a monster: a new voice joins. Merge two: their lane upgrades. Lose the run: your song's earnings bank, the band persists. "Save my song" re-renders the exact same schedule into an OfflineAudioContext and downloads it as a 16-bit WAV - your collection, as a track.

Part 2 - the code-DAW (bespoke tracks for flagship games)

07Compose: oscillators with a music degree

A 500-line Python "code-DAW" (numpy/scipy) builds richer instruments than the browser engine: band-limited additive saws (harmonics summed below Nyquist - no aliasing), 7-voice detuned supersaws, an FM electric piano, and synthesized drums (pitch-glide kick, tone+noise snare, triple-burst clap, highpassed hats).

08Arrange: hand-written sections, seed-varied songs

Three hand-arranged styles (synthwave 116 BPM, swung lofi 76 BPM, ambient calm 64 BPM) with intro / breakdown / drop sections. The per-game seed transposes the key (-6..+5 semitones), nudges tempo +/-6%, and picks the chord progression and melody from pools - so two synthwave games are different songs, not the same song in a new key.

09Mix and master, still in code

voices -> sidechain duck (to the kick) -> ping-pong stereo delay
       -> convolution reverb (the impulse response is GENERATED:
          decaying filtered noise + 3 early reflections)
       -> soft-knee compression -> tanh limiter -> 44.1kHz WAV -> ffmpeg mp3

10Ship: bespoke first, engine as the safety net

The factory renders a bespoke track per game (gen_soundtrack.sh). In the game, GF.bgMusic() plays that file on the first tap and, if it is missing or fails to decode, falls back to the live engine from Part 1 - so no game can ever ship silent.