Describe a sound. Get a song you can open.
You type a sentence. A frontier model writes it as Strudel live-coding patterns, layer by layer — kick, bass, the voice that answers the bass — and hands you the code, not a bounced MP3. Play it, rearrange it, extend it, sing over it, take the lid off and type into it while it plays, go live and let a room hear it. Nothing under your music is sealed.
Nothing here asks a model for a song and waits. A loop is built the way a person builds one — one layer at a time, each layer written by its own call that can see everything already playing underneath it.
Your sentence becomes a brief: key, tempo, feel, what this section is for. Then the loop grows. Kick. The bass that answers the kick. The voice that answers the bass. After each layer the house asks one cheap question — is this finished, or does it want one more? — and below eight parts it refuses to take yes for an answer. Sixteen is the ceiling; most loops stop themselves before it.
Every layer comes back as Strudel source, never audio. It's parsed and evaluated server-side before you hear a note; if it would throw, the error goes back to the model once and the layer is rewritten. Nothing else touches it — no cleanup pass, no regex patching the model's output into shape. A prompt that produces bad code is a prompt bug, and it gets fixed in the prompt.
Editing is the same machine pointed backwards. "Make the bass drunker" is one call that returns the whole revised loop; the lines that come back byte-identical keep their layer, their mute, their volume. And the arrangement — drag, delete, insert, extend — never calls a model at all. Moving a loop is not a question anyone needs answered.
One file decides who writes what. lib/llm.ts holds a table
with one row per AI call in the whole product — invent a layer, re-bar a loop,
repair a crash, name a stem — and each row pins its model, how hard that call
thinks, and its token ceiling. Inventing music gets the strongest hand and the
longest leash. Naming a preset gets a cheap one with thinking switched off. Call
sites choose nothing; if a decision isn't in that table, it doesn't exist. The
one knob you get is per song and reads like what it does — Standard, or Studio
for a stronger hand on the loops at roughly twice the tokens
(lib/models.ts). It is not a model picker; naming engines at
people was never the product.
And you can read every word we say to it. The system prompts are strings in this
repo (lib/compose-prompts.ts is where the music
ones live), short on purpose and free of nannying. Your own words stop at the
door: a request naming an artist is translated into pure musical terms before
anything downstream sees it, and never echoed forward.
Klappn is the studio. Under it are two engines we wrote ourselves, and they are siblings on purpose:
| Klappn | the studio | you describe, it composes — hits, loops, sets, events |
| zaltz | the sound | one file of C on the audio thread (engine/zaltz.c) |
| zissl | the light | one file of WGSL on the GPU (zissl) |
The names are a Yiddish pair: zaltz un tsuker — salt and sugar. zaltz is
the salt: the low end, the thing you feel in your chest. zissl is the sweet one:
the picture it throws on the wall. You'll also see them shouted — ZALTZ,
ZISSL-FIRST, THE SALT SHAKER — but that's only ever at the top of a source
file, where every subsystem in this codebase announces itself in caps. In prose
they stay lowercase, because they're words before they're products. Salt is not
a brand.
They were built the same way, twice. Both keep a beloved live-coding language and replace the machine underneath it: zaltz keeps Strudel's vocabulary and rebuilds superdough as freestanding C inside an AudioWorklet, where your UI thread physically cannot reach it — the page can stutter, sleep, or die mid-animation, the music does not. zissl keeps Hydra's vocabulary and rebuilds it as WGSL on WebGPU, which buys the thing WebGL never had: compute — a million agents sensing the picture and drawing filaments back over it. Both were held to a golden gate against the engine they replaced, and neither shipped until the gate went green and the ear agreed.
And they run on one clock. Klappn's visuals take all their motion from
H(signal) — the bridge that samples a continuous signal on the transport
clock, in cycles, not on the wall clock. The picture doesn't chase the sound;
they're both reading the same bar position. That's why a live set can stream
audio alone: every listener's phone renders the visuals natively from a few
lines of text, in lockstep, at full GPU quality. A picture that's generated,
not transmitted.
Three doors into the same machine:
- klappn.com — the studio. Say what you want to hear.
- klappn.com/engine — the engine room: the same machine with the lid off, your hits underneath, live code on top, sound the moment it lands.
- zaltz.klappn.com · zissl.klappn.com — the engines' own front doors. Press a patch; the waveform is the C talking. Type a chain; the room lights up.
Both engines are separately released and separately usable — npm i zaltz,
zaltz ·
zissl. Take one, leave the studio.
That's the point of the license.
The whole machine, face up on the table. Three promises, kept here and on the
app's /open page:
- The code is open. Every prompt that talks to the model, the audio engine byte by byte, this file — all AGPL-3.0, Strudel's license. Read what happens under your music. Run it, fork it, steer it.
- The price is readable. The whole price sheet is one screen of
lib/pricing.ts— a price here is a line of open code, so any change is a commit with our name on it, never a surprise on a bill. The instrument is free; only the machine's composing is prepaid. Self-hosting? Your key, your bill, our code. - The data deal is in the open. Hosted generations and edits will raise Klappn's own music model — a model that stays inside this project, so the tool stops renting anyone else's brain. Consented in plain language, opt-out is one message, past included; self-hosted instances send us nothing. Said now, so nobody finds it out later.
We'd rather earn a community than rent customers. Every sentence above is a promise — if the code ever contradicts one, that's a bug. File it.
Discord: link coming — it lives in lib/links.ts, and the
UI lights up the moment it's filled in.
- Vinext — Cloudflare's Vite-based Next.js replacement (App Router), deployed to Cloudflare Workers.
- PlanetScale Postgres via a Hyperdrive binding (porsager
postgresdriver). - Better Auth — email OTP sign-in (the 6-digit code prints to the dev server console when no email provider is configured).
- Cloudflare Workflows for durable background generation + editing.
- Strudel (
@strudel/web) patterns, played 100% in-browser by zaltz (engine/zaltz.c; lineage: SuperDirt → superdough → zaltz, seeNOTICE.md). superdough stays as a fallback:?engine=superdough. - zissl for visuals, with
hydra-synthas the fallback for browsers without WebGPU, so nobody loses the picture (?zissl=0forces it). - Anthropic's Claude, server-side only, called per-agent from the table in
lib/llm.ts— a stronger model where music is invented, a cheaper one where a label is named. Bring your own API key when self-hosting: your key, your bill, our code. - Stripe for token top-ups and event tickets (optional — the app runs without it).
Node ≥ 22.15 required (vinext and wrangler both need it; Node 24 works).
This repo is the engine's source of truth: engine/zaltz.c and its golden
tests live here, and the standalone zaltz package vendors from them. Engine
patches land here first.
Browser (vinext client + zaltz/zissl/Strudel) ──POST create/edit──▶ Route handlers (Workers)
▲ │ poll GET /api/songs/:id │ auth + ownership
│ └─────────────────────────────────────────────────────────── │ DB via postgres driver
│ │ trigger Workflow (REST API)
└──── parts render as status flips to "ready" ◀──── Workflows worker (durable)
└─ model calls + DB writes
The Workflow classes live in a separate Worker (./workflows); the app
triggers them through the Cloudflare Workflows REST API
(lib/workflows.ts). In local dev (no Cloudflare creds) the
same job core (lib/jobs.ts) runs in-process, so the whole flow
works without deploying.
The picture engine is a sibling checkout ("zissl": "file:../zissl"), so clone
it next to this repo before installing:
git clone https://github.com/eliyahuleinkram/zissl.git
git clone https://github.com/eliyahuleinkram/klappn.git
cd klappn
- Install (Node ≥ 22.15):
npm install - Bring up Postgres and create a database (
createdb klappn). - Copy env:
cp .env.example .envand fill in at leastDATABASE_URLandBETTER_AUTH_SECRET; addANTHROPIC_API_KEYto enable generation (your key, your cost, your account). - Migrate Better Auth's tables, then apply our schema. npm scripts don't read
.env, so load it into the shell first:set -a; . ./.env; set +a npm run auth:migrate npm run db:schema - Run the dev server:
npm run dev→ http://localhost:3001- Sign in with any email; the OTP code prints to the server console.
npm run verify runs the lot: lint, types, unit tests, the engine's golden
gates, and the sample-parity audit.
Self-hosted instances talk only to the providers you configure — no telemetry, no data back to us.
wrangler login- Create Hyperdrive over your Postgres:
wrangler hyperdrive create klappn-pg --connection-string="postgres://..."and paste the id intowrangler.jsonc+workflows/wrangler.jsonc. - Deploy the Workflows worker:
cd workflows wrangler secret put ANTHROPIC_API_KEY wrangler deploy --config wrangler.jsonc - Set the app's secrets, then
npm run deploy(vinext build + wrangler — never barewrangler deploy, it ships stale assets). See.env.examplefor the full list; the core set:wrangler secret put BETTER_AUTH_SECRET wrangler secret put BETTER_AUTH_URL wrangler secret put ANTHROPIC_API_KEY wrangler secret put CLOUDFLARE_ACCOUNT_ID wrangler secret put CLOUDFLARE_API_TOKEN wrangler secret put EMAIL_FROM # optional: STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET (token top-ups)
See CONTRIBUTING.md. Two house rules worth knowing before
your first PR: the ear is the acceptance test for anything that touches
sound, and arrangement operations never call AI.
AGPL-3.0 — see LICENSE. Third-party credits in
NOTICE.md. Same license family as Strudel: if you host a modified
Klappn, your users are entitled to your source, the same way you're entitled to
ours.