- Run Faust compile + WebAudio + UI entirely in the browser.
- Keep existing runtimes untouched and cohabitating (new files only).
- Preserve MCP tool surface so clients can switch runtimes without changes.
- Document every implementation step clearly and explicitly.
- No changes to
faust_node_server.py,faust_node_worker.mjs, orui/rt-node-ui.*. - No Node process for DSP/runtime in this architecture.
- No attempt to run WebAudio headlessly.
The browser-only runtime lives beside the existing runtimes. It does not replace or modify them. All new files are separate and can be run independently.
faust_browser_server.py: Python entrypoint for static hosting and MCP proxying.faust_browser_runtime.mjs: Browser runtime module (compile/start/stop + metrics).ui/rt-browser-ui.html: Browser-only UI entrypoint.ui/rt-browser-ui.js: Browser-only UI logic (no HTTP polling).ui/rt-browser-ui.css: Browser-only CSS (can importrt-node-ui.css).
The browser is the only DSP runtime. It compiles Faust to WASM, runs audio via AudioWorklet, and renders UI locally. The Python process is optional and only used to expose MCP tools to external clients.
- User opens
rt-browser-ui.htmlin a real browser. - UI and runtime are self-contained; tools are only callable from the page.
- Good for manual use or when the LLM is embedded in the same page.
- Python FastMCP server exposes tools over SSE/stdio.
- The browser page connects outbound to the proxy (WebSocket or HTTP polling).
- The proxy forwards tool calls to the browser and returns results.
- This is the only way to let external MCP clients reach a browser runtime because browsers cannot accept inbound HTTP/SSE connections.
The browser runtime mirrors the tool surface from faust_node_server.py and
faust_node_worker.mjs so MCP clients can switch between runtimes.
Required runtime methods:
check_syntax(faust_code, name)compile(faust_code, name, input_source, input_freq, input_file, hide_meters)compile_and_start(...)start()/stop()get_status()get_dsp_json()get_params()/get_param(path)/get_param_values()set_param(path, value)/set_param_values(values)get_audio_metrics(...)get_midi_inputs()/get_midi_status()/select_midi_input(...)get_svg_diagrams(name?, args?)
All methods must return the same shape as the Node runtime so the UI and MCP clients remain compatible.
- Create the new Python, JS, and web files listed in the Coexistence Contract.
- Add clear TODO blocks and documented interfaces in each file.
- Keep filenames and structure parallel to existing runtimes.
- Initialize Faust WASM
- Load
@grame/faustwasmand create a shared compiler instance. - Cache the wasm module to avoid repeated network loads.
- Load
- Compile DSP
- Implement
check_syntaxusing the Faust compiler without starting audio. - Implement
compileto generate DSP JSON + wasm factory. - Store
dsp_json,params, andui_jsonin runtime state.
- Implement
- Audio graph setup
- Create
AudioContextwithlatencyHintsupport. - Install an AudioWorklet module for Faust DSP.
- Create an AudioWorklet node and connect it to the destination.
- Create
- Parameter control
- Maintain an in-memory param map (path -> value).
- Implement
set_paramandset_param_valuesto update DSP params.
- Metrics capture
- Add
AnalyserNodefor scope/spectrum. - Implement
get_audio_metricsusing time-domain + FFT data. - Provide the same JSON shape as the Node runtime.
- Add
- MIDI
- Use Web MIDI API for device enumeration and selection.
- Implement
get_midi_inputs,get_midi_status,select_midi_input.
- Lifecycle
- Implement
start/stopto start/closeAudioContextand DSP node. - Ensure stop releases worklet + MIDI references.
- Implement
- SVG diagram rendering
- Use
FaustSvgDiagramsfrom@grame/faustwasm(browser example in faustwasm README). - Call
svgDiagrams.from(name, code, argv.join(" "))after compilation. - Return the SVG map (
{ "process.svg": "<svg ...>", ... }) viaget_svg_diagrams. - Cache SVG results per compiled DSP to avoid re-running the compiler.
- Use
- HTML
- Copy the structure of
ui/rt-node-ui.html(same IDs/classes). - Update the title and script link to
rt-browser-ui.js.
- Copy the structure of
- CSS
- Import
rt-node-ui.cssor clone it to allow runtime-specific tweaks.
- Import
- JS wiring
- Replace HTTP polling with calls into
faust_browser_runtime.mjs. - Use the same UI rendering flow for params, scope, spectrum, and probes.
- Add a clear empty state when no DSP is loaded.
- Replace HTTP polling with calls into
- DSP load flow
- Define a single entry point:
compile_and_start. - Optional: accept
?dsp=query param or a local file picker.
- Define a single entry point:
- SVG UI render (left column)
- Add a collapsible "DSP Diagram" panel below the Faust UI panel (left column).
- Use
get_svg_diagramsfrom the runtime and injectprocess.svginto the panel. - Provide a toggle to show/hide SVG to keep the UI lightweight.
- Serve
ui/rt-browser-ui.html, JS modules, CSS, and wasm assets. - Map
/tort-browser-ui.htmlfor convenience. - Provide CLI flags/env vars:
BROWSER_UI_PORT,BROWSER_UI_HOST,BROWSER_UI_ROOT.
- Ensure MIME types are correct for
*.mjsand*.wasm.
- Transport choice
- HTTP long-polling is acceptable for a first version because the proxy only forwards control-plane tool calls (scope/spectrum stay in the browser UI).
- WebSocket remains the recommended upgrade path if streaming or lower latency is needed later.
- Browser connection
- Browser registers on load and maintains a long-polling loop.
- The proxy tracks the active browser session(s).
- Request/response protocol
- JSON messages with
id,method, andparams(mirrors Node worker). - Browser responds with
{ id, result }or{ id, error }. - Suggested endpoints (first version):
POST /bridge/register->{ session_id }GET /bridge/poll?session_id=...-> pending requests (blocks with timeout)POST /bridge/reply->{ id, result|error }
- JSON messages with
- Tool mapping
- MCP tools call
BrowserBridge.request(method, params). - Results are returned verbatim to the MCP client.
- MCP tools call
- Resilience
- Reject tool calls if no browser session is connected.
- Add timeouts for browser responses.
- UI loads and runs a DSP in a real browser.
compile_and_startreturns params and DSP JSON.- Param changes update audio in real time.
- Scope/spectrum/probe rendering works without HTTP polling.
- MCP proxy path works end-to-end:
- MCP client -> Python proxy -> Browser runtime -> MCP client.
- New browser-only runtime files listed in Coexistence Contract.
- Updated plan with explicit implementation steps and responsibilities.
- Should the Python proxy also serve static files, or should it be separate?
- Is WebSocket acceptable as the browser <-> proxy transport, or do you prefer HTTP long-polling for simpler dependencies?
- Should the UI accept DSP code via query param, file upload, or both?