supersonic-scsynth
v0.66.0
Published
SuperCollider scsynth WebAssembly port for AudioWorklet - Run SuperCollider synthesis in the browser
Maintainers
Readme
SuperSonic 0.66.0
SuperCollider's scsynth in the browser as an AudioWorklet.
Auto-generated from project docs — see GitHub for full documentation.
Table of Contents
- Quick Start
- API Overview
- Patterns
- Constructor Options
- Server Options
- scsynth Command Quick Reference
- Full Method Reference
- Support + License
Quick Start
You've installed SuperSonic - now let's make some sound.
We'll create a simple page with two buttons: one to boot the audio engine, and one to trigger a synth.
<button id="boot-btn">boot</button>
<button id="trig-btn">trigger</button>import { SuperSonic } from "https://unpkg.com/supersonic-scsynth@latest";
const supersonic = new SuperSonic({
baseURL: "https://unpkg.com/supersonic-scsynth@latest/dist/",
synthdefBaseURL: "https://unpkg.com/supersonic-scsynth-synthdefs@latest/synthdefs/",
});
const bootBtn = document.getElementById("boot-btn");
const trigBtn = document.getElementById("trig-btn");
bootBtn.onclick = async () => {
await supersonic.init();
await supersonic.loadSynthDef("sonic-pi-prophet");
};
trigBtn.onclick = () => {
supersonic.send("/s_new", "sonic-pi-prophet", -1, 0, 0, "note", 28, "release", 8, "cutoff", 70);
};Let's break down what's happening here.
User Interaction
Web browsers have an autoplay policy that prevents websites from making sound without user consent. Audio can only start after a user interaction like a click, tap, or keypress.
This is why we use a boot button - calling init() from a button handler satisfies the browser's autoplay policy and allows audio to begin.
Creating a SuperSonic Instance
const supersonic = new SuperSonic({
baseURL: "https://unpkg.com/supersonic-scsynth@latest/dist/",
synthdefBaseURL: "https://unpkg.com/supersonic-scsynth-synthdefs@latest/synthdefs/",
});This creates a new SuperSonic instance configured to load assets from CDN. The baseURL tells SuperSonic where to find the WASM engine and workers. The synthdefBaseURL tells it where to find synthdef files when you call loadSynthDef(). The instance doesn't start the audio engine yet - it just sets up the configuration. You can pass additional options to configure transport mode, debug output, and scsynth engine settings (see API Reference).
Booting the Engine
await supersonic.init();Calling init() boots the scsynth audio engine. Behind the scenes this:
- Creates an AudioContext and AudioWorklet
- Loads the WebAssembly module containing scsynth
- Starts scsynth running in a dedicated high-priority audio thread
This is an async operation - the await ensures we don't proceed until the engine is ready.
Loading a Synth Definition
await supersonic.loadSynthDef("sonic-pi-prophet");Before you can play a synth, you need to send its design to scsynth. This design is called a synth definition (or "synthdef") and is a recipe that describes a synth's audio graph - what oscillators, filters, and effects it uses and how they're connected.
SuperSonic comes with 127 ready-to-use synthdefs from Sonic Pi. Here we're loading sonic-pi-prophet, a warm polyphonic synth inspired by the Prophet-5.
Note: you can also use SuperCollider's Desktop app to design your own synthdefs and directly import them live at runtime into your SuperSonic session.
Triggering a Synth
supersonic.send("/s_new", "sonic-pi-prophet", -1, 0, 0, "note", 28, "release", 8, "cutoff", 70);Now for the fun part - making sound! The send() method sends OSC (Open Sound Control) messages to scsynth. The /s_new command creates a new synth instance.
The arguments are:
| | | |
|---------------|----------------------|----------------------------------------|
| synthdef name | "sonic-pi-prophet" | Which synth to create |
| node ID | -1 | Let scsynth assign an ID automatically |
| add action | 0 | Add to the head of the target group |
| target | 0 | The root group |
| params... | "note", 28, ... | Name/value pairs for synth parameters |
The synth parameters control the sound. Here we set note to 28 (a low E), release to 8 seconds, and cutoff to 70 (filter brightness). Each synthdef has its own parameters - see the synthdef documentation for available options.
Working Example
See example/simple.html for a complete working example you can run locally, or example/simple_metrics.html for the same example with a live metrics dashboard.
Adding a Metrics Dashboard
SuperSonic includes a web component that renders a full metrics dashboard from the schema:
<link rel="stylesheet" href="https://unpkg.com/supersonic-scsynth@latest/dist/metrics-dark.css" />
<script type="module" src="https://unpkg.com/supersonic-scsynth@latest/dist/metrics_component.js"></script>
<supersonic-metrics id="metrics"></supersonic-metrics>Connect it after boot to start live updates:
bootBtn.onclick = async () => {
await supersonic.init();
await supersonic.loadSynthDef("sonic-pi-prophet");
document.getElementById("metrics").connect(supersonic, { refreshRate: 10 });
};See Metrics Component for theming, layout control, and customisation.
API Overview
Coordinates WASM, AudioWorklet, SharedArrayBuffer, and IO Workers to run scsynth with low latency inside a web page.
Core
| Member | Description |
| ----------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| init() | Initialise the engine. |
| shutdown() | Shut down the engine. |
| destroy() | Destroy the engine completely. |
| recover() | Smart recovery — tries a quick resume first, falls back to full reload. |
| suspend() | Suspend the AudioContext and stop the drift timer. |
| resume() | Quick resume — calls purge to flush stale messages, resumes AudioContext, and resyncs timing. |
| reload() | Full reload — destroys and recreates the worklet and WASM, then restores all previously loaded synthdefs and audio buffers. |
| reset() | Shutdown and immediately re-initialise. |
| send() | Send any OSC message. |
| sendOSC() | Send pre-encoded OSC bytes to scsynth. |
| sync() | Wait for scsynth to process all pending commands. |
| purge() | Cancel all pending scheduled messages everywhere in the pipeline. |
| cancelAll() | Cancel all scheduled messages in the JS prescheduler. |
| cancelSession() | Cancel all scheduled messages for a session. |
| cancelSessionTag() | Cancel scheduled messages matching both a session and run tag. |
| cancelTag() | Cancel all scheduled messages with the given run tag. |
Asset Loading
| Member | Description |
| ----------------------------------- | ------------------------------------------------------------------------- |
| loadSynthDef() | Load a SynthDef into scsynth. |
| loadSynthDefs() | Load multiple SynthDefs by name in parallel. |
| loadSample() | Load an audio sample into a scsynth buffer slot. |
| sampleInfo() | Get sample metadata (including content hash) without allocating a buffer. |
Events
| Member | Description |
| --------------------------------------------- | ------------------------------------------------------------- |
| on() | Subscribe to an event. |
| off() | Unsubscribe from an event. |
| once() | Subscribe to an event once. |
| removeAllListeners() | Remove all listeners for an event, or all listeners entirely. |
Node Tree
| Member | Description |
| ------------------------------- | ------------------------------------------------------------------- |
| getTree() | Get the node tree in hierarchical format. |
| getRawTree() | Get the node tree in flat format with linkage pointers. |
| getSnapshot() | Get a diagnostic snapshot with metrics, node tree, and memory info. |
Metrics
| Member | Description |
| ----------------------------------------- | -------------------------------------------------------------- |
| getMetrics() | Get current metrics as a named object. |
| getMetricsArray() | Get metrics as a flat Uint32Array for zero-allocation reading. |
| getMetricsSchema() | Get the metrics schema describing all available metrics. |
Properties
| Member | Description |
| ------------------------------- | -------------------------------------------------- |
| initialized | Whether the engine has completed initialisation. |
| initializing | Whether init is currently in progress. |
| audioContext | The underlying AudioContext. |
| node | AudioWorkletNode wrapper for custom audio routing. |
Advanced
| Member | Description |
| --------------------------------------------------- | ----------------------------------------------------------------------- |
| getInfo() | Get engine info: sample rate, memory layout, capabilities, and version. |
| createOscChannel() | Create an OscChannel for direct worker-to-worklet communication. |
| startCapture() | Start capturing audio output to a buffer. |
| stopCapture() | Stop capturing and return the captured audio data. |
| getCaptureFrames() | Get number of audio frames captured so far. |
| isCaptureEnabled() | Check if audio capture is currently enabled. |
| getMaxCaptureDuration() | Get maximum capture duration in seconds. |
| setClockOffset() | Set clock offset for multi-system sync (e.g. Ableton Link, NTP server). |
Advanced
| Member | Description |
| ----------------------------------------- | ------------------------------------------------------------ |
| bufferConstants | Buffer layout constants from the WASM build. |
| initTime | NTP time (seconds since 1900) when the AudioContext started. |
| mode | Active transport mode ('sab' or 'postMessage'). |
| ringBufferBase | Ring buffer base offset in SharedArrayBuffer. |
| sharedBuffer | The SharedArrayBuffer (SAB mode) or null (postMessage mode). |
| getLoadedBuffers() | Get info about all loaded audio buffers. |
| getSystemReport() | Get a comprehensive system performance report. |
| nextNodeId() | Get the next unique node ID. |
| getRawTreeSchema() | Get schema describing the raw flat node tree structure. |
| getTreeSchema() | Get schema describing the hierarchical node tree structure. |
OscChannel
OscChannel — unified dispatch for sending OSC to the AudioWorklet.
Obtain a channel via SuperSonic.createOscChannel on the main thread, then transfer it to a Web Worker for direct communication with the AudioWorklet.
| Member | Description |
| --------------------------------------------- | ------------------------------------------------------------------------------- |
| getCurrentNTP | Set the NTP time source for classification (used in AudioWorklet context). |
| mode | Transport mode this channel is using. |
| transferable | Serializable config for transferring this channel to a worker via postMessage. |
| transferList | Array of transferable objects (MessagePorts) for the postMessage transfer list. |
| classify() | Classify an OSC message to determine its routing. |
| close() | Close the channel and release its ports. |
| getAndResetMetrics() | Get and reset local metrics (for periodic reporting). |
| getMetrics() | Get current metrics snapshot. |
| nextNodeId() | Get the next unique node ID. |
| send() | Send an OSC message with automatic routing. |
| sendDirect() | Send directly to worklet without classification or metrics tracking. |
| sendToPrescheduler() | Send to prescheduler without classification. |
| fromTransferable() | Reconstruct an OscChannel from data received via postMessage in a worker. |
Example
// Main thread: create and transfer to worker
const channel = sonic.createOscChannel();
myWorker.postMessage(
{ channel: channel.transferable },
channel.transferList,
);
// Inside worker: reconstruct and send
import { OscChannel } from 'supersonic-scsynth/osc-channel';
const channel = OscChannel.fromTransferable(event.data.channel);
channel.send(oscBytes);Constructors
Constructor
new OscChannel():
OscChannel
Returns
Accessors
getCurrentNTP
Set Signature
set getCurrentNTP(
fn):void
Set the NTP time source for classification (used in AudioWorklet context).
Parameters
| Parameter | Type |
| --------- | -------------- |
| fn | () => number |
Returns
void
mode
Get Signature
get mode():
TransportMode
Transport mode this channel is using.
Returns
transferable
Get Signature
get transferable():
OscChannelTransferable
Serializable config for transferring this channel to a worker via postMessage.
Example
worker.postMessage({ ch: channel.transferable }, channel.transferList);Returns
transferList
Get Signature
get transferList():
Transferable[]
Array of transferable objects (MessagePorts) for the postMessage transfer list.
Example
worker.postMessage({ ch: channel.transferable }, channel.transferList);Returns
Transferable[]
Methods
classify()
classify(
oscData):OscCategory
Classify an OSC message to determine its routing.
Parameters
| Parameter | Type | Description |
| --------- | ------------ | ----------------- |
| oscData | Uint8Array | Encoded OSC bytes |
Returns
close()
close():
void
Close the channel and release its ports.
Returns
void
getAndResetMetrics()
getAndResetMetrics():
OscChannelMetrics
Get and reset local metrics (for periodic reporting).
Returns
getMetrics()
getMetrics():
OscChannelMetrics
Get current metrics snapshot.
Returns
nextNodeId()
nextNodeId():
number
Get the next unique node ID.
Thread-safe — can be called concurrently from multiple workers and no two callers will ever receive the same ID. IDs start at 1000 (0 is the root group, 1 is the default group, 2–999 are reserved for manual use).
Returns
number
A unique node ID (>= 1000)
send()
send(
oscData):boolean
Send an OSC message with automatic routing.
Classifies the message and routes it:
- bypass categories → sent directly to the AudioWorklet
- far-future bundles → routed to the prescheduler for timed dispatch
Parameters
| Parameter | Type | Description |
| --------- | ------------ | ----------------- |
| oscData | Uint8Array | Encoded OSC bytes |
Returns
boolean
true if sent successfully
sendDirect()
sendDirect(
oscData):boolean
Send directly to worklet without classification or metrics tracking.
Parameters
| Parameter | Type | Description |
| --------- | ------------ | ----------------- |
| oscData | Uint8Array | Encoded OSC bytes |
Returns
boolean
true if sent successfully
sendToPrescheduler()
sendToPrescheduler(
oscData):boolean
Send to prescheduler without classification.
Parameters
| Parameter | Type | Description |
| --------- | ------------ | ----------------- |
| oscData | Uint8Array | Encoded OSC bytes |
Returns
boolean
true if sent successfully
fromTransferable()
staticfromTransferable(data):OscChannel
Reconstruct an OscChannel from data received via postMessage in a worker.
Parameters
| Parameter | Type | Description |
| --------- | ----------------------------------------------------- | --------------------------------------------------- |
| data | OscChannelTransferable | The transferable config from channel.transferable |
Returns
Example
// In a Web Worker:
self.onmessage = (e) => {
const channel = OscChannel.fromTransferable(e.data.ch);
channel.send(oscBytes);
};Variables
Patterns
Scheduling with OSC Bundles
Full-Precision NTP Timestamps
When scheduling bundles, a plain JavaScript number loses sub-microsecond precision because IEEE 754 float64 only has 52 mantissa bits — not enough for NTP's full 64-bit range. Use a [seconds, fraction] uint32 pair for lossless timestamps:
// Float — easy but loses precision at large NTP values
const ntpTime = SuperSonic.osc.ntpNow();
SuperSonic.osc.encodeBundle(ntpTime + 0.5, [
["/s_new", "sonic-pi-beep", 1001, 0, 0],
]);
// Uint32 pair — full 64-bit precision, no float loss
SuperSonic.osc.encodeBundle([3913056000, 2147483648], [
["/s_new", "sonic-pi-beep", 1001, 0, 0],
["/n_set", 1001, "amp", 0.5],
]);TimeTag formats accepted by encodeBundle():
| Value | Meaning |
|---|---|
| 1, null, undefined | Execute immediately |
| [seconds, fraction] | NTP uint32 pair — preserves full 64-bit precision |
| Number (e.g. 3913056000.5) | NTP float — seconds since 1900 (fractional part encoded) |
Sending OSC from Workers
Avoiding the Main Thread
When you send OSC from the main thread, it has to compete with everything else happening there - DOM updates, event handlers, animations, your application logic. For simple cases this is fine, but if you're running a sequencer, processing MIDI input, or doing other timing-sensitive work, you can end up with jitter or UI stuttering.
Web Workers let you move work off the main thread. The challenge is getting OSC from a worker to the AudioWorklet efficiently. You could send messages back to the main thread and have it forward them - but that adds latency and defeats the purpose of using a worker in the first place.
SuperSonic solves this with OscChannel - a transferable object that gives workers a direct line to the AudioWorklet.
OscChannel
Normally you'd just call supersonic.send() - but workers are separate threads with no access to your main thread's objects. The supersonic instance doesn't exist in the worker's world.
You could have the worker send messages back to the main thread via postMessage, and have the main thread forward them to scsynth. But that puts the main thread back in the middle of every message - defeating the purpose of using a worker.
The solution is to create a direct channel from the worker to scsynth running in the AudioWorklet. SuperSonic gives you an OscChannel for exactly this.
You create an OscChannel with createOscChannel and then you need to transfer it to the worker.
Note: Transferring is critical because the core internal comms mechanisms cannot be copied to the worker with a standard postMessage, they must be explicitly transferred. This enables the worker to have full and unique ownership of the newly created OscChannel.
Here's how you create an OscChannel and transfer it to a worker:
// Main thread - create and transfer
const channel = supersonic.createOscChannel();
worker.postMessage(
{ type: "init", channel: channel.transferable },
channel.transferList
);The second argument to postMessage is optional - when provided, it lists which objects to transfer rather than copy.
In your worker, handle this as an init message and reconstruct the channel:
// Worker thread
import { OscChannel } from "supersonic-scsynth";
let channel = null;
self.onmessage = (event) => {
if (event.data.type === "init") {
channel = OscChannel.fromTransferable(event.data.channel);
}
};
// Now you can send OSC directly to the AudioWorklet
channel.send(oscBytes);Using a type field lets you handle different message types cleanly - for example init, start, stop (see the sequencer example below).
Multiple Workers
Each call to createOscChannel() returns a new channel with a unique source ID. An optional blocking parameter (SAB mode only) controls whether Atomics.wait() is used for guaranteed ring buffer delivery (default: true for workers where sourceId !== 0, false for main thread). Set to false for AudioWorklet use. This means you can have multiple workers all sending OSC independently - they don't need to coordinate with each other, and their messages can be traced back to their source in metrics and logs.
const sequencerChannel = supersonic.createOscChannel(); // sourceId: 1
const lfoChannel = supersonic.createOscChannel(); // sourceId: 2
const midiChannel = supersonic.createOscChannel(); // sourceId: 3
sequencerWorker.postMessage({ channel: sequencerChannel.transferable }, sequencerChannel.transferList);
lfoWorker.postMessage({ channel: lfoChannel.transferable }, lfoChannel.transferList);
midiWorker.postMessage({ channel: midiChannel.transferable }, midiChannel.transferList);SAB vs PM Mode
OscChannel works identically in both modes - it detects the active mode and uses the appropriate communication method automatically. Your worker code doesn't need to know or care which mode is active.
See Communication Modes for details on choosing between SAB and postMessage modes.
Message Routing
OscChannel.send() automatically classifies messages and routes them appropriately:
| Message Type | Where It Goes | Why | | ---------------------------------- | ---------------------- | ----------------------------------- | | Regular messages (not bundles) | Direct to AudioWorklet | No timing requirements | | Immediate bundles (timetag 0 or 1) | Direct to AudioWorklet | Execute now | | Near-future bundles (within 500ms) | Direct to AudioWorklet | Close enough to buffer | | Late bundles (in the past) | Direct to AudioWorklet | Execute immediately | | Far-future bundles (>500ms ahead) | Prescheduler | Hold until closer to execution time |
The 500ms threshold is configurable via bypassLookaheadMs in the SuperSonic constructor.
OscChannel API
Methods
| Method | Description |
| ---------------------- | ------------------------------------------ |
| send(oscBytes) | Send with automatic routing |
| sendDirect(oscBytes) | Force direct send (bypass prescheduler) |
| classify(oscBytes) | Get routing classification without sending |
| close() | Release resources |
Properties
| Property | Description |
| --------------- | ------------------------------------------------------------- |
| mode | 'sab' or 'postMessage' |
| transferable | Data for postMessage transfer |
| transferList | Transferable objects array |
| getCurrentNTP | (setter) Set NTP time source function for timestamp classification |
Static Methods
| Method | Description |
| ----------------------------------- | ----------------------------- |
| OscChannel.fromTransferable(data) | Reconstruct channel in worker |
Example: Sequencer Worker
Here's a worker that runs a simple step sequencer:
// sequencer-worker.js
import { OscChannel, osc } from "supersonic-scsynth";
let channel = null;
let running = false;
let step = 0;
let bpm = 120;
const pattern = [60, 62, 64, 65, 67, 65, 64, 62]; // Notes to play
self.onmessage = (event) => {
const { type, data } = event.data;
if (type === "init") {
channel = OscChannel.fromTransferable(data.channel);
} else if (type === "start") {
running = true;
step = 0;
tick();
} else if (type === "stop") {
running = false;
} else if (type === "bpm") {
bpm = data.bpm;
}
};
function tick() {
if (!running || !channel) return;
const note = pattern[step % pattern.length];
const msg = osc.encodeMessage("/s_new", [
"sonic-pi-beep", -1, 0, 0,
"note", note,
"amp", 0.5
]);
channel.send(msg);
step++;
const msPerBeat = 60000 / bpm;
setTimeout(tick, msPerBeat / 4); // 16th notes
}Lifecycle & Recovery
Setup vs Ready
The setup event fires after init/recover completes, before ready. Async handlers are awaited. Use it for any persistent audio infrastructure that needs to exist on both initial boot and after recovery:
- Groups — node tree organization
- FX chains — reverb, filters, compressors
- Bus routing — synths that read/write to audio buses
- Persistent synths — always-on nodes like analyzers or mixers
supersonic.on("setup", async () => {
// Create group structure
supersonic.send("/g_new", 100, 0, 0); // synths group
supersonic.send("/g_new", 101, 1, 0); // fx group (after synths)
// Create FX chain
supersonic.send("/s_new", "sonic-pi-fx_reverb", 2000, 0, 101,
"in_bus", 20, "out_bus", 0, "mix", 0.3);
await supersonic.sync();
});Why setup instead of ready? When recover() falls through to a full reload(), WASM memory is destroyed and recreated, so all nodes are lost. The setup event lets you rebuild consistently on both initial boot and after recovery, regardless of transport mode. See Communication Modes for more on SAB vs postMessage.
Tab Visibility & Recovery
Use visibilitychange to recover when the user switches back to your tab:
document.addEventListener("visibilitychange", async () => {
if (!document.hidden) {
await supersonic.recover();
}
});Resume vs Reload
resume() is fast but only works if the worklet is still alive — it calls purge() to flush stale scheduled messages, resumes the AudioContext, and resyncs timing. reload() is a full restart. When you don't know which is needed, use recover() — or handle it manually:
if (await supersonic.resume()) {
console.log("Quick resume worked, nodes preserved");
} else {
console.log("Worklet was killed, need full reload");
await supersonic.reload();
}Audio Routing
Connecting Microphone Input
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
const micSource = audioContext.createMediaStreamSource(stream);
// Connect mic to SuperSonic input
micSource.connect(supersonic.node.input);
// Audio flows through scsynth's input buses (bus 2+ by default)
// Use In.ar(2) in a synthdef to read the mic signalConnecting to an Analyser
const analyser = supersonic.audioContext.createAnalyser();
// Connect SuperSonic output to analyser (in addition to speakers)
supersonic.node.connect(analyser);
// Read frequency data
const data = new Uint8Array(analyser.frequencyBinCount);
analyser.getByteFrequencyData(data);Samples
Loading from Different Sources
// By name (uses sampleBaseURL)
await supersonic.loadSample(0, "loop_amen.flac");
// By full path/URL
await supersonic.loadSample(0, "./custom/my-sample.wav");
// From user-selected file
const file = document.querySelector('input[type="file"]').files[0];
await supersonic.loadSample(0, file);
// From ArrayBuffer (e.g., fetched manually)
const response = await fetch("./audio/sample.wav");
const arrayBuffer = await response.arrayBuffer();
await supersonic.loadSample(0, arrayBuffer);
// Load partial sample (frames 1000-2000)
await supersonic.loadSample(0, "long-sample.flac", 1000, 1000);Deduplication with sampleInfo()
Use sampleInfo() to get a SHA-256 content hash without allocating a buffer, then check before loading:
const info = await supersonic.sampleInfo("kick.wav");
console.log(info.duration, info.numChannels, info.sampleRate);
// Check for duplicates
const loaded = supersonic.getLoadedBuffers();
if (loaded.some(b => b.hash === info.hash)) {
console.log("Already loaded, skipping");
} else {
await supersonic.loadSample(nextBufnum, "kick.wav");
}Constructor Options
| Property | Type | Description | Required |
| ----------------------------------------------------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------- |
| activityEvent? | ActivityLineConfig | Line length limits for activity events emitted to listeners. | |
| audioContext? | AudioContext | Provide your own AudioContext instead of letting SuperSonic create one. | |
| audioContextOptions? | AudioContextOptions | Options passed to new AudioContext(). Ignored if audioContext is provided. | |
| autoConnect? | boolean | Auto-connect the AudioWorkletNode to the AudioContext destination. Default: true. | |
| baseURL? | string | Convenience shorthand when all assets (WASM, workers, synthdefs, samples) are co-located. | Yes* |
| bufferGrowIncrement? | number | Bytes to grow the buffer pool per growth event. Default: 32MB. | |
| bypassLookaheadMs? | number | Bundles scheduled within this many ms of now are dispatched immediately for lowest latency. Bundles further in the future are held and dispatched closer to their scheduled time. Default: 500. | |
| coreBaseURL? | string | Base URL for GPL assets: WASM and AudioWorklet (supersonic-scsynth-core package). Defaults to baseURL. | |
| debug? | boolean | Enable all debug console logging. Default: false. | |
| debugOscIn? | boolean | Log incoming OSC messages to console. Default: false. | |
| debugOscOut? | boolean | Log outgoing OSC messages to console. Default: false. | |
| debugScsynth? | boolean | Log scsynth debug output to console. Default: false. | |
| fetchMaxRetries? | number | Max fetch retries when loading assets. Default: 3. | |
| fetchRetryDelay? | number | Base delay between retries in ms (exponential backoff). Default: 1000. | |
| maxBufferMemory? | number | Maximum buffer pool capacity in bytes. Pool grows on demand up to this limit. Default: 256MB. | |
| mode? | TransportMode | Transport mode. - 'postMessage' (default) — works everywhere, no special headers needed - 'sab' — lowest latency, requires Cross-Origin-Opener-Policy and Cross-Origin-Embedder-Policy headers See docs/MODES.md for a full comparison of communication modes. | |
| preschedulerCapacity? | number | Max pending events in the JS prescheduler. Default: 65536. | |
| sampleBaseURL? | string | Base URL for audio sample files (used by SuperSonic.loadSample). | |
| scsynthOptions? | ScsynthOptions | Engine options passed to scsynth World_New(). | |
| snapshotIntervalMs? | number | How often to snapshot metrics/tree in postMessage mode (ms). | |
| synthdefBaseURL? | string | Base URL for synthdef files (used by SuperSonic.loadSynthDef). | |
| wasmBaseURL? | string | Base URL for WASM files. Defaults to coreBaseURL + 'wasm/'. | |
| wasmUrl? | string | Full URL to the WASM binary. Overrides wasmBaseURL. | |
| workerBaseURL? | string | Base URL for MIT worker scripts. Defaults to baseURL + 'workers/'. | |
| workletUrl? | string | Full URL to the AudioWorklet script. Overrides coreBaseURL. | |
Required unless both coreBaseURL/workerBaseURL and wasmBaseURL are provided.
Server Options
| Property | Type | Description | Default | Range |
| ----------------------------------------------------------- | ---------- | ------------------------------------------------------------------------------ | ------- | -------------- |
| bufLength? | 128 | Audio buffer length — must be 128 (WebAudio API constraint). | 128 | 128 (fixed) |
| loadGraphDefs? | 0 | 1 | Auto-load synthdefs from disk: 0 or 1. Default: 0. | 0 | 0–1 |
| maxGraphDefs? | number | Max synth definitions. Default: 1024. | 1024 | 1+ |
| maxNodes? | number | Max synthesis nodes — synths + groups. Default: 1024. | 1024 | 1+ |
| maxWireBufs? | number | Max wire buffers for internal UGen routing. Default: 64. | 64 | 1+ |
| memoryLocking? | boolean | Memory locking — not applicable in browser. Default: false. | false | — |
| numAudioBusChannels? | number | Audio bus channels for routing between synths. Default: 128. | 128 | 1+ |
| numBuffers? | number | Max audio buffers (1–65535). Default: 1024. | 1024 | 1–65535 |
| numControlBusChannels? | number | Control bus channels for control-rate data. Default: 4096. | 4096 | 1+ |
| numInputBusChannels? | number | Hardware input channels. Default: 2 (stereo). | 2 | 0+ |
| numOutputBusChannels? | number | Hardware output channels (1–128). Default: 2 (stereo). | 2 | 1–128 |
| numRGens? | number | Random number generators per synth. Default: 64. | 64 | 1+ |
| preferredSampleRate? | number | Preferred sample rate. 0 = use AudioContext default (typically 48000). | 0 | 0, 8000–384000 |
| realTime? | boolean | Clock source. Always false in SuperSonic (externally clocked by AudioWorklet). | false | — |
| realTimeMemorySize? | number | Real-time memory pool in KB for synthesis allocations. Default: 8192 (8MB). | 8192 | 1+ |
| verbosity? | number | Debug verbosity: 0 = quiet, 1 = errors, 2 = warnings, 3 = info, 4 = debug. | 0 | 0–4 |
scsynth Command Quick Reference
Send commands using send() which auto-detects types:
supersonic.send("/s_new", "sonic-pi-beep", 1000, 0, 0, "note", 60);Or directly send OSC bytes that you have already pre-encoded via sendOSC():
supersonic.sendOSC(oscBytes);| Command | Description |
| -------------------------------------------- | -------------------------------------------------- |
| Top-Level | |
| /notify | Register for node event notifications |
| /status | Query server status (UGens, synths, CPU) |
| /sync | Wait for async commands to complete |
| /version | Query server version info |
| /rtMemoryStatus | Query realtime memory usage |
| Synth Definitions | |
| /d_recv | Load a synthdef from binary data |
| /d_free | Free loaded synthdefs by name |
| Nodes | |
| /n_free | Delete nodes |
| /n_run | Turn nodes on or off |
| /n_set | Set node control values |
| /n_setn | Set sequential control values |
| /n_fill | Fill controls with a single value |
| /n_map | Map controls to control buses |
| /n_mapn | Map sequential controls to control buses |
| /n_mapa | Map controls to audio buses |
| /n_mapan | Map sequential controls to audio buses |
| /n_before | Move node before another |
| /n_after | Move node after another |
| /n_query | Query node info |
| /n_trace | Debug trace node execution |
| /n_order | Reorder nodes within a group |
| Synths | |
| /s_new | Create a new synth |
| /s_get | Get synth control values |
| /s_getn | Get sequential synth control values |
| /s_noid | Remove synth ID tracking |
| Groups | |
| /g_new | Create a new group |
| /p_new | Create a parallel group |
| /g_head | Move node to head of group |
| /g_tail | Move node to tail of group |
| /g_freeAll | Free all nodes in group |
| /g_deepFree | Recursively free all synths in group |
| /g_dumpTree | Print group tree (debug) |
| /g_queryTree | Query group tree structure |
| Buffers | |
| /b_alloc | Allocate an empty buffer |
| /b_allocRead | Allocate buffer and load audio file |
| /b_allocReadChannel | Allocate buffer and load specific channels |
| /b_free | Free a buffer |
| /b_zero | Zero buffer contents |
| /b_set | Set individual samples |
| /b_setn | Set sequential samples |
| /b_fill | Fill samples with a value |
| /b_gen | Generate buffer contents (sine, cheby, etc.) |
| /b_query | Query buffer info |
| /b_get | Get sample values |
| /b_getn | Get sequential sample values |
| Control Buses | |
| /c_set | Set control bus values |
| /c_setn | Set sequential bus values |
| /c_fill | Fill buses with a value |
| /c_get | Get bus values |
| /c_getn | Get sequential bus values |
| SuperSonic Extensions | |
| /b_allocFile | Load audio from inline file data (SuperSonic only) |
Parameter Types
| Notation | Type | Description |
| -------- | ------- | ------------------------ |
| int | Integer | 32-bit signed integer |
| float | Float | 32-bit floating point |
| double | Double | 64-bit floating point |
| string | String | Null-terminated string |
| bytes | Blob | Binary data (byte array) |
Repetition
N × indicates the parameter can be repeated N times. For example:
// /n_free takes N node IDs
supersonic.send("/n_free", 1000, 1001, 1002);Node IDs
- Use
-1to have the server auto-generate a unique node ID - Node ID
0is the root group (always exists) - Positive integers are user-assigned IDs
Add Actions
Used when creating or moving nodes:
| Value | Action | Description | | ----- | ------- | --------------------------- | | 0 | head | Add to head of target group | | 1 | tail | Add to tail of target group | | 2 | before | Add before target node | | 3 | after | Add after target node | | 4 | replace | Replace target node |
Control Values
Controls can be set by index (integer) or name (string). Values can be:
floatorint- Direct value"cN"- Map to control bus N (e.g.,"c0")"aN"- Map to audio bus N (e.g.,"a0")
Asynchronous Commands
Commands marked Async execute on a background thread. They reply with /done on success or /fail on error. Use /sync to wait for all async commands to complete.
These commands don't work in SuperSonic due to browser/AudioWorklet constraints.
For a complete guide to all differences between SuperSonic and scsynth—including unsupported UGens, architectural differences, and error handling—see SCSYNTH_DIFFERENCES.md.
Scheduling and Debug Commands
| Command | Reason |
| ------------- | --------------------------------------------------------------------------------------------------------------------- |
| /clearSched | Use cancelAll() or the fine-grained cancelTag(), cancelSession(), cancelSessionTag() methods instead |
| /error | SuperSonic always enables error notifications so you never miss a /fail message |
| /quit | Use destroy() to shut down the SuperSonic instance |
Plugin Commands
| Command | Status |
| -------- | -------------------------------------------------------------------- |
| /cmd | No commands currently registered |
| /u_cmd | No UGens currently define commands |
These commands allow plugins to register custom functionality beyond the standard OSC API. None of the built-in UGens use them, but the mechanism exists if compelling use cases emerge. If you have a need for custom plugin commands, open an issue describing your use case.
Filesystem Commands
No filesystem in browser/WASM, so file-based commands aren't available:
| Command | Alternative |
| -------------------- | ------------------------------------------------------------------------- |
| /d_load | loadSynthDef() or /d_recv with bytes |
| /d_loadDir | loadSynthDefs() |
| /b_read | loadSample() |
| /b_readChannel | loadSample() |
| /b_write | Not available |
| /b_close | Not available |
Buffer Commands
| Command | Reason |
| ----------------- | -------------------------------------------------------------------------------- |
| /b_setSampleRate| Not implemented - WebAudio automatically resamples buffers to context sample rate |
Use the JavaScript API to load assets - it fetches via HTTP and sends the data to scsynth:
await supersonic.loadSynthDef("sonic-pi-beep");
await supersonic.loadSample(0, "loop_amen.flac");For full details on every command (parameters, replies, examples), see the scsynth Command Reference.
Full Method Reference
Methods
cancelAll()
cancelAll():
void
Cancel all scheduled messages in the JS prescheduler.
Returns
void
cancelSession()
cancelSession(
sessionId):void
Cancel all scheduled messages for a session.
Parameters
| Parameter | Type | Description |
| ----------- | -------- | ----------------- |
| sessionId | string | Session to cancel |
Returns
void
cancelSessionTag()
cancelSessionTag(
sessionId,runTag):void
Cancel scheduled messages matching both a session and run tag.
Parameters
| Parameter | Type | Description |
| ----------- | -------- | -------------------------------- |
| sessionId | string | Session to match |
| runTag | string | Tag to match within that session |
Returns
void
cancelTag()
cancelTag(
runTag):void
Cancel all scheduled messages with the given run tag. Only affects messages in the JS prescheduler (not yet dispatched to WASM).
Parameters
| Parameter | Type | Description |
| --------- | -------- | ------------- |
| runTag | string | Tag to cancel |
Returns
void
createOscChannel()
createOscChannel(
options?):OscChannel
Create an OscChannel for direct worker-to-worklet communication.
The returned channel can be transferred to a Web Worker, allowing that worker to send OSC directly to the AudioWorklet without going through the main thread. Works in both SAB and postMessage modes.
The blocking option defaults to true for worker channels (sourceId !== 0)
and false for main thread. Set to false for AudioWorkletProcessor use.
In postMessage mode this has no effect.
For AudioWorkletProcessor use, import from 'supersonic-scsynth/osc-channel'
which avoids DOM APIs unavailable in the worklet scope.
See docs/WORKERS.md for the full workers guide.
Parameters
| Parameter | Type | Description |
| ------------------- | -------------------------------------------------- | ------------------------------------------------------- |
| options? | { blocking?: boolean; sourceId?: number; } | Channel options |
| options.blocking? | boolean | Whether sends block until the worklet reads the message |
| options.sourceId? | number | Numeric source ID (0 = main thread, 1+ = workers) |
Returns
Example
const channel = sonic.createOscChannel();
myWorker.postMessage(
{ channel: channel.transferable },
channel.transferList,
);destroy()
destroy():
Promise<void>
Destroy the engine completely. The instance cannot be re-used.
Calls shutdown then clears the WASM cache and all event listeners.
Emits 'destroy'.
Returns
Promise<void>
getCaptureFrames()
getCaptureFrames():
number
Get number of audio frames captured so far.
Returns
number
getInfo()
getInfo():
SuperSonicInfo
Get engine info: sample rate, memory layout, capabilities, and version.
Returns
Example
const info = sonic.getInfo();
console.log(`Sample rate: ${info.sampleRate}Hz`);
console.log(`Boot time: ${info.bootTimeMs}ms`);
console.log(`Version: ${info.version}`);getLoadedBuffers()
getLoadedBuffers():
LoadedBufferInfo[]
Get info about all loaded audio buffers.
Returns
Example
const buffers = sonic.getLoadedBuffers();
for (const buf of buffers) {
console.log(`Buffer ${buf.bufnum}: ${buf.duration.toFixed(1)}s, ${buf.source}`);
}getMaxCaptureDuration()
getMaxCaptureDuration():
number
Get maximum capture duration in seconds.
Returns
number
getMetrics()
getMetrics():
SuperSonicMetrics
Get current metrics as a named object.
This is a cheap local memory read in both SAB and postMessage modes — no IPC
or copying. Safe to call from requestAnimationFrame.
See docs/METRICS.md for the full metrics guide.
Returns
Example
const m = sonic.getMetrics();
console.log(`Messages sent: ${m.oscOutMessagesSent}`);
console.log(`Scheduler depth: ${m.scsynthSchedulerDepth}`);getMetricsArray()
getMetricsArray():
Uint32Array
Get metrics as a flat Uint32Array for zero-allocation reading.
Returns the same array reference every call — values are updated in-place. Use SuperSonic.getMetricsSchema for offset mappings.
Returns
Uint32Array
Example
const schema = SuperSonic.getMetricsSchema();
const arr = sonic.getMetricsArray();
const sent = arr[schema.metrics.oscOutMessagesSent.offset];getRawTree()
getRawTree():
RawTree
Get the node tree in flat format with linkage pointers.
More efficient than getTree for serialization or custom rendering.
Returns
getSnapshot()
getSnapshot():
Snapshot
Get a diagnostic snapshot with metrics, node tree, and memory info.
Useful for capturing state for bug reports or debugging timing issues.
Returns
getSystemReport()
getSystemReport():
SystemReport
Get a comprehensive system performance report.
Includes hardware info, audio configuration, Chrome playbackStats (if available), a cross-browser audio health percentage, and a human-r
