Pretzel is a live browser-based sequencer with a Talon-powered co-producer.
The browser renders the stage, Cloudflare Durable Objects hold the authoritative playback state, and Talon drives changes through a narrow MCP contract.
- Renders a multi-scene step sequencer in the browser
- Plays audio directly from
PlaybackState -> Strudel code -> @strudel/webaudio - Exposes a small MCP surface so an agent can inspect and mutate the arrangement
- Uses a single global Durable Object as the realtime source of truth
src/- React + Vite
- browser stage/controller UI
- audio runtime in
src/audio/StrudelAudioEngine.ts
cloudflare/index.ts: top-level request routerroutes/: HTTP/WebSocket/Talon/MCP entrypointsdurable-object/: global state class + socket/control protocolmcp/: MCP server/tool registrationstate/: playback normalization and exact mutations
- Talon session auth minted at
/talon/session-token - MCP endpoint at
/mcp - exact mutation contract only; no whole-state overwrite tools
Current supported tools:
get_playback_state()list_tracks({ sceneId?: string })get_track({ trackId: string, sceneId?: string })set_scene_order({ sceneOrder: string[] })set_active_scene({ sceneId: string })set_track_steps({ sceneId: string, trackId: string, bars: number[][] })put_track({ sceneId: string, trackId: string, trackName: string, soundCode: string, bars: number[][], volume?: number, isActive?: boolean })remove_track({ trackId: string, sceneId?: string })
Rules:
trackIdis authoritative identitytrackNameis display-onlyput_trackis the only create/replace pathremove_trackremoves by exacttrackId- the backend is the source of truth; the frontend should only render snapshots
Pretzel supports a constrained Strudel subset for grid tracks:
- samples:
kick,snare,hat,closed,ride,rim,tom - synth voices:
sine,triangle,square,sawtooth - supported single-voice chains such as:
note('e2').s('sawtooth').lpf(80).distort(0.4).slide(0.12)note('e4').s('sawtooth').room(0.4).env(0.01,0.2,0.8,0.4).gain(1)
Aliases are canonicalized server-side to runtime-safe forms.
Install dependencies:
npm installRun the frontend:
npm run devRun the Cloudflare worker locally:
npm run worker:devTypecheck:
npm run typecheckUnit and integration tests:
npm run testLive worker state validation:
npm run test:liveFull repo check:
npm run checknpm run worker:deployRequired Worker secrets:
TALON_MCP_TOKENGATEWAY_JWT_SECRET
- The current system uses one global DJ state, not rooms.
- The stage UI intentionally renders the global union of track rows across scenes.
- The backend stores sparse per-scene tracks; absent rows in a scene are rendered as silence.