OSC protocol
blzr exposes an OSC (Open Sound Control) interface that lets external applications control synth parameters in real time. The current transport uses window.postMessage, making it ideal for iframe embeds — no servers, no ports, zero setup.
Quick start
Section titled “Quick start”Embed blzr in an iframe with a preset patch and start sending OSC messages:
<iframe id="blzr" src="https://blzr.app?embed=true&patch=voder-voice" allow="autoplay" style="width:800px; height:600px; border:none;"></iframe>
<script>const blzr = document.getElementById("blzr");
// Wait for blzr to be readywindow.addEventListener("message", (e) => { if (e.data?.type === "osc/ready") { console.log("blzr ready:", e.data.patch, e.data["audio-state"]);
// Resume audio (required — browsers block autoplay) blzr.contentWindow.postMessage({ type: "osc/resume-audio" }, "*"); }
if (e.data?.type === "osc/audio-state-changed") { console.log("audio state:", e.data["audio-state"]); }});
// Set oscillator frequency to 880 Hzblzr.contentWindow.postMessage({ type: "osc", address: "/blzr/node/fo-osc/param/frequency", args: [880]}, "*");</script>Embed mode
Section titled “Embed mode”Adding ?embed=true to the blzr URL activates embed mode:
- All UI chrome is hidden (welcome panel, side panels, toolbar)
- A minimal modeline appears at the bottom with audio toggle and “Open in blzr” link
- The postMessage listener starts automatically
- An
osc/readymessage is sent to the parent window once initialization is complete
URL parameters
Section titled “URL parameters”| Parameter | Type | Description |
|---|---|---|
embed | "true" | Enable embed mode |
patch | string | Load a module preset by name (see available patches) |
When patch is set, the module is loaded in expanded mode — child nodes keep their original IDs (e.g., fo-osc, vca-amp) instead of being wrapped in a module container. This gives you stable, predictable OSC addresses.
Address format
Section titled “Address format”All OSC addresses follow this pattern:
/blzr/node/{node-id}/param/{param-key}| Segment | Description |
|---|---|
/blzr | Namespace prefix (always present) |
node | Literal keyword |
{node-id} | The node’s ID in the patch (e.g., fo-osc, vca-amp) |
param | Literal keyword |
{param-key} | Parameter name (e.g., frequency, gain, cutoff) |
Examples
Section titled “Examples”| Address | Effect |
|---|---|
/blzr/node/fo-osc/param/frequency | Set oscillator frequency |
/blzr/node/fo-osc/param/detune | Set oscillator detune (cents) |
/blzr/node/fo-flt/param/frequency | Set filter cutoff frequency |
/blzr/node/fo-flt/param/Q | Set filter resonance |
/blzr/node/vca-amp/param/gain | Set amplifier gain |
/blzr/node/vca-const/param/offset | Set constant source offset |
Finding node IDs
Section titled “Finding node IDs”Node IDs depend on the loaded patch. When using a module preset via ?patch=, the IDs come from the module spec. You can find them in the Module Library.
For example, the filtered-osc module contains nodes: fo-osc, fo-flt, fo-amp.
Message types
Section titled “Message types”Single message
Section titled “Single message”Set one parameter value immediately.
iframe.contentWindow.postMessage({ type: "osc", address: "/blzr/node/fo-osc/param/frequency", args: [440]}, "*");The args array accepts raw numbers or typed OSC values:
// Raw numberargs: [440]
// Typed OSC argument (equivalent)args: [{ type: "f", value: 440.0 }]Only the first argument is used. Additional arguments are ignored.
Bundle
Section titled “Bundle”Send multiple parameter changes atomically in a single message.
iframe.contentWindow.postMessage({ type: "osc", bundle: true, messages: [ { address: "/blzr/node/fo-osc/param/frequency", args: [440] }, { address: "/blzr/node/fo-flt/param/frequency", args: [2000] }, { address: "/blzr/node/fo-amp/param/gain", args: [0.8] } ]}, "*");Timed bundles (sequences)
Section titled “Timed bundles (sequences)”Schedule parameter changes along a timeline with sample-accurate automation. Timetags are in milliseconds relative to “now” (the current AudioContext time when the message is received).
iframe.contentWindow.postMessage({ type: "osc", bundles: [ { timetag: 0, messages: [ { address: "/blzr/node/fo-osc/param/frequency", args: [440] } ] }, { timetag: 500, messages: [ { address: "/blzr/node/fo-osc/param/frequency", args: [880] } ] }, { timetag: 1000, messages: [ { address: "/blzr/node/fo-osc/param/frequency", args: [440] } ] } ]}, "*");This plays 440 Hz → ramps to 880 Hz over 500ms → ramps back to 440 Hz over 500ms.
How timed bundles work
Section titled “How timed bundles work”- All bundles arrive in a single postMessage
- Messages are grouped by
[node-id, param-key] - Each group produces one automation sequence on the Web Audio parameter:
cancelScheduledValuesat the first timetagsetValueAtTimefor the first valuelinearRampToValueAtTimefor each subsequent value
- Timetags are converted:
audioContext.currentTime + (timetag / 1000)
This means multiple parameters can be automated independently and simultaneously — each parameter gets its own automation lane.
Handshake protocol
Section titled “Handshake protocol”The embed lifecycle follows a strict handshake:
Parent blzr iframe | | | ←── osc/ready ─────────────────| (patch loaded, listener active) | | | ─── osc/resume-audio ────────→ | (user gesture required) | | | ←── osc/audio-state-changed ── | (state: "running") | | | ─── osc messages ────────────→ | (control parameters) | ─── osc bundles ─────────────→ | | ─── osc timed bundles ──────→ | | | | ─── osc/suspend-audio ──────→ | (pause) | ←── osc/audio-state-changed ── | (state: "suspended") | | | ─── osc/ping ────────────────→ | | ←── osc/pong ─────────────────|Outbound messages (blzr → parent)
Section titled “Outbound messages (blzr → parent)”osc/ready
Section titled “osc/ready”Sent once after initialization is complete, patch is loaded, and the listener is active.
{ type: "osc/ready", patch: "voder-voice", // patch name from URL param, or null "audio-state": "suspended" // initial AudioContext state}Always wait for this message before sending any OSC data.
osc/audio-state-changed
Section titled “osc/audio-state-changed”Sent whenever the AudioContext state changes.
{ type: "osc/audio-state-changed", "audio-state": "running" // "running" | "suspended" | "closed"}osc/pong
Section titled “osc/pong”Reply to a ping. Use for health checks.
{ type: "osc/pong" }Inbound commands (parent → blzr)
Section titled “Inbound commands (parent → blzr)”osc/resume-audio
Section titled “osc/resume-audio”Resume the AudioContext. Required after page load due to browser autoplay policies — the browser will not start audio without a user gesture. Your host page must call this in response to a user interaction (click, keypress, etc.).
iframe.contentWindow.postMessage({ type: "osc/resume-audio" }, "*");osc/suspend-audio
Section titled “osc/suspend-audio”Suspend the AudioContext (pause all audio processing).
iframe.contentWindow.postMessage({ type: "osc/suspend-audio" }, "*");osc/ping
Section titled “osc/ping”Check if the embed is alive. Receives osc/pong in response.
iframe.contentWindow.postMessage({ type: "osc/ping" }, "*");Available patches
Section titled “Available patches”These module presets can be loaded with ?patch=:
| Patch name | Description | Key node IDs |
|---|---|---|
vca | Voltage-controlled amplifier | vca-amp, vca-const |
filtered-osc | Oscillator → filter → amp | fo-osc, fo-flt, fo-amp |
lfo-mod | LFO modulation source | lfo-osc, lfo-amp, lfo-const |
fm-voice | FM synthesis voice | fm-carrier, fm-modulator, fm-mod-amp, fm-mod-depth |
midi-voice | MIDI-controlled voice | mv-osc, mv-flt, mv-amp, mv-adsr |
voder-excitation | VODER excitation source | ve-buzz-osc, ve-noise, ve-mix |
voder-filterbank | VODER filter bank | vf-band-0 through vf-band-9, vf-merge |
voder-master | VODER master control | vm-amp, vm-tilt-flt |
voder-voice | Complete VODER voice | All voder nodes combined |
Each module’s full node list and parameter specs are documented in the Module Library.
Complete example
Section titled “Complete example”A minimal host page that loads a filtered oscillator and sweeps the cutoff frequency:
<!DOCTYPE html><html><head><title>blzr OSC demo</title></head><body> <button id="start">Start</button> <input id="cutoff" type="range" min="20" max="20000" value="2000" step="1"> <label for="cutoff">Filter cutoff</label>
<iframe id="blzr" src="https://blzr.app?embed=true&patch=filtered-osc" allow="autoplay" style="width:100%; height:400px; border:1px solid #333;" ></iframe>
<script> const blzr = document.getElementById("blzr"); const startBtn = document.getElementById("start"); const cutoffSlider = document.getElementById("cutoff"); let ready = false;
window.addEventListener("message", (e) => { if (e.data?.type === "osc/ready") { ready = true; startBtn.disabled = false; } });
startBtn.addEventListener("click", () => { blzr.contentWindow.postMessage({ type: "osc/resume-audio" }, "*"); });
cutoffSlider.addEventListener("input", (e) => { if (!ready) return; blzr.contentWindow.postMessage({ type: "osc", address: "/blzr/node/fo-flt/param/frequency", args: [Number(e.target.value)] }, "*"); }); </script></body></html>Timed sequence example
Section titled “Timed sequence example”Schedule a simple melody using timed bundles:
function playMelody(iframe) { const notes = [ { freq: 261.63, time: 0 }, // C4 { freq: 329.63, time: 250 }, // E4 { freq: 392.00, time: 500 }, // G4 { freq: 523.25, time: 750 }, // C5 { freq: 392.00, time: 1000 }, // G4 { freq: 329.63, time: 1250 }, // E4 { freq: 261.63, time: 1500 }, // C4 ];
iframe.contentWindow.postMessage({ type: "osc", bundles: notes.map(n => ({ timetag: n.time, messages: [{ address: "/blzr/node/fo-osc/param/frequency", args: [n.freq] }] })) }, "*");}The entire sequence is scheduled ahead of time on the AudioContext timeline, so playback is glitch-free regardless of main thread activity.
Integration ideas
Section titled “Integration ideas”The OSC protocol opens blzr to control from any web-based tool:
- Live coding — Send parameter changes from Strudel, Hydra, or custom REPL environments
- Sequencers — Drive blzr patches from web-based step sequencers or DAWs
- Data sonification — Map data streams to synth parameters
- Interactive installations — Control audio from sensor data, webcam tracking, or touch interfaces
- Education — Build teaching tools that demonstrate synthesis concepts
Since the transport is postMessage, any JavaScript context that can reference the iframe can control blzr — including browser extensions, Web Workers (via proxy), and other iframes on the same page.
Error handling
Section titled “Error handling”- Messages with invalid addresses (wrong prefix, missing segments) are silently ignored
- Messages with unrecognized
typevalues are silently ignored - Non-object postMessage data is silently ignored
- If the target node ID does not exist in the current patch, the parameter set is a no-op
- Sending OSC messages before
osc/readyhas no effect — the listener is not yet active
Limitations
Section titled “Limitations”- Transport: Only
postMessageis supported (no WebSocket or UDP yet) - Direction: Parameter control is parent → iframe only. There is no way to read parameter values back via OSC
- Enum parameters: Setting enum params (like oscillator
type) via OSC works but is not schedulable with timed bundles — only AudioParam-backed numeric parameters supportlinearRampToValueAtTime - Same-origin:
postMessagewith"*"target origin works cross-origin, but consider restricting to a specific origin in production for security - Autoplay: Browsers require a user gesture before audio can start. Your host page must handle the
osc/resume-audiocall in response to user interaction