Execution and optimization
This document answers how one Rust processing core runs in a browser tab, a DAW plugin and an exported product, what the audio thread may and may not do, and how performance work i
This document answers how one Rust processing core runs in a browser tab, a DAW plugin and an exported product, what the audio thread may and may not do, and how performance work is sequenced.
Status: the Rust compiler/engine compile natively and to wasm32, the AudioWorklet host and the JUCE shell over the versioned C ABI are implemented, and native/WASM parity fixtures run in npm run check. Measured profiles exist for the DSP processors and noise. Real-device, DAW underrun and cross-host equivalence qualification remain open; see validation.
Rust, JUCE, and WASM#
One Rust processing core is compiled separately for native and wasm32, with target adapters around it. JUCE does not compile Rust source directly and a WASM binary does not become a VST. A native Rust static library exposes a versioned C ABI; a C++ JUCE processor owns it and supplies buses, parameters, events, transport, latency and DAW state. The browser loads a WASM build of the same processing semantics behind an AudioWorklet adapter.
%%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#FAFAF7', 'primaryTextColor': '#1C1C1B', 'primaryBorderColor': '#1C1C1B', 'lineColor': '#64645F', 'fontFamily': 'DM Sans, Inter, system-ui, sans-serif'}}}%%
flowchart LR
SRC[("Toy source")] --> COMPILER["crates/compiler<br/>resolve · check · admit · program"]
COMPILER --> ENGINE["crates/engine<br/>prepare · render"]
ENGINE --> FFI["crates/engine-ffi<br/>versioned C ABI"]
ENGINE --> WASM["wasm32 build"]
FFI --> JUCE["native/plugin-shell<br/>buses · params · transport · state"]
WASM --> WORKLET["packages/runtime-web<br/>AudioWorklet adapter"]
JUCE --> PLAYER["Player · independent exports"]
WORKLET --> TAB["Browser tab"]
classDef source fill:#F0442E,stroke:#1C1C1B,color:#FAFAF7
classDef shared fill:#DCEBDD,stroke:#1C1C1B,color:#1C1C1B
classDef host fill:#DED8F0,stroke:#1C1C1B,color:#1C1C1B
class SRC source
class COMPILER,ENGINE,FFI,WASM shared
class JUCE,WORKLET,PLAYER,TAB hostRust documents FFI and static-library integration, JUCE's CMake API describes plugin wrapper targets, and the Web Audio specification defines worklet execution. These enable the architecture; they do not prove Tune.Toys real-time performance or cross-target equivalence. Toolchains and the JUCE revision are pinned (native/juce.lock.json, Cargo.lock).
The legacy tree used one C DSP/compiler implementation for native and WASM and specialized it for exported applets. Its algorithms, fixtures, graph-admission lessons and export evidence remain migration inputs, compared against the same fixtures as the Rust core. Never maintain two independently evolving production engines. If a measured profile ever requires a different backend, record a decision and keep it behind the same boundary.
Prepare and render#
source → resolve dependency closure → type/semantic checks → target admission → immutable program → prepare resources → activate generation → render
%%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#FAFAF7', 'primaryTextColor': '#1C1C1B', 'primaryBorderColor': '#1C1C1B', 'lineColor': '#64645F', 'actorBkg': '#DCEBDD', 'actorBorder': '#1C1C1B', 'signalColor': '#1C1C1B', 'noteBkgColor': '#DED8F0', 'noteBorderColor': '#1C1C1B', 'fontFamily': 'DM Sans, Inter, system-ui, sans-serif'}}}%%
sequenceDiagram
participant Main as Main / owner thread
participant Audio as Audio thread
Main->>Main: resolve, check, admit, build immutable program
Main->>Main: prepare generation N+1 (allocate state, decode assets, routing, latency)
Note over Main: allocation and I/O happen here only
Main->>Audio: hand over complete generation at a block boundary
Audio->>Audio: render N+1 from preallocated buffers
Audio-->>Main: return ownership of generation N
Main->>Main: reclaim generation N off the callback
Note over Audio: a failed preparation leaves N runningPreparation allocates graph state, decodes assets, validates resource bounds, builds routing, initializes parameters and computes latency off the audio thread. Activation exchanges a complete generation at a block boundary. The previous generation is reclaimed off the callback after ownership returns. Failure keeps the last valid generation; tails, crossfade and latency behavior must be defined per host before hot replacement is supported.
The callback handles bounded preallocated audio buffers and timestamped events. It never performs I/O, compilation, asset decoding, JSON parsing, mutex waits, UI calls or unbounded logging. Host adapters slice arbitrary host block sizes into admitted internal blocks and preserve sample offsets. UI repaint, camera tracking, network sync and analysis run at their own cadences; their bounded outputs enter the control/event path.
| Rule | Contract |
|---|---|
| Sample format | f32, explicit sample-offset events, target-specific denormal/non-finite policy |
| Smoothing | Part of the semantic contract, not a UI animation |
| Floating point | No bit-identical results assumed across native/WASM/SIMD; tolerance and event-order rules are fixed in fixtures |
| Feedback | Requires explicit delay boundaries; the current draft validator rejects all signal cycles |
Sharing and state#
Immutable primitive code, dependency definitions, assets, compiled programs and cached artifacts are shared where valid. Each running instance owns its mutable parameters, voices, delay lines, random seeds, note ownership and observations. Browser previews, audition candidates and DAW tracks can never share mutable compiler/engine instances by accident.
The compiler and engine crates share a small program representation; source JSON interpretation stays outside rendering. no_std compatibility is a design goal for the processing crate, not a reason to constrain the first application to MCU budgets. Firmware is deferred and no MCU target or profile is selected.
Optimization plan#
Semantic execution identity, presentation identity and full release integrity are separate. Compiled/prepared artifacts are cached by source/dependency hashes, semantic version, engine build, target profile, feature set and toolchain. Canonicalization must be specified and tested before these hashes become release authority.
- Prune DSP by reachability from outputs, explicitly retained side effects and required state/event roots. Frame visibility never decides audio liveness. Retain a source map so diagnostics name authored IDs after inlining.
- Specialize builds to admitted primitives/services, statically import only used widget families, hash/deduplicate packaged assets and exclude the editor from published bundles. Benchmark that specialization preserves output/state behavior.
- Measure native/WASM fixture parity, render CPU and deadline misses, memory per instance, preparation time, download size, UI cold/warm load, and open/closed editor cost.
- Add later, by evidence: SIMD, multithreading, shared memory or code generation. Browser shared-memory optimizations bring deployment/isolation constraints and are not prerequisites for the first worklet host.
Implementation#
crates/compiler,crates/engine,crates/engine-ffipackages/runtime-web— worklet, WASM loading, generation handoffnative/plugin-shell— JUCE processor over the C ABItests/conformance— shared native/WASM/browser fixtures