mirror of
https://github.com/storytold/soundcraft.git
synced 2026-10-30 05:26:52 -06:00
A bounce rendered the whole mix (or bus) into memory and then encoded a second copy, and the render silently stopped at 2^29 samples: about 93 minutes of stereo at 48 kHz, 31 of 5.1, 15 of 7.1.4. Longer bounces came out cut short while file.bounce_mix still reported the requested length. - audio-io: FileEncoder writes WAV/BWF (RF64 above 4 GiB), AIFF and FLAC block by block into any Write + Seek, byte for byte what encode writes for the whole buffer. check_room fails up front when an AIFF (4 GiB) or a FLAC (2^36 frames) can't hold the bounce. AIFF and FLAC encode run on the same writers; their output is unchanged. - mix: RangeRender renders the main mix or one bus over a range of any length, a block at a time; render_range and render_bus (in memory, still capped) are built on it. - engine: io::Bounce streams the main mix or a bus, or its stereo fold-down, into "<name>.part" next to the target, syncs it and renames it into place, so a failed bounce leaves no partial file and keeps the old one. In the browser it encodes into memory (failing instead of aborting when memory runs out) and delivers the file as before. Normalize renders twice (peak, then the scaled write); levels are measured as blocks are written. Stems stream the same way and share the 4-hour limit. A 100-minute bounce of the demo session now writes all 288,000,000 frames with 83 MB peak memory.
83 lines
4.9 KiB
Markdown
83 lines
4.9 KiB
Markdown
# Architecture
|
||
|
||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-12 · **Change:** minor (bounces stream to disk) · **Target:** Avid Pro Tools Ultimate 2026.4.1
|
||
|
||
SoundCraft is a Cargo workspace of small crates with enforced layering (`cargo xtask layers`).
|
||
Nothing below the UI knows about egui, so the interface can be replaced.
|
||
|
||
The UI package pins `craft-ui` for common icon-button and track record-enable interactions.
|
||
SoundCraft supplies each state's colors, border, radius and icon painter; selection and command
|
||
dispatch remain in the toolbar and track controls. The single-shape frame option preserves the
|
||
existing fill/border blending when controls are disabled. Specialized transport Play/Stop/Record styling,
|
||
nonlinear meters, faders, pan gestures, fonts and the live theme resolver remain local. Shared adapters read the current palette. The UI
|
||
requires Rust 1.95 or later without raising the core workspace's Rust 1.90 declaration.
|
||
|
||
```
|
||
L0 time audio-io midi video (standalone: no workspace deps)
|
||
L1 dsp clap-host vst3-host au-host (plugins; the three hosts are the only crates
|
||
allowed `unsafe`, confined to their FFI modules)
|
||
L2 model (the session document)
|
||
L3 mix (the mix engine)
|
||
L4 engine playback (commands, undo, I/O · audio devices)
|
||
L5 automation (MCP server, control-channel client)
|
||
L6 ui-egui (the user interface)
|
||
apps: soundcraft (desktop) · soundcraft-cli · soundcraft-web (wasm)
|
||
```
|
||
|
||
## The document
|
||
|
||
`soundcraft_model::Session` is plain data (serde): tracks with playlists of clips, mixer state
|
||
(inserts, sends, routing, automation lanes), tempo and meter maps, memory locations, groups,
|
||
busses, and `EditState` (selection, tools, modes, view flags). Decoded audio lives in a
|
||
`SourcePool` of `Arc`s that is not serialised; cloning a session is cheap. Sessions save as
|
||
`.scraft` JSON plus an `Audio Files/` folder.
|
||
|
||
## Commands and undo
|
||
|
||
Every user action is a `CommandSpec` (id, label, menu path, shortcut, params doc, enabled, run)
|
||
registered in `crates/engine/src/cmd/`. `Engine::execute(id, params)` runs it, catches any escaped
|
||
panic, and pushes an undo snapshot (the previous `Arc<Session>`) when the document changed.
|
||
Continuous gestures use `execute_merged` so a fader drag is one undo step. Menus are built from
|
||
the incumbent's menu catalog (`crates/engine/catalog/menus.txt`); a menu item lights up when a
|
||
command has the same menu path and label (or an alias maps it), which also drives the parity
|
||
report.
|
||
|
||
## Audio
|
||
|
||
`soundcraft_mix::MixEngine::render(session, pos, frames, out)` renders one block: clips (gain,
|
||
fades, clip effects) → trim → inserts → pre-fader sends → fader/mute (automation, VCA, trim
|
||
automation) → post-fader sends → pan → busses → aux inputs → master faders. Independent strips
|
||
process in parallel; plugin delay compensation aligns every path. The same engine renders
|
||
bounces offline (`RangeRender`, streamed to the file block by block) and realtime playback
|
||
(`soundcraft_playback::Player`, which owns it on the audio thread and receives new session
|
||
snapshots through a channel).
|
||
|
||
Device I/O today is the system's *default* output and *default* input device, opened as two
|
||
separate cpal streams; the mix renders 512-sample blocks and resamples to the device rate when
|
||
they differ. There is no device or buffer-size selection, no ASIO, and no MIDI device layer yet
|
||
(see [`hardware-parity.md`](hardware-parity.md)). Sources are decoded whole into the `SourcePool`
|
||
and recordings are captured in memory until stop.
|
||
|
||
`soundcraft-video` holds our own H.264, ProRes and Motion JPEG decoders and the ISO-BMFF demuxer
|
||
for the video track. Hosted plugins (`clap-host`, `vst3-host`, `au-host`) are scanned, created and
|
||
destroyed off the audio thread; only `process` runs on it.
|
||
|
||
The cpal callbacks mark their thread (`soundcraft_playback::mark_audio_thread`). Code that may run
|
||
there can ask `on_audio_thread()` before doing anything that blocks; the desktop app's logger does,
|
||
so a `log::` record from the audio thread (a stream error, a full synth event queue, a hosted
|
||
plugin's failed `process`, a CLAP plugin's own log call) is kept without waiting and written later
|
||
by the UI thread (see README › Logs).
|
||
|
||
## Agent control
|
||
|
||
The desktop app's control channel (JSON lines over TCP) exposes engine commands, inspection,
|
||
synthetic input and screenshots; `soundcraft-cli mcp` wraps either a headless engine or a running
|
||
app as an MCP server. See `control-protocol.md` and `mcp.md`.
|
||
|
||
## Revision history
|
||
|
||
| Date | Change | Summary |
|
||
|---|---|---|
|
||
| 2026-10-10 | minor | Added video, vst3-host and au-host to the layer diagram; documented device I/O limits and in-memory media |
|
||
| 2026-10-05 | major | First architecture overview |
|