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.
    • Colours come from tokens: no hex colour in an area file (styles.test.ts fails on one). A colour that’s the same in every theme (a piano key, the VU meter’s face) still gets a token; see Add a theme colour.
    • Type scale. Six sizes in tokens.css: --fs-xs 10 px, --fs-sm 11, --fs-md 12 (the body), --fs-lg 13, --fs-xl 15, --fs-xxl 17. Text uses --fs-sm and up; --fs-xs is only for tiny numbers and captions inside fixed-size readouts (meter scales, the CPU figure, the LCD’s captions). Display numerals sized to their box (the LCD’s digits, the Tuner’s note) set a px size of 18 or more. Canvas text (rulers, regions, notes) is sized in its draw code.
    • Default states stay quiet. Colour marks what differs from the default: Read is grey and Touch, Latch and Write are coloured; the power button is grey while the track plays and stands out when it’s off.
    • Keyboard. :focus-visible draws --focus-ring (base.css); a click never shows one, and outside text fields the ring waits for Tab (src/focusRing.ts). Don’t add a bare outline: none: a field with its own focus look (accent border, glow) may turn the outline off, and an editor focused only by a click (tabIndex -1) may too. Custom controls get a role and a name: knobs and faders use sliderAria and sliderKeyDown (Controls.tsx), which go through the same onChange and touch path as a drag; an icon-only button gets an aria-label, usually its tooltip.
  • 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.

Marbble is released under the MIT licence.Source on GitHub.