@codex-music/audio-sdk
The public entry point: engine selection, host bridge, and shared browser audio/MIDI sessions.
createAudioEngine()throws today. Backends are being wired in order — shared-WASM in the browser first, then the nativecodexHosttransport. The types below are real and stable; only the runtime is pending.
This is the one package an application imports. It exists to answer a single question — which transport carries my audio in this environment? — and to keep that answer out of your component code.
What it exports
The module surface includes createAudioEngine, isNativeHost,
AudioSessionController, the CodexHostBridge interface, and an
export * from "@codex-music/audio-contracts"
re-export. That last line matters more than it looks: you never need to add
@codex-music/audio-contracts to your package.json. ParamSchema,
ParamDescriptor, AudioKernel, KernelIO, AudioEvent, GraphPlan,
AudioEngine, EngineBackend, plus bridgeParams, normalize, denormalize
and sortEvents all come through the SDK unchanged. One import specifier for
the whole authoring surface.
Shared browser audio and MIDI session
AudioSessionController normalizes the browser-facing controls shared by the
main website and local plugin workbench. It owns input and output selection,
start and stop state, Web MIDI discovery, MIDI input binding, MIDI output,
sample and microphone sources, monitor recording, analyser access, status, and
errors.
import { AudioSessionController } from "@codex-music/audio-sdk";
const session = new AudioSessionController({
inputs: [
{ id: "mic", label: "Microphone", kind: "microphone" },
{ id: "keys", label: "Computer keyboard", kind: "keyboard" },
],
outputs: [{ id: "speakers", label: "System output", kind: "speakers" }],
});
await session.start();
session.noteOn(60, 0.8);
session.noteOff(60);
The built-in runtime is appropriate for the local workbench. A website or
other host that already owns an engine supplies runtime.start, stop,
selectInput, selectOutput, noteOn, and noteOff. The state machine and UI
stay the same while the audio implementation changes.
@codex-music/site-ui/components/audio-session-controls is the shared shadcn
view for this controller. Updating its labels, device menus, error handling, or
accessibility updates both the website and runner.
createAudioEngine
import { createAudioEngine } from "@codex-music/audio-sdk";
import type { AudioEngineOptions } from "@codex-music/audio-sdk";
const options: AudioEngineOptions = {
backend: "shared-wasm",
sampleRate: 48000,
graph: {
schema: MY_PEDAL_SCHEMA,
nodes: [{ kind: "scfx_", params: {} }],
},
};
const engine = createAudioEngine(options);
AudioEngineOptions has exactly three fields: backend (required),
sampleRate (optional — the transport picks a device rate otherwise), and
graph, a GraphPlan pairing your ParamSchema with an ordered node list.
The returned AudioEngine is host-neutral: send(events) for a batch of
frame-offset AudioEvents, setParam(path, value) with the dotted path from
your schema, noteOn / noteOff, render(frames) for an offline pull,
and start() / close() for the real-time lifecycle. Every backend implements
the same interface, which is what lets a UI component be written once.
Backend selection
EngineBackend is a closed union of five values, and you pass one explicitly:
| Backend | Transport |
|---|---|
"tone" | Tone.js reference path, for A/B against the legacy graph. |
"shared-wasm" | AudioWorklet + the ScriptC WASM reactor. The browser default. |
"hybrid" | Web Audio nodes with kernel inserts where they exist. |
"native" | Inside the plugin shell — bridged to the compiled engine. |
"offline-native" | Node/CI renders through @codex-music/audio-native. |
There is no "auto". Selection is a decision your app makes, usually one
ternary against isNativeHost(), because "which transport am I on" is
information your UI often wants anyway — for a badge, for a preset-export
button, for a hidden dev panel.
isNativeHost and the codexHost bridge
import { createAudioEngine, isNativeHost } from "@codex-music/audio-sdk";
const engine = createAudioEngine({
backend: isNativeHost() ? "native" : "shared-wasm",
graph: { schema: MY_PEDAL_SCHEMA, nodes: [{ kind: "scfx_", params: {} }] },
});
isNativeHost() is a one-line existence check for window.codexHost. The
native plugin shell injects that object into the webview before your bundle
loads, so the check is already true by the time your first module evaluates —
no race, no await, no readiness event to subscribe to.
The bridge itself is two methods:
export interface CodexHostBridge {
postMessage(message: unknown): void;
onMessage(handler: (message: unknown) => void): void;
}
The SDK augments the global Window type with an optional codexHost?: CodexHostBridge, so TypeScript knows about it in any file that imports the
SDK. Messages crossing that boundary are the JSON protocol union documented in
plugin-shell-kit — codex.ready, codex.setParam,
codex.hello, and the rest. You will rarely touch postMessage yourself; the
native backend speaks the protocol on your behalf. It is exported because
tooling, dev overlays, and tests occasionally need to listen in.
What is real today
createAudioEngine() throws with a message naming the workstream. Everything
around it is landed: the contracts are complete, the native shell exists and
passes the VST3 validator, and @codex-music/plugin-shell-kit generates its
param table and bridge from a real manifest. Write your UI against this API now
and it will keep compiling when the transports arrive — that is the point of
shipping the types first. See How it works for the
pipeline and audio-contracts for the type reference.