← back to the star map

STELLATE

A generative instrument you play by moving through a 178-genre vector space. This page walks the whole pipeline: how one point on the star map becomes a song, how the song refuses to loop, and how a machine proves every genre still sounds like itself.

Part one

HOW A SONG IS MADE — seven stages, always in this order, every random draw pulled from seeded streams so the same seed always renders the same music.

01The point becomes weights

app/targeting.js · weightsAt

dub grime bossa doom house ·38 disco ·31 funk ·19 boogie ·12 you are here
inverse-square blend over the ≤4 nearest anchors

The star map turns a point into a genre-weight blend: inverse-square distance over the ≤4 nearest anchors, plus a continuous snap-to-anchor pull scaled by how unambiguously nearest the closest star is — on a star ⇒ pure genre; between two ⇒ natural blend. Each genre neighborhood also gets its own key (name-hash mod 12), so crossing into a new dominant genre is a key change.

02resolveMulti — weights into choices

engine/genre-kernel.js · resolveMulti(weights, seed)

house ·38 kit: house four bpm 118–126 disco ·31 kit: full · shuffle bpm 104–118 mulberry32 (seed) the choice bpm 121 kit: house prog: disco coherent
blends combine, never smear

One mulberry32(seed) stream — mulberry32 is a tiny seeded random-number generator — with a fixed draw order. Scalars (bpm, swing, humanize, fx) blend by weighted lerp (a linear blend) of each parent's [lo,hi] range, then one sample. Pools (progression, kit, form, voicing) draw one parent proportional to weight, then pick inside that parent's pool — blends combine, never smear. Grouped dimensions draw ONE parent for the whole group so a recipe and its source pool always agree.

Energy-preserving pattern inheritance: for bass and lead patterns, a parent whose pattern-pool energy is ≥1.25 rungs above the runner-up and carries weight ≥.35 takes the draw outright — a dancey parent keeps its groove against a wash-heavy neighborhood. DX7 patches sharing an FM algorithm morph by lerping their ~144-dim param vectors. The MUSIC-MIND axes (adventure, color, voicing, reharm, pipes, rhythm complexity) draw dead last on the same stream.

Then constrain() keeps midpoints songs: the kit snaps to jungle/breaks/machine-four above 150 bpm, adventure is capped at .75, ≤2-chord progressions force reharm off, rhythm complexity is capped .4 above 165 bpm.

03The state — a song's blueprint

engine/genre-kernel.js · toState(choice, opts)

form graph "drop" — typed nodes, one interpreter intro ground build peak release cad. ×2×2 ×3×2×1 0:00 180 s ±10%
the duration solver lands cycle counts on the target — shape-preserving, never drops a section

toState applies the 8 macro sliders (acoustic, density, dust, space, bright, feel, energy, vocal — post-resolution, zero-rng, all-neutral = byte-identical no-op), then builds sections: a form graph walked by one generic interpreter, with a duration solver that lands cycle counts within ±10% of the 180 s target. Past 175 s unevolved, the 3-minute rule splits plateaus and evolves at boundaries — a key walk of +2/−3/+5, a kit or lead re-roll, or a one-time form pivot.

Sampled by default: the state ships with the SF2 sample library (105 sampled instruments, plus 114 DX7 patches), while signature synths — tb303, reese, wobble, acid, synclead, modeld, vocoder — stay pure synth. The absent-knob law holds: if a feature wasn't chosen, the blueprint simply doesn't carry that knob — and an absent knob cannot change the output (the byte-identity law).

04buildEvents — the one score brain

engine/csd-engine.js · buildEvents(state)

pads bass drums melody rubato one smooth monotonic beat-warp — every consumer inherits the same musical clock
the event fabric: four arrays, one clock

One function turns the state into every note. It runs in this exact order, each pass on its own seed stream:

  1. Theory / reharm — with reharm on, the named progression is only a skeleton: a T→S→D→T functional walk regenerates the chords, adventure gating diatonic → modal interchange → secondary dominants → chromatic mediants and tritone subs; minimal-motion voice-leading. stream seed+40961
  2. Sections, voice by voice — per cycle per chord: pads, bass, drums, melody, countermelody — plus found-sound beds, beat-synced break slicing, one-shot hits, glitched vox, rave stabs; transitions at every boundary (fills, hat rush, snare roll, stutter, dropout, the per-genre micro lick).
  3. Transform pass — a generic per-bar pass over the resolved pool (rev, ply, degrade, octflip, rest, rot, stutter); hooks left alone. stream seed+31337
  4. Feel — jux stereo divergence — the left and right channels play slightly different variations; groove/swing with per-voice push-pull; thunk (soft key-noise thumps); snare ping-pong throws; tb303 accent and slide. groove on seed+777
  5. Sample roles + perc lane — generalized sample placements (bed, slice, one-shot, opener, cadence, buried, response) and a percussion lane over the kit. stream seed+9091
  6. Rubato — ONE smooth monotonic beat-warp applied to all four arrays; every consumer inherits the same musical clock.
  7. Pipes — the resolved pipe chain (≤3 of 10: harmonize, echoCanon, strum, ghost, callResponse, densityArc, sweepArc, vibratoSwell, throwFx, octavePump) applied at the one true choke point; densityArc never drops a kick or snare. pipe i on seed+71000+i×97
  8. Snare-law, dead last — a kernel-wide mandate: no bar's snare or hat signature repeats three times, measured in one rolling window across the whole timeline; machine kits vary with machine moves, human kits with ghosts, drags and drunk displacement. stream seed+5150

05mapEvents — notes into knobs

engine/faust/state-engine.js · mapEvents

event freq · gain · decay cutoffMul ·8 vib ·02 NOTE_PARAMS whitelist cutoff vibrato gain
annotations become sliders only where the module exposes them

The event fabric becomes per-note Faust parameter sets: freq, gain and decay everywhere; the NOTE_PARAMS whitelist maps pipe annotations to real DSP sliders only where the module exposes them (cutoffMul ⇒ cutoff/tone, vib ⇒ vibrato) and silently drops the rest. Blue-note bends render only on sampler units; sampled drums emit one-shots with real tom repitch; found sounds get an auto-tune spec and a speech-rate cap for intelligibility.

06Faust WASM voices

engine/faust/state-engine.js · voiceUnits

SF2 sampler dx7_algN tb303 per-voice modules inserts dry · rev · del reverbColor 3-band comp fx bus out
the synth fleet, DX7 cartridges, and native PCM samplers, mixed on one bus

Per-voice modules are picked from the synth fleet, dx7_algN cartridges and native PCM samplers — the SF2 default — with per-voice insert chains, pool-level dry/reverb/delay/ping-pong sends, and an fx bus: zita or a per-genre reverbColor module, plus a 3-band master compressor. The synth fleet and the DX7 cartridges are Faust compiled to WebAssembly; the sampled instruments are native PCM zone playback — everything mixed on one bus, live in your browser.

07The live chord-bar scheduler

engine/faust/live.js · makeWalk

bar · serial 0 bar · serial 1 bar · serial 2 new voices equal-power crossfade reseed per bar: seed + serial×7919
one authoritative walk; each call builds exactly one chord bar

ONE authoritative walk shared by the live map and the offline journey renderer: each call builds exactly one chord bar — it polls the current target (retarget and glide just mutate what it reads), reseeds seed + serial×7919 per bar while instrument identity stays on the song seed, fills only on a section's last cycle, sweeps only on its first and last. Two output paths: the ring/worklet path (desktop — same voice topology ⇒ glide by feeding bars into the running stream; a topology change ⇒ bridge stream and equal-power crossfade) and the WAV-first path (mobile — rolling rendered WAV segments on a real audio element, pocket-proof).

Part two

HOW A SONG EVOLVES — nothing loops verbatim, and all of it is seeded.

+2 −3 +5 ghost snare cell mutation kit re-roll the whole band — samplers included — transposes on every key walk
per-cycle humanity below the line, structural evolution above it

Per-cycle humanity. Hats drop out 9% per hit; drum levels breathe (25% of hits ±15%); a quiet ghost snare lands on an off-slot in 40% of chord bars; later cycles of a section get busier; bass notes octave-pop 5%, push late 6%, rest 5%. With the rhythm knob: per-cycle bass-cell mutation (drop, anticipate, octave-flip — hard cap 2 per cycle) and melody rhythm cells that snap phrases onto one of three rhythm grids (dotted pairs, tresillo, 3-3-2), pitch material untouched. The snare-law guarantees no snare or hat bar three-peats anywhere.

Section form. 7 form graphs (dj, drop, wave, pop generative; ritual, anthem, transit hand-authored and video-locked) of typed nodes — ground, build, peak, release, exposed, cadence — walked by one interpreter. Past 175 s unevolved the track MUST evolve: modulate (a cumulative +2/−3/+5 semitone key walk — the whole band, samplers included, transposes), re-roll a fresh kit and lead from the anchor's own pools, or form-pivot, once per track and never into wave.

Glide and travel. While LIVE, moving the cursor queues a smooth glide — the continuous morph plus discrete voice and section flips — through the same per-bar path; the traveler walks a drawn path leg by leg, retargeting every bar. Blends interpolate every dimension including the mind axes.

Journeys. Waypoints are genre names or drawn points; weight vectors lerp leg by leg. Per track, the key walks the circle of fifths (+7 or +5 semitones mod 12), and a novelty memory remembers the last 2 tracks' kit/progression/bass/lead/found signature — a candidate matching ≥3 of 5 fields is rerolled, up to 6 attempts. Rendered journeys are DJ-mixed at the PCM level: downbeat-aligned seams, beatmatch when the bpm gap ≤8, a 2-bar crossfade when ≤40, a hard cut beyond that.

Part three

THE VERIFIER LOOP — how a machine keeps 178 genres honest.

rendered as → scored against ↓ · 178 × 178 178/178 diagonal-dominant · seeds 1, 2, 3
the genre-confusion matrix: the diagonal is the law

Every render is measured on 23 symbolic features — bpm, off-grid feel, snare balance, hat and drum density, variation, wash, sub, motion, sevenths, break/chop/bed use, crackle, pump, compression, swing, humanize, acoustic, rubato, lead voices, soft top, interlock — and scored against 178 per-genre target rows of [lo, hi, weight] fences. The matrix renders every anchor at seeds 1, 2, 3 and requires 178/178 diagonal dominance: every genre scores highest as itself. Run after every change.

The two standing laws

1 · Determinism. Same seed ⇒ byte-identical events; an absent knob ⇒ byte-identical output (zero rng draws). Every subsystem draws from its own seeded stream, so new dimensions draw dead last and untouched genres press byte-identically.

2 · The matrix. 178/178 diagonal-dominant after every change. If a tweak makes house stop sounding most like house, the change does not land.