Skip to content

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 rtrb rings and atomics, not channels or mutexes. New engine state arrives as a Cmd and replaced objects leave as Garbage. See ARCHITECTURE.md › Real-time rules.
  • Model compatibility. Every new field in model/ or prefs.rs gets #[serde(default)] (or a struct-level #[serde(default)]), and optional fields usually also skip_serializing_if = "Option::is_none", so older projects and settings files still load.
  • Struct literals. Every Region { .. } literal needs loop_end, takes, params and pattern, every RegionContent::Audio { .. } needs fade_shape, every RegionDsp::Audio needs laws, and every Note needs muted. Tests and examples build these by hand, so Default::default() (or None) is usually what they want.
  • Clippy. cargo clippy --workspace --all-targets -- -D warnings is clean on Linux, and CI keeps it so. It isn’t run on Windows or macOS, where CI only compiles, so cfg code 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_TOOLCHAIN in ci.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 are i32 in 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 in restore(), build_track() and rebuild_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 with run_on_main_thread (see bounce_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 fitting commands/<area>.rs and of the invoke_handler list in main.rs, new CSS at the end of its area’s file in src/styles/ (a new area: a new file imported at the end of src/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 --all for Rust, which CI checks. rustfmt.toml allows 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, so git config blame.ignoreRevsFile .git-blame-ignore-revs lets blame look through it. TypeScript is strict with no unused locals or parameters.