Soundorv0.13.0

Project lifecycle

Config → generated contract → UI bundle → runtime; file ownership and reloads.

Soundor's CLI loads soundor.config.ts, generates the shared contract, bundles the UI for development or production, and invokes the selected runtime. Each runtime exposes the same five lifecycle phases.

Commands and phases

CommandPurpose
soundor init [runtime]Scaffold user-owned host files; preserve files that already exist
soundor genRegenerate shared declarations and runtime-specific framework files
soundor gen --checkVerify generated output matches the current contract without writing it
soundor dev <runtime>Generate development bindings, bundle and watch the UI, and run one runtime's development workflow
soundor build [runtime]Generate production bindings, bundle the UI, and build runtime output
soundor doctorCheck the environment, configuration, and all configured runtimes' prerequisites

init and build operate on all configured runtimes when their selector is omitted. dev requires a runtime: soundor dev juce or soundor dev web. For example, soundor build web builds only the browser host. Generation runs before development and production builds; the standalone gen command is also useful for refreshing types in an editor.

File ownership

LocationOwnerEditing rule
soundor.config.tsYouDeclare identity, parameters, native API, and runtime options
src/YouShared UI and application logic
runtimes/<id>/YouHost project, DSP, runtime-specific API implementations, build customization
.soundor/generated/SoundorRegenerated declarations, manifests, and framework; change the config or generator instead
.soundor/ui/SoundorDevelopment and production UI bundles, assets, and build metadata
.soundor/dist/<id>/Build toolsRuntime build trees and output
.soundor/dev/Development sessionBuild logs and session files

init and gen deliberately have different ownership rules. Re-running init can fill in missing files but never upgrades an existing user-owned processor or editor. Framework changes belong in the runtime package or generated code, so existing projects can receive them without overwriting custom code.

Configuration and generated types

Config loading bundles and evaluates soundor.config.ts, then validates the result. Runtime entries are factory results such as juceRuntime() and webRuntime(), rather than plain runtime IDs. Validation reports all problems with paths into the configuration.

Shared generation writes .soundor/generated/ declarations for parameters, the configured native API, runtime modules, Web globals, and imported images. Runtime generation adds its own implementation contract:

  • JUCE generates CMake integration, processor/editor bases, and C++ native API declarations and bindings under .soundor/generated/runtimes/juce/.
  • Web generates a manifest and the WebNativeApi interface under .soundor/generated/runtimes/web/.

The shared UI TypeScript project includes the generated declarations and uses lib: ["ES2023"] without DOM. Browser host files use a separate configuration with browser types. See CLI bundling and Configuration.

The UI bundle

The CLI finds the project's src/main.tsx entry, with .ts, .jsx, and .js alternatives, and bundles its npm imports with tsdown. soundor:* imports stay external for the runtime to supply.

Each build produces bundle.mjs, an ES module for the web, and bundle.js, the native module form lowered for Hermes. Images are bundled as content-addressed assets. Development output includes source maps; production output is minified and tree-shaken. process.env.NODE_ENV is replaced during bundling.

Native builds compile production JavaScript to Hermes bytecode and embed it in the plugin. Web builds pass the ES module to Vite, which builds the browser host and emits its static assets. Vite does not separately bundle the original shared UI source.

Development and reload

For JUCE, dev builds a debug plugin, uses the development UI files on disk, and launches the Standalone when configured. A successful UI rebuild destroys the old RuntimeHost and creates a fresh one. Timers, subscriptions, gestures, and pending work from the old UI end. Processor parameters and persisted data can outlive the UI; local React state cannot.

For the web runtime, Vite serves the host. A changed UI build ID reloads the page. Host-source edits follow Vite's own update behavior. In either runtime a failed UI rebuild reports an error and keeps the last working UI build.

Restart development after changes to soundor.config.ts or native C++ code. Console output and uncaught errors reach the terminal, with native source maps used to map UI stack frames back to TypeScript.

The CLI can also start a local DevTools session for a runtime that declares support. The native development Standalone offers tree inspection, picking, animation information, and measured rendering diagnostics. See CLI development.

Production builds

build generates the contract and one production UI bundle, then runs the selected runtimes' build phases. JUCE uses a Release build with bytecode-only Hermes and no DevTools bridge; the browser runtime emits a static site with relative asset URLs.

JUCE's default prebuilt mode verifies and links the engine libraries for the installed engine version. Source mode compiles them, caching heavy dependencies. A missing or incompatible prebuilt fails explicitly. It never silently changes to source mode. See Soundor Engine.

On macOS, final bundle modification and signing have a fixed order: Soundor writes the VST3 manifest before final signing and verification. Signing details and the separate notarization workflow are documented in the JUCE guide.

Adding a runtime

Install its package, add its factory to runtimes, and run soundor init <id>. Implement that host's audio and native API while keeping the UI contract shared.

Runtime authors implement init, gen, dev, build, and doctor with @soundor/runtime-sdk. Lifecycle context carries the project paths, config, file system, logger, codegen sink, build mode, UI bundle, signing settings, and optional development-session services. Runtimes own platform behavior; the CLI owns common orchestration.

On this page