Getting started
Create a project, connect the UI, add the browser runtime, test, and build.
This guide creates a JUCE plugin, explains the generated project, and shows how to add the browser runtime. To explore Soundor without a native toolchain, use the repository's browser example.
Requirements
Use Node.js ^22.18.0 || >=24.11.0, and pnpm or npm. Soundor supports these two package
managers only: Yarn's Plug'n'Play leaves no node_modules for native builds to
find packages in, and bun is untested.
Native builds also need CMake, a C++20 compiler, and a JUCE checkout. Plugin builds use Ninja on macOS and Linux, and Visual Studio on Windows. Source engine builds additionally need the tools described in Soundor Engine.
Soundor discovers JUCE in standard locations, including /opt/JUCE on Linux,
or through jucePath or JUCE_DIR. You provide the JUCE checkout; Soundor never
downloads it. Run soundor doctor to check discovery and toolchain setup. See
JUCE resolution for the
complete search order.
The browser runtime needs Node.js and a browser, with no C++ build. Audio starts after a user gesture in the browser host.
Create a project
pnpm create soundor-app@0.13.0 my-plugin
cd my-plugin
pnpm doctor
pnpm devWith npm, run npm create soundor-app@0.13.0 my-plugin,
then npm run doctor and npm run dev. The version is the current
release; naming it keeps the scaffolder, and so the new project, on the version
these docs describe.
The scaffolder prompts for the runtime and package manager, copies the template,
installs dependencies, and runs soundor init. It currently offers the JUCE
runtime, with VST3 and Standalone formats. Native development builds and launches
the Standalone; it needs a graphical desktop to open the window.
Soundor is pre-1.0, and a minor release (0.10 to 0.11) can change its APIs. The new project therefore pins each Soundor package to the exact version the scaffolder was released with. Upgrade them together, after reading their changelogs.
The template includes a gain control UI and a JUCE processor scaffold. Implement your audio behavior in the processor; declaring a parameter does not apply it to the audio automatically.
Understand the files
my-plugin/
soundor.config.ts plugin identity, parameters, native API, runtimes
src/main.tsx shared TypeScript/React UI
tsconfig.json UI types, including generated declarations
runtimes/juce/ your CMake project, processor, and editor
.soundor/
generated/ generated types and runtime framework
ui/ bundled UI and assets
dist/ runtime build outputYou own src/, the config, and the files under runtimes/. Soundor owns
.soundor/, which is disposable build output. init preserves existing host
files; gen rewrites generated files. See Project lifecycle.
Declare the plugin contract
The scaffold creates a config like this:
import { defineSoundorConfig } from '@soundor/config';
import { juceRuntime } from '@soundor/juce-runtime';
export default defineSoundorConfig({
plugin: { id: 'com.example.my-plugin', name: 'My Plugin' },
runtimes: [juceRuntime({ formats: ['vst3', 'standalone'] })],
parameters: [
{ type: 'float', id: 'gain', label: 'Gain', min: 0, max: 1, default: 0.5 },
],
});Choose a stable, globally unique plugin.id before release. Hosts and saved
sessions use its derived identities. plugin.name is the display name.
Run pnpm gen after editing the contract to refresh editor types. Restart
pnpm dev after config or native code changes. Ordinary UI edits rebuild
automatically during development.
Published JUCE runtimes use a matching prebuilt engine by default. A checkout of
this monorepo has no published-engine manifest, so its examples use
juceRuntime({ engine: { mode: 'source' } }). See Engine modes.
Connect the UI
Replace src/main.tsx with a small parameter-aware UI:
import { Pressable, Text, View, render, useParameter } from '@soundor/react';
import { parameters } from 'soundor:parameters';
function App() {
const gain = useParameter(parameters.gain);
return (
<View style={{ flex: 1, padding: 20, gap: 12 }}>
<Text>Gain: {gain.toFixed(2)}</Text>
<Pressable
accessibilityLabel="Reset gain"
onPress={() => {
parameters.gain.beginGesture();
try {
parameters.gain.set(parameters.gain.info.default);
} finally {
parameters.gain.endGesture();
}
}}
>
<Text>Reset</Text>
</Pressable>
</View>
);
}
render(<App />);useParameter subscribes to the runtime's parameter value. Calling set
updates that parameter; gestures tell the native host which edits belong to a
user action. For continuous controls and host automation, read
Parameters. The React reference covers styles,
events, canvases, focus, accessibility, and animations.
Add the browser runtime
Choose the web runtime along with JUCE when you create the project, and it is
set up already: run it with pnpm dev:web.
To add it to an existing project, install the web runtime:
pnpm add @soundor/web-runtimeImport webRuntime from @soundor/web-runtime in soundor.config.ts and append
webRuntime() to the existing runtimes array. Then initialize it, once:
pnpm exec soundor init weband give package.json the scripts a new project with both runtimes has (the
Web host's code has its own tsconfig, so typecheck checks it separately):
"build:juce": "soundor build juce",
"build:web": "soundor build web",
"dev:web": "soundor dev web",
"typecheck": "soundor gen && tsc -b && tsc -p runtimes/web"Then run it with pnpm dev:web.
Open the URL printed by the CLI. Your shared src/main.tsx runs inside the
browser host. runtimes/web/src/audio.ts is where you connect a Web Audio graph;
src/native.ts in that same host directory implements your configured native
API. These are separate implementations from the JUCE processor and C++ API.
For the declared gain parameter, an audio implementation can be:
import type { WebAudioSetup } from '@soundor/web-runtime/client';
import { parameters } from 'soundor:parameters';
export const setupAudio: WebAudioSetup = ({ context, input, output }) => {
const gain = context.createGain();
gain.gain.value = parameters.gain.get();
const unsubscribe = parameters.gain.subscribe((value) =>
gain.gain.setTargetAtTime(value, context.currentTime, 0.01),
);
input.connect(gain);
gain.connect(output);
return () => {
unsubscribe();
input.disconnect();
gain.disconnect();
};
};Press Play in the host to enable audio. The web runtime guide explains the host, storage, transport, TypeScript native API, and browser limitations.
Test and build
The testing guide explains how to install the Vitest integration and Testing Library. Tests can render the real React components against a simulated runtime without opening a plugin or a browser.
pnpm typecheck
pnpm buildpnpm build builds every configured runtime; with several, pnpm build:juce
and pnpm build:web build one. Native output lives under
.soundor/dist/juce/; the browser build is a static site under
.soundor/dist/web/. Platform-specific details are in the
JUCE guide and web guide.
Try the repository example
From a checkout of Soundor, build the workspace packages and start the basic example in the browser:
pnpm install
pnpm build
pnpm --filter basic dev:webThis path needs no JUCE or native engine build. The example already includes both host projects, a gain processor for each, and application tests. See Basic example for its file map and Contributing for repository checks.