Conventions
These keep the code maintainable and the merges clean.
- Real-time safety. Nothing on the audio thread allocates, locks, blocks or does I/O, and
nothing waits on another thread: use
rtrbrings and atomics, not channels or mutexes. New engine state arrives as aCmdand replaced objects leave asGarbage. See ARCHITECTURE.md › Real-time rules. - Model compatibility. Every new field in
model/orprefs.rsgets#[serde(default)](or a struct-level#[serde(default)]), and optional fields usually alsoskip_serializing_if = "Option::is_none", so older projects and settings files still load. - Struct literals. Every
Region { .. }literal needsloop_end,takes,paramsandpattern, everyRegionContent::Audio { .. }needsfade_shape, everyRegionDsp::Audioneedslaws, and everyNoteneedsmuted. Tests and examples build these by hand, soDefault::default()(orNone) is usually what they want. - Clippy.
cargo clippy --workspace --all-targets -- -D warningsis clean on Linux, and CI keeps it so. It isn’t run on Windows or macOS, where CI only compiles, socfgcode may have warnings of its own. A narrow#[allow]with a one-line reason is right where a lint would make DSP or tables worse. CI lints on a pinned toolchain (LINT_TOOLCHAINinci.yml) so a new Rust release can’t turn it red with no code change; to move to a newer one, bump it there and fix what it reports in the same commit. - Every platform. Keep platform-specific code behind
cfg. VST3 enum casts stay explicit: the C enums arei32in the MSVC bindings, so a cast that clippy calls a no-op on Linux is needed on Windows. - Undo. Every user edit goes through a checkpointed command (
edit,try_edit,edit_made). Performance switches (mute, solo, on/off, arm, monitor) and transport are not undoable. Anything you send to the engine must also be handled inrestore(),build_track()andrebuild_graph(). - Main thread for plug-ins. VST3 controllers and editors are touched only on the main
thread (synchronous commands,
idle). Long work goes in an async command; hand edits back to the main thread withrun_on_main_thread(seebounce_in_place,freeze_tracks). - Argument names. Tauri maps camelCase JS argument names to snake_case Rust parameters. Struct fields keep their serde (snake_case) names.
- Where things go. Substantial new code goes into a new module (
session/<feature>.rs,src/<feature>.ts,src/components/<Feature>.tsx) and is wired in with small edits. New struct fields go at the end of the struct, new commands at the end of the fittingcommands/<area>.rsand of theinvoke_handlerlist inmain.rs, new CSS at the end of its area’s file insrc/styles/(a new area: a new file imported at the end ofsrc/styles/index.css), new menu items next to related ones. - Comments. Public items get doc comments. Comments explain why, not what. Keep the Logic Pro reference in mind: behaviour and labels follow Logic unless there is a reason not to, and the reason goes in a comment.
- UI. Use the existing CSS variables and components (
Form.tsx,Controls.tsx,Menu.tsx). Follow the Logic-like visual language; don’t introduce a new look. - Formatting.
cargo fmt --allfor Rust, which CI checks.rustfmt.tomlallows lines up to 160 columns;#[rustfmt::skip]stays on tables that would otherwise fall apart. The one project-wide pass is listed in.git-blame-ignore-revs, sogit config blame.ignoreRevsFile .git-blame-ignore-revslets blame look through it. TypeScript is strict with no unused locals or parameters.