🎵 Web Audio Studio
A browser-based synth-based music studio. Compose looping tracks the way you
would in a small studio (Pro Tools–style stacked lanes) — everything is
synthesised live with the Web Audio API, so there are no audio files and no
dependencies. It's a single self-contained index.html plus a small song-data
module.
It was extracted from the Frog vs Toad
game, whose soundtrack ships here as the Froggy Hop example song.
The editor, with Rust Foundry loaded. Each track's inserts are lettered
chips in its header; the knobs live in the inspector column on the right.
Features
Composing
- Stacked tracks with a shared timeline and playhead
- Add / rename / remove / reorder tonal tracks, plus one or more rhythm
tracks sharing a fixed 10-piece palette (kick, snare, rim, hi-hat, open hat,
shaker, tom, clap, crash, ride)
- Duplicate a track — the part and the whole voice, copied below it. "Try
this line on another instrument" is a two-second move rather than a selection,
a new track and a paste. It lives with Remove track in the Track box at
the bottom of the inspector column, and both ask before acting: next to
Mute/Solo/arm in the track header they were two small unlabelled buttons away
from three you press constantly
- Clips — a track's part lives in windows onto its notes rather than in a
flat list, so trimming an edge hides material instead of deleting it and
pulling the edge back out returns the same notes. Split at the playhead, heal
two halves back together, or drag a clip along the timeline with everything it
holds. Every track starts as one clip covering the song, labelled with the bars
it spans; its grip and trim edges appear under the Grab tool, the same rule
a note's own resize handle follows. Each block is washed in its own track's
colour — the colour that track's name and notes already carry — so a clip
says which part it belongs to without your reading it off the position
- Three drum kits — each rhythm track picks whether those ten pieces are
synthesised as Retro (tight and dry, the 8-bit default), 80s (gated
snare, long crash, wide clap) or Acoustic-ish (lower kick, more skin on
the snare), from the same picker a tonal track uses for its waveform, each
kit drawing the shape of its own decay. Same rows, same patterns, same hits:
switching re-voices the part without touching a note of it, and the choice
saves with the song
- A playable piano keyboard down the left of every tonal track instead of a
column of note names — press a key to hear it through that track's own
instrument, drag down the keys to glissando. Rhythm tracks keep their ten
piece names
- A note lane per track (the Note button): one stem per note or hit with
a diamond head to drag, showing velocity, pan or bend — pick which
from its header. The grid dims a quiet item, but which of these is loudest
is a comparison, and heights compare better than shades of one colour
- Free placement in the Grid menu (Pro Tools' Slip): place, drag and resize
without snapping. The grid still sets how long a new note is
- Transpose (menu → Transpose) by semitones, octaves or — with a key set —
by scale steps, which move each note to the next pitch in the key, so a
melody nudged upward stays in the key instead of landing on the note between.
Fit to the scale pulls a take onto the nearest scale tones, and never
merges two notes into one. With In scale on, ↑/↓ move by a scale
step and Alt+↑/↓ stay chromatic
- Dynamics (menu → Dynamics) — the third axis. Vary walks each note's
velocity randomly around where it already is, so a stamped-in groove stops
sounding like a machine without losing the shape you gave it; Accent the
beat plays the notes on a beat harder than the ones between. Only velocity
moves, so trying one costs an undo at worst
- Quantize and humanize (menu → Timing), with a strength: 100% lands
every note on the grid, 50% halves the error and keeps the feel. Recording
no longer snaps as you play — a take keeps your timing, and correcting it is
a separate decision
- Pen / Eraser / Grab tools; drag to move, drag the right edge to resize,
drag empty space to marquee-select
- Key and scale — pick a tonic and a scale in the bottom bar and the lane
shades every row outside it, dims those keys on the keyboard, and marks the
tonic. Nothing is forbidden; you can just see where the wrong notes are
before you hear them. Turn on In scale and a note placed or dragged
with the mouse moves to the nearest pitch in the scale — playing is never
corrected, the same way recording never quantises as you play
- Chord progressions — a built-in progression (I–V–vi–IV, ii–V–I, the
Andalusian cadence, a 12-bar blues, …) written straight into a track, one
chord per bar, in the song's key. They're stored as degrees, not chords,
so the same progression comes out major or minor depending on the scale you
picked: change the key and insert it again to hear it somewhere else
entirely. The drum side has had this for a while — this is the same idea for
the half of a song that isn't the beat
- Parts that follow the chords — from the same Chords dialog, write a bass
line (roots, root–fifth, eighth pulse, octaves, a chord walk) or an arpeggio
(up, down, up-down, broken chord, fast 7ths) into a track, built from the
chords another track is playing. It reads whatever chords are there — a
progression you inserted or ones you wrote yourself — and starts the figure
over on every chord change
- Arrange — insert or delete bars across the whole song at once: every
track, its automation, the markers and the loop move together. Markers
double as sections (Verse, Chorus, …): duplicate one, copy it to the
end, or delete it, and everything after it moves to make room or close the
gap
- Variation — the same part, not exactly the same: move a share of the
notes to a neighbouring scale step, jump some an octave, or thin the part
out while keeping every downbeat. Press again for a different answer
- Tap tempo — tap the button beside the tempo field in time and the
tempo follows; stop for two seconds to start a new count
- Euclidean rhythms — in the Patterns dialog, spread a number of hits as
evenly as they go over a number of steps on one kit piece (the tresillo,
the bossa clave, a five-against-four drift, ...), rotated as you like, and
insert it as a layer that replaces only that piece, so a groove can be
built one piece at a time
- Ghost notes — the other tonal tracks' notes show faintly in the active
track's piano roll, so you can see the chords while you write the melody
over them. They can't be clicked, so they never get in the way; the button
beside the Grid picker turns them off
- Chords — place several pitches in one column, or build one from a
selected note with the inspector's ten quick voicings (power chord, maj,
min, dim, aug, sus2, sus4, 7, maj7, m7), which add real notes above the
root rather than flagging the one you picked
- Selectable grid resolution (1/4, 1/8, 1/16 and triplets) and a swing
control for a shuffled 8th feel
- Time signature (4/4, 3/4, 6/8, …) and named timeline markers
- Loop range, zoom, multi-select, copy/paste, undo/redo
- Recording from the computer keyboard — arm a track with its R
button, hit Record for a bar of count-in, and play: a tracker-style key
layout (ZSXDCV… plus the row above for sharps, QWERTY an octave up,
[/]
to shift octave) on tonal tracks, the ten kit pieces on rhythm tracks.
What you play is kept as played, not snapped to the grid — Quantize
(with a strength) tidies it afterwards. A metronome
clicks on every beat, accented on the downbeat, and never reaches an
exported WAV
- Overdub — turn Loop on before you record and the transport keeps
going round: the count-in happens once, and each lap adds to what's already
there instead of replacing it, which is how a drum part actually gets built.
Playing the same pitch in the same place replaces just that note
- MIDI keyboard — Connect MIDI keyboard in the menu and play a real
keyboard into the armed track, with how hard you hit the keys: velocity
reaches the note you hear while playing and the note that lands on the grid,
drawn there as brightness. Works for monitoring, recording and step entry
alike; drums map through General MIDI. Listens to every input on every
channel, so there is nothing to pick
- Step entry — with a track armed and the transport stopped, the same
keys write a note at the playhead and move it on one step, so you can type
a part out at your own pace. Keys held together land as a chord on one
column;
←/→ move through time, ↑/↓ between tracks, Home/End to
the ends, Backspace steps back and clears. Every move is announced with
the position and what's already there, so this is also how you compose —
and read a song back — without a mouse at all
- Built-in rhythm patterns (Rock, Techno, Disco, Swing, Hip-Hop, House,
Breakbeat, Funk, Half-time, Bossa Nova, Reggae, Trap) you can audition and
stamp into a rhythm track. Every hit lands with its own velocity, so the
groove has accents rather than a flat machine pulse, and each pattern
carries a fill bar used on the last bar of every phrase (2, 4 or 8
bars, or off), with a crash on the downbeat after it. The kit is spread
across the stereo field on insert — hi-hat and ride right, shaker, toms
and crash left, kick and snare centred — which you can switch off
Sound design
- Per-track waveform — square, PWM (a pulse width that sweeps
continuously across the part, so every note picks the sweep up where the
last one left off), triangle, saw, sine, half sine, an NES triangle
wavetable, saw+tri, hard sync (with a sweep), a Karplus-Strong
pluck, pitched noise (a chip's noise channel, so it buzzes at the
note rather than just hissing), ring modulation, FM (with
modulator ratio/depth), and additive harmonics (eight partials)
- Per-track ADSR envelope, a resonant lowpass filter with its own
envelope amount, and a duty cycle (pulse width) for square-wave tracks
that any single note can override
- Instrument presets — nine built-in starting points (electric piano,
bell, marimba, plucked string, brass, warm pad, round bass, upright bass,
chip lead) that
are pure settings for the synth already there: the electric piano is an FM
patch, the plucked string is a filter envelope. Load one, tune it, and save
the result as your own preset for any tonal track in any song. An acoustic
piano is not among them — that needs samples, which is a separate
question (
TODO.md)
- Per-track FX: continuous Delay / Chorus / Reverb sends, a 3-band
EQ, a compressor, a bitcrush downsampler, a tremolo, a
formant filter (vowel shapes), and a vibrato on tonal tracks — all neutral by default. The track header
shows them as compact chips (which effect, in what order, what's
bypassed); the knobs live in the inspector column, which shows the active
track's whole chain whenever no note is selected
- Per-note effects: velocity, pan, bend, vibrato, tremolo, pulse width
(duty), arpeggio, portamento, bitcrush, echo, chorus and reverb — pan places
a single note in the stereo field on top of its track's own pan
- Per-hit velocity and pan on rhythm tracks — click a drum hit to set how
hard it's struck, so a pattern can have accents instead of sounding
mechanical, and where it sits in the stereo field, so the kit spreads out
instead of stacking in one spot; quieter notes and hits are drawn dimmer.
Velocity shapes the tone as well as the level — a soft hit is darker,
like a real drum struck gently, so a ghost note sits behind the beat rather
than being a quiet copy of the accent
- Automation curves per track — draw Volume, Pan, or Delay/Chorus/Reverb
send over time
The master bus takes the same strip a track does, so its five groups are
visible at once. A chip is dimmed while its effect is doing nothing.
Saving & exporting
- Song library — bundled examples or your own songs saved in this browser
- Save as .json / Load .json save/open a song as
.json. In Chrome/Edge this goes through
the browser's own file picker rather than always dropping into Downloads — pick a
project folder once and Save/Load/Export MIDI/Export WAV all remember it together
(Firefox/Safari fall back to the old auto-download)
- Export / Import MIDI move a Standard MIDI File (format 1)
- Export WAV renders the whole song offline and downloads a
.wav
- Export code writes the song as
TRACKS / RHYTHM_TRACKS JS literals to drop into
a game, with a Copy button for the result
- Keyboard & screen reader: Tab reaches every control with a visible focus
ring, Shift+←/→ walks the notes of a track and Home/End jump to its ends,
with each selection announced as pitch and bar/beat — and step entry (above)
adds a cursor you can move around the grid in both directions, announcing
what's at each step, so a song can be written and read back without a mouse
- On a phone it's a player, not an editor — below 760px the page becomes
a song list with a transport, a draggable position bar and a level meter,
since listening is what a phone is actually for here. The controls stay
pinned at the top and only the list scrolls, so you don't lose Play while
thumbing for the next song. "Open the editor anyway" is there if you
disagree, and is remembered; the ☰ menu's "Back to the player" undoes it
- Installable PWA — works offline once loaded, and can be added to the
home screen as a standalone/fullscreen app; a Fullscreen menu item also
toggles plain browser fullscreen
Roadmap
Most of the original audio-engine roadmap is now built — wavetable
synthesis, FM, a per-track resonant filter, aux sends for reverb/delay,
custom DSP via AudioWorklet, spectrum + LUFS metering, parallel compression,
sidechain ducking and voice pooling all ship today.
Still open (see TODO.md for the full breakdown):
- Sampling — sample playback and granular synthesis
- Collaboration — cloud sync and live multi-user editing
- Accessibility — the grid is labelled, keyboard-navigable in both
directions and notes can be created from the keyboard (step entry); what
the remaining gaps are is now a question for someone who uses a screen
reader daily rather than something to guess at
Run it locally
No build step, nothing to install. Serve the folder over http:// (ES modules
don't work from file://):
node dev.js # starts the server and opens a browser
node dev-server.js # server only — then open http://localhost:8080
On Windows you can double-click start.cmd instead of opening a terminal.
Any static server works too, e.g. python3 -m http.server 8080.
There's no build or lint step and no test framework. The closest thing to a
test command is:
node verify.js # headless-browser smoke test
node verify.js --only kit # just the steps whose name contains "kit"
The screenshots above are generated the same way, so they can be refreshed
rather than re-staged by hand when the interface moves:
node shots.js # rewrites docs/img/*.png
It starts its own server, drives the app through a set of core interactions in
a real headless Chromium over the Chrome DevTools Protocol, and fails if any
expectation is wrong or if the page logs a console error at any point. It
needs a Chromium-family browser on the machine (CHROME_PATH=… to point at a
specific one).
The app icon is drawn by a command too, from one path in one file:
node icons.js # rewrites icons/*.png and icons/*.svg
Songs: examples vs. your own
- Examples live in
songs/ and are listed in songs/index.json. They load
over the network (from this site) via the Songs menu.
- Your songs are saved in the browser's
localStorage — nothing is
uploaded. Save in the menu (or Ctrl+S) stores the current song
under its name; Songs lists them first, with Load/Delete and a Save as.
Changes you haven't saved are asked about before a load or a new song
replaces them, and a session that ended unsaved comes back at the top of
the list as Unsaved.
- Save as .json / Load .json in the menu save/open a song as a
.json file — via
the browser's native file picker in Chrome/Edge (remembering one project
folder across Save/Load/Export MIDI/Export WAV), or a plain download
elsewhere.
The page always starts as a fresh project with the starter tracks (Lead,
Harmony, Bass, Pad and Rhythm, all empty); pick a song explicitly from the
Songs menu. Work is autosaved to this browser in the background purely
as crash recovery — it's never restored automatically.
Add an example song
- In the editor, build a song and click Save as .json to download its
.json.
- Drop the file into
songs/ (e.g. songs/my-tune.json).
- Add an entry to
songs/index.json:
{ "file": "my-tune.json", "name": "My Tune", "desc": "One-line description." }
- Bump
CACHE_NAME in sw.js so installed clients pick up the change.
The bundled examples are listed in songs/index.json, which is what the Songs
menu reads — twenty-one of them at the time of writing, from froggy-hop.json (the
game demo) to the SID-voiced Bach arrangements (inventio.json, air.json, …).
Deploy to GitHub Pages
The site is fully static, so it needs no build and no workflow. Once this
folder is the root of its own repo:
- Settings → Pages → Build and deployment → Source: Deploy from a branch,
main / / (root).
- Push to
main.
Pages serves the whole folder, so the examples load from the same origin.
(.nojekyll is included so the files are served as-is.)
Installing on Android
This is a Progressive Web App: open
the site in Chrome on Android, then use the browser menu's Add to Home
screen / Install app option (Chrome may also prompt automatically). The
installed app opens without browser chrome and keeps working offline once
it's been loaded once, since sw.js precaches the app shell and the bundled
example songs. manifest.webmanifest requests fullscreen display where the
browser supports it (falling back to a standalone app window otherwise); the
in-app Fullscreen menu item toggles fullscreen for regular browser tabs too.
Layout
index.html the editor (self-contained: HTML + CSS + JS + synthesis)
help.html the in-depth guide — opens in its own tab, works offline too
js/song-data.js the demo song's note data (TRACKS, RHYTHM_TRACKS, TEMPO_BPM)
js/downsample-processor.js AudioWorklet behind the master and per-track bitcrush
songs/ example songs + index.json
manifest.webmanifest PWA manifest (name, icons, display mode)
sw.js service worker: offline cache for the app shell
icons/ generated app icons (see icons.js)
dev-server.js tiny static server for local use
dev.js starts dev-server.js and opens a browser
start.cmd Windows double-click entry point
verify.js headless-browser smoke test
shots.js regenerates the screenshots in docs/img/
icons.js regenerates the app icon in icons/
cdp.js shared browser-driving plumbing for the three above
docs/img/ the screenshots this README (and help.html) link to
CHANGELOG.md what changed in each version
TODO.md / DONE.md what's left, and the journal behind what's built
docs/ design notes and implementation plans
CHANGELOG.md lists what changed in each version, for the people using the app.
For a deeper tour, DESIGN.md specifies the GUI and the internal architecture,
CLAUDE.md is a shorter orientation for editing the code, TODO.md tracks what
isn't built yet, and DONE.md is the working journal behind what is — the
measurements, the hypotheses that were ruled out and why each solution looks the
way it does.