A GAN with words. A generator plays an unheard take; a critic measures it against a sixteen-feature ruler; the argument converges in golden ratio — until the critic can no longer tell whether this is a song he simply hasn't heard before.
Grow the musician. The song is the receipt.
This is the public instrument of the duke-lab doctrine
(AI-Writings/philosophy/, 2026-08-25) and the Plainsong technology stack
(SuperInstance/plainsong): plain-text notation
that compiles to MIDI and embeds in markdown like mermaid.
Zero build. Open index.html from file:// or serve the folder:
python3 -m http.server 8923
# → http://127.0.0.1:8923/index.htmlDeep links: ?autorun=1 runs the argument on load · ?seed=caravan/9 picks the dice ·
?round=4 jumps to a round after the run.
The page, before and after an argument (screenshots are committed, regenerated by the QA harness):
![]() |
![]() |
| At rest — sixteen hands, waiting; the Eye blank at distance 1. | Converged in 6 rounds — take notation, radar at σ 0.115, referee's log full. |
The same converged run on a phone-width viewport — the studio stacks; nothing is hidden.
- Run the argument — pick a canon (Duke / Evans / Monk), pick a gardener (Purist / Engineer / Romantic / Historian — the loss function is swappable mid-run, per the journal), press Run. Eight rounds play out live: notation diffs glow brass, the radar tracks take vs. reachable canon, σ shrinks along a golden-section grid.
- Listen — every take is synthesized in-browser (WebAudio): melody, walking bass, voicing ghosts, brushes on 2 & 4 with engine swing. Piano-roll playhead included.
- Lean in — type what you want to hear ("moodier, more space, swing harder, angular…"). The ask becomes 16-feature deltas for the next round.
- Blind test — two 4-bar excerpts, guess which is the later round. If you can't, the critic can't either. That is the whole point.
- Export —
.song(Plainsong, compilable offline:plainsong compile take.song),.mid(built in-browser, no dependencies), copy to clipboard. - Verify the honesty — same seed, same argument, byte for byte. The seed is on screen. Re-roll and come back; nothing about the run is a performance.
- Average the listen — σ is a measurement, not a mood: the listens knob re-synthesizes the take N times (1–64) and averages. Averaging kills jitter, not bias — below the floor, more listens change nothing, and the page says so.
Deterministic, dependency-free, dual-export (browser window.DukeLab / Node module.exports).
FNV-1a → mulberry32 seeded PRNG. One honest dice chain, no hidden entropy.
| Concept | Mechanic | Doctrine |
|---|---|---|
| 16-feature ruler | registerSpread … cadenceRegular, each with floor + critic hint |
the fakebook theorem: judge the trace, not the summary |
| Generator | param vector → seeded take (melody/bass/comp events) | grow the musician |
| Critic | weighted σ-distance vs. effective centroid (4-take calibration at the canon, cached) | the residue is the style |
| Revision | golden-section critique budget max(1, round(3·φ⁻ʳ)), attacked axes stepped toward target, all axes drift home |
the golden residue: 1/φ ≈ 0.618 |
| Verdict | EMA σ < threshold → CONVERGED ("I can no longer tell…") · else HONEST GAP with residue named |
ends in ratio, never in fact |
| Medium floor | plainsong's one-velocity-per-row law caps expressible axes → measured residue is quantitative | the journal's honest-gap page |
| Ask | parseAsk lexicon → feature deltas (nudge, not teleport) |
the operator's voice enters the loop |
Tests: node tests/engine.test.js — determinism, convergence behavior across 12 seeds
× 3 artists × 4 personas, plainsong contract, MIDI byte format, lexicon, personas,
floor law. 28 checks, all green.
The site is fully honest on local seeded dice — that is the design, not a fallback. Point it at a Worker and the same UI upgrades to LIVE FLEET MODE (set the endpoint in the footer, it's remembered in localStorage):
| Route | What it does |
|---|---|
GET /health |
mode-badge handshake |
POST /api/ask |
free-text desire → 16-feature deltas via LLM (OPENAI_API_KEY); degrades to the local lexicon, never a 502 |
POST /api/round |
critic prose, LLM-polished in persona voice |
GET /api/targets |
artist centroids — static today; bind Vectorize for a corpus-derived canon |
POST /api/compile |
stretch: pipe .song through real plainsong → MIDI bytes |
Deploy (one command):
mkdir -p worker/public && cp index.html app.js engine.js worker/public/
cd worker && npx wrangler deploy
npx wrangler secret put OPENAI_API_KEY # optional, enables the fleet earGitHub Pages (instant): repo settings → Pages → serve main root. The site works from
file:// too — no server, no build, no tracking.
The instrument is also a musician foundry. Every musician is a point in [0,1]¹⁶ — their 16-feature centroid — and the fleet learns from every argument anyone runs.
- Design a musician — describe one in words; the fleet ear (or the honest local lexicon) turns the description into a measurable centroid. They register into the same canon as Ellington and Monk — the machine doesn't special-case them. That is the point of designing in the open.
- Vibe-code the judge — not presets: a loss function in words. What the judge punishes becomes weights over the 16 axes; the argument answers to them, by name, in the journal.
- The bandstand — once a musician has matured (argument finished, params evolved), sit them in with another. They trade 8-bar phrases; the responder answers with the previous phrase's own cadence three times out of four. The banter score (quotes ÷ responses) is fitness, and it goes in the ledger.
- Every generation helps — each finished run POSTs its matured params to
/api/learn, which graduates them into musician-space: a D1 row, a 16-dim native Vectorize embedding (the mathematical musician-space), and a 768-dim semantic embedding of the description./api/musicians/similaranswers "who plays like this one" — ghost suggestions for cross-breeding. - Liberal limits — 45 requests/min/IP on purpose: most any GAN work teaches the fleet something. Visitors can opt out of the ledger with one checkbox; the site says so on the page.
cd worker
npx wrangler d1 create duke-lab-db # paste the id into wrangler.toml
npx wrangler vectorize create musicians-native --dimensions=16 --metric=cosine
npx wrangler vectorize create musicians-semantic --dimensions=768 --metric=cosine
npx wrangler d1 execute duke-lab-db --file=schema.sql --remote
npx wrangler secret put OPENAI_API_KEY
npx wrangler deployNone of it is required for the page to work — every endpoint degrades honestly (local lexicon, D1-less ledger) rather than 502.
- Same seed → same argument, byte for byte. Seed displayed on screen.
- LOCAL SEED MODE is not a demo mode. The worker is an upgrade, never a requirement.
- When the worker is unreachable, the UI says so and local logic stands in. No silent stubs.
HONEST GAPis a first-class verdict. The residue axes are named, not hidden.- The medium's floor (plainsong's one-velocity law) is presented as a measured quantity
(
mediumResidue(artist)), not an excuse.
Reid Miles' Blue Note sleeves · Dieter Rams · the duke-lab journal, 2026-08-25 · Castro & Liskov on consensus (the referee is a quorum of one, and still outvoted by the music).
DUKE LAB · a SuperInstance instrument · the song is the receipt · the residue is the style

