A spatial audio studio that runs entirely in the browser. Place sounds around your head in three dimensions, move them along a timeline, and layer in frequency tools that are labelled for what the research actually supports.
No account, no upload, no tracking. Every sample is decoded locally and every tone is generated in the Web Audio graph.
Live: 3d-sound-journey-builder.vercel.app
The field. A metre-accurate map of the space around your head. Drag a sound to move it on the ground plane, hold Alt to lift it, right-drag to orbit. The 2D map and the 3D view are one camera blended between orthographic and perspective, so switching does not cut.
The focus view. One screen, one button. Pick a mode and a length, press the ring. No map, no timeline, and a readout that names every frequency currently playing.
| Area | Detail |
|---|---|
| Spatial audio | HRTF panner per source, shoulder and outer-ear filters that track elevation, three listener postures, head tracking through device orientation |
| Sound library | Sixty-two entries in seven categories: recorded ambience, sound-healing instruments, offline-rendered instruments, procedural frequency tools, generative layers, and your own files |
| Synthesised healing set | The bowl strike, the gong and the seven chakra bowls are generated, not recorded. Each bowl sounds within ten cents of the frequency on its label, and a test measures it |
| Frequency tools | Binaural, isochronic and monaural beats, pure and solfeggio tones, white, pink and brown noise, a breathing pacer, a 7.83 Hz pulse. Each carries an evidence label |
| Per-source inserts | Low-pass, high-pass, playback speed, tremolo, reverb send, fade in, fade out, repeat cycle |
| Master bus | Shared convolution reverb, soft limiter, stereo meters |
| Timeline | Clips, draggable keyframes with volume envelopes, mute and solo, named sections, zoom-aware snapping, fit to view |
| Journeys | Nine built in, from a twenty-five minute deep-work block to an hour-long sound bath and a thirty minute sleep descent |
| Focus modes | Six single-screen sessions with a countdown ring, a live frequency readout, a settling and wind-down phase, and a taper that slows the frequencies on the way out |
| Output | Headphones with HRTF, stereo speakers, 5.1, or a custom speaker layout placed by dragging |
| Editing | One undo stack across moves, adds, deletes, clip edits, keyframe edits, parameters and motion paths |
| Sharing | Scenes in local storage, plus links that carry the whole journey in the URL |
| Languages | English and German |
Frequency tools ship with an evidence grade drawn from a literature review kept in docs/research. The grade describes the published research, not a promised effect.
| Tool | Grade |
|---|---|
| Pink and white noise, breathing pacer at six breaths a minute | Some evidence |
| Binaural, isochronic and monaural beats, brown noise, 40 Hz gamma | Weak evidence |
| Solfeggio tones, 432 Hz tuning, 7.83 Hz Schumann | No evidence |
Each grade comes with a one-line reason. Select a binaural beat and the inspector says it needs headphones, and offers to swap it for an isochronic pulse that survives on speakers.
Forty hertz gamma stimulation has strong evidence in clinical Alzheimer's trials using combined light and sound devices. That does not transfer to a wellness audio app, and the app does not claim it does.
This is not a medical device.
npm install
npm run dev # http://localhost:5199
npm test # 254 unit tests
npm run build # landing page + app
npm run audit:audio # what is in public/sounds, and does the library agree
npm run sounds:check # check the candidate sounds and their licences, write nothing
npm run sounds:fetch # download the ones that passed
npm run shoot # the screenshots in docs/, against a running dev serverThe build stamps its own identity: the version from package.json, the commit
it was built from, and the date. The app shows all three on the welcome card and
under Settings, About. Deployment here is a CLI upload rather than a git push,
so nothing in the hosting platform knows which commit is live and the app has to
say it itself. In development the values are read once when the dev server
starts, so a version bump needs a restart to show.
sounds:check reads scripts/sound-sources.json, opens each source page, and
reads both the audio link and the credit out of it. A file is accepted only
when the credit line leads with the agency, because a US federal page is public
domain only for the agency's own recordings. It writes nothing; sounds:fetch
downloads what passed and records source, credit, licence, size, checksum and
retrieval date in docs/sound-provenance.json. A new file still needs an entry
in src/data/SoundLibrary.js before the app can use it.
shoot reads four environment variables: SHOOT_VIEW (field, 3d, focus
or landing), SHOOT_JOURNEY, SHOOT_OUT and SHOOT_SIZE (desktop or
mobile, the 375x812 box the layout breakpoints are written against). The
phone layout in docs/screenshot_mobile.png comes
from SHOOT_SIZE=mobile SHOOT_VIEW=field SHOOT_JOURNEY=soundbath.
audit:audio reports every bundled file with its size, duration, encoder and
embedded tags, and fails if the library references a file that is not there or
a file sits unreferenced.
The dev server serves the landing page at / and the app at /app.html.
src/
audio/
AudioEngine.js master bus, sources, inserts, analysers, output modes
Generators.js procedural sources: binaural, isochronic, monaural,
tone, noise, breath, pad, drone, shimmer, schumann
InstrumentSynth.js offline-rendered instrument buffers
SceneManager.js save, load and share, including generator parameters
SpeakerConfig.js headphone and speaker layouts
HeadTracker.js device orientation to listener rotation
core/UndoManager.js one command stack for every edit
data/
SoundLibrary.js the registry every surface reads from
Presets.js journeys with sections, focus modes, static scenes
ui/
Camera.js projection and inverse projection
CanvasGrid.js the field: rendering, input, motion paths
Timeline.js the journey editor
Library.js left panel
Inspector.js right panel
FocusView.js the single-screen player
Icons.js one line-icon set for DOM and canvas
sample BufferSource -> LP -> HP -> tremolo -> shoulder -> pinna -> HRTF panner -> gain
generator generator output ---------------------> shoulder -> pinna -> HRTF panner -> gain
binaural generator output (stereo, head-locked) ----------------------------------> gain
every gain -> reverb send -> convolver -> return -> master ; gain -> analyser
master master -> limiter -> destination ; master -> splitter -> left/right analysers
Binaural beats need a different frequency in each ear. Sending them through the HRTF panner would mix the channels and destroy the beat, so they bypass it and stay locked to the listener. The inspector says so rather than hiding it.
World space is metres: x to the right, y forward, z up. Web Audio takes
(x, z, -y). The camera carries yaw, pitch and a blend between orthographic
and perspective; picking and dragging run through the inverse projection, so
they stay exact at any angle.
The browser's own HRTF does the binaural work, and it has limits worth stating
rather than discovering. Measured in Chrome 152 with an OfflineAudioContext,
comparing rendered buffers sample by sample:
| Azimuth | interpolated, smooth between grid points |
| Elevation | not interpolated, snaps to a 15 degree grid |
| Below -45 degrees | no data: -50, -60, -75 and -90 render bit-identical to -45 |
| Distance | gain only. The interaural level difference at 0.2 m, 1 m and 3 m is the same 11.193 dB |
So sideways movement is smooth, vertical movement is a staircase, nothing can be placed steeply underfoot, and bringing a sound to your ear only makes it louder. The shoulder and pinna filters move continuously with elevation and carry some cue past the floor, but the spatial image does not follow. No bundled journey or set crosses that floor, and a test keeps it that way.
Sources feeding the panner are summed to mono first. A stereo buffer in HRTF mode gets the left response applied to the left channel and the right to the right, which does not place the sound at all: measured at -0.9 dB right minus left for a source 3 m to the right, against +11.2 dB once summed.
Full working and sources: docs/research/2026-09-15-spatial-audio-and-head-tracking.md, which also records why AirPods head tracking cannot reach a web page.
- Check the licence allows redistribution in an MIT project. See docs/research for vetted sources.
- Drop the file in
public/sounds/. - Add one entry to
SOUNDSinsrc/data/SoundLibrary.js. Runnpm run audit:audioto confirm the file and the entry agree. - Add the display name to both language tables in
src/i18n.js. - Record the source and licence in
docs/ATTRIBUTION.md.
Everything else follows: the library card, the map colour and glyph, the timeline lane and the inspector all read from that one entry.
MIT, see LICENSE — for the code.
Built by LeopardCode.AI.



