· apps · soundbox · music · audio · gato

Soundbox: visual graph audio in the browser, no login

Soundbox in gato: graph + Web Audio in TypeScript — open these repo paths beside the post so every claim maps to real code.

Soundbox in-app view: grid canvas with Kick and Bass nodes, red edge, 1/4 timing chip, palette with Start Kick Snare Hat Tone Bass 808 Rad, and dark mixer panels
Capture from dev (April 2026); same UI at gato.to/soundbox. Hero image is ~23 KB WebP + ~38 KB JPEG fallback at 960px wide.

Nodes on a canvas, pulses down the wires — the beat is the diagram.


How to read this (so it is not bland marketing): run the marketing site locally (cd marketing && npm run dev), open this post, and keep soundbox/ in your editor. Every section below points at real files; if a sentence does not trace to code, treat it as opinion.

Start here in the repo: soundbox/src/main.tssoundbox/src/app/bootstrap.tssoundbox/src/models/graph.ts.

The hero image above is the real in-app canvas: default graph transport → Kick → Bass, edge timing 1/4+2, palette dock, floating mini-mixers.


Soundbox is gato’s visual audio creation tool: you place nodes (transport, drums, tone/bass, TR-808-style grids, “Rad” melodic grids), connect them with edges, and hit play. It runs in the browser, is served from the main site at /soundbox/, and is built as its own TypeScript + Vite app under soundbox/ in the repo — not a React “Vault” app, but a first-class sibling product on the same platform.

There is no login. Persistence is literally two string keys in StorageService:

// soundbox/src/services/StorageService.ts — lines 9–10
const STORAGE_KEY = "soundbox.project.v1";
const BEATS_KEY = "soundbox.beats.v1";

loadProject() / saveProject() round-trip JSON; loadBeats() / saveBeat() store named snapshots of a Project — see SavedBeat in the same file.


Node vocabulary (one line from the source of truth)

Allowed node kinds are a closed union — not marketing labels, TypeScript:

// soundbox/src/models/graph.ts — line 1
export type NodeType = "transport" | "kick" | "snare" | "hat" | "tone" | "bass" | "tr808" | "rad";

If your UI shows something else, it was migrated out; StorageService still filters unknown type values on load.


Bootstrap: the real wiring diagram

bootstrap is a plain function that instantiates services in dependency order, wires preset resolvers into the audio engine + exporter, and debounces saves to 120 ms:

// soundbox/src/app/bootstrap.ts — lines 21–54 (excerpt)
  const paletteService = new PaletteService();
  const storageService = new StorageService();
  const toneService = new ToneService();
  const exportService = new ExportService();
  const initialProject = storageService.loadProject() ?? paletteService.createDefaultProject();

  const graphService = new GraphService(initialProject);
  const transportService = new TransportService(initialProject.bpm);
  const audioEngineService = new AudioEngineService();
  const renderSceneService = new RenderSceneService(stage);
  const pulseFlowService = new PulseFlowService(graphService, transportService);
  // …
  audioEngineService.setPresetResolver((id) => toneService.getPreset(id));
  audioEngineService.setDefaultPresetId(toneService.getActivePresetId());
  // …
  const scheduleSave = (): void => {
    if (saveTimeoutId !== null) {
      window.clearTimeout(saveTimeoutId);
    }
    saveTimeoutId = window.setTimeout(() => {
      saveProject();
      saveTimeoutId = null;
    }, 120);
  };

That is the “app shell” — no framework magic, just constructors.


/soundbox/ is not hand-wavy routing

The Vite app is built with base: '/soundbox/' so asset URLs resolve when the bundle is copied under the main gato site:

// soundbox/vite.config.js — lines 3–6
// Served from the main gato site at /soundbox/ (dev proxy + production copy into dist/soundbox)
export default defineConfig({
  base: '/soundbox/',

Repo glue: scripts/ensure-soundbox-dist.mjs, scripts/copy-soundbox-to-dist.mjs, and root package.json build chain copy soundbox/distdist/soundbox.


What you actually do in Soundbox

  1. Open Soundbox on gato (or /soundbox/ on your build).
  2. Drag node types from the palette onto the stage (kick, snare, hat, tone, bass, TR-808, Rad, etc. — see NodeType in soundbox/src/models/graph.ts).
  3. Connect nodes so pulses flow from the transport along edges; PulseFlowService and edgeTiming decide when downstream nodes fire.
  4. Press play — the overlay calls audioEngineService.unlock() then TransportService toggles; AudioEngineService turns triggers into Web Audio hits (kicks, snares, hats, tonal chords from presets, TR-808 lanes, Rad sequences).
  5. Tweak BPM, pro mode, tone presets (save named presets in ToneService), and optional export paths wired through ExportService.

The scene is a Three.js orthographic canvas (RenderSceneService): nodes are sprites, edges and animated pulses are rendered in WebGL, and pointer interaction goes through InteractionService so the graph feels like a tactile instrument.

Here is a richer real UI snapshot — same app, but with every drum/tone inspector expanded and an 808 Rhythm Composer visible, so you can see the full surface area rather than just the clean hero graph:

Soundbox running: top toolbar with Pause, BPM 112, Select, Connect, Delete, Reset, Pro, dupe length 1/4, and Auto connect dupe; canvas showing Kick and Bass nodes connected to five inspector panels (Kick, Snare, Hat, Bass, Tone) plus a TR-808 Rhythm Composer panel; palette strip at the bottom (Start, Kick, Snare, Hat, Tone, Bass, 808, Rad); help text lower-left; Beats button lower-right
Full UI — inspectors for each drum/tone node, TR-808 Rhythm Composer, and the palette at the bottom. ~15 KB WebP + ~31 KB JPEG fallback at 1200px wide.

How the pieces fit (high level)

PieceRole
PaletteServiceDefault projects and palette items (starting graphs).
GraphServiceSource of truth for nodes, edges, BPM, pro mode; subscribe for reactive updates.
TransportServiceClock: BPM, play/stop, beat index; notifies PulseFlowService.
PulseFlowServiceTurns transport beats into pulses and trigger events along the graph.
AudioEngineServiceAudioContext, synthesis, TR-808 / Rad sequencing, preset preview.
RenderSceneServiceThree.js rendering and hit-testing for the graph.
StorageServicelocalStorage load/save for Project + saved beats.

Bootstrapping wires this together in soundbox/src/app/bootstrap.ts: create services, load or create a project, subscribe the renderer to graph changes, schedule debounced saves (~120 ms after edits).


Try it

  • Live: gato.to/soundbox
  • Local (main gato dev): after npm run dev from repo root, open /soundbox/ on that origin — vite.config.js serves the built soundbox/dist tree when present (soundboxDevStatic plugin).
  • Local (marketing blog only): cd marketing && npm run devSoundbox posts index (this series: visual / pulses / Web Audio).

If soundbox/dist is missing, run npm run build --prefix soundbox (or let predev run ensure-soundbox-dist).

Play it here (live embed)

The iframe below is the actual app on this domain (/soundbox/). First click unlocks audio; after that you can drop palette nodes, connect edges, and hit play. Lazy-loaded so the hero stays the priority asset.

Live embed of /soundbox/. On staging/production this is the same build the standalone page serves.

Sources: soundbox/src/app/bootstrap.ts, soundbox/src/models/graph.ts, soundbox/src/services/StorageService.ts, soundbox/src/services/RenderSceneService.ts, marketing/src/pages/apps/music.astro.