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.- Colours come from tokens: no hex colour in an area file (
styles.test.tsfails 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-xs10 px,--fs-sm11,--fs-md12 (the body),--fs-lg13,--fs-xl15,--fs-xxl17. Text uses--fs-smand up;--fs-xsis 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-visibledraws--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 bareoutline: 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 usesliderAriaandsliderKeyDown(Controls.tsx), which go through the sameonChangeand touch path as a drag; an icon-only button gets anaria-label, usually its tooltip.
- Colours come from tokens: no hex colour in an area file (
- 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.
Marbble is released under the MIT licence.Source on GitHub.