Cross-cutting patterns
A few patterns recur across features. Follow them when adding one.
Playback-list transforms
Section titled “Playback-list transforms”The project stores what the user edited; what plays is derived from it when the session
builds a track’s playback list (Session::regions_dsp), never on the audio thread. The engine
only ever sees plain RegionDsp::Audio segments and RegionDsp::Midi regions, so a feature
built this way needs no new Cmd and plays the same in every render. regions_dsp applies,
in order:
- Freeze (
freeze_dsp): a frozen track plays only its freeze file. - Region playback parameters (
params_map,delay_audioinsession/region_params.rs; the transforms inregion_params.rs): an audio region’s delay moves it, and a MIDI region with parameters becomes one transformedRegionDsp::Midiper pass (region_params::pass). - Pattern regions (
realize_patternsinsession/patterns.rs,pattern::realize): each becomes the plain MIDI region its pattern plays. - Comps and crossfades: take folders expand into comp pieces
(
takes::pass_pieces_edged), andfades::crossfadesandfades::loop_seamsplace the crossfades between neighbours and loop repetitions.
MIDI take folders need no step of their own: the region’s content always holds the notes of
the take it plays (see Take folders and comps). Code that reads
regions directly instead of the playback list (Join, MIDI file export, render ranges) calls
region_params::bake (which realizes a pattern first) or region_params::bounds. Transforms
that read the tempo map are rebuilt on tempo edits by push_tempo_regions, and the snapshot’s
played list (played_notes) gives the Tracks area the notes as they play, while the Piano
Roll and Event List edit the stored ones. A new transform goes into regions_dsp and bake,
and into push_tempo_regions if it follows the tempo.
Time maps for section edits
Section titled “Time maps for section edits”Edits that move whole stretches of the timeline, the Arrangement track’s moves, copies and
deletes and Edit › Section Between Locators, are described as a time map: a list of
arrangement::Piece { start, end, dst, copy } saying where each stretch of the old timeline
lands. The pure side builds it (arrangement::move_section, copy_section and
delete_section return a Plan of pieces plus the new sections; arrange_cmds::section_pieces
for Insert Silence, Cut and Repeat Section), and one apply step maps everything through it:
regions with session/arrangement.rs remap_regions (a region inside one piece moves whole,
one across a piece edge is cut with range_edit::sub_region), automation lanes with
arrangement::remap_points, markers with arrangement::map_pos, and, for the section edits,
the arrangement sections with arrange_cmds::remap_sections. The
tempo and signature tracks never move. With a marquee, the section edits map only its tracks
(session/arrange_cmds.rs section_edit takes a scope). Every apply refuses, changing
nothing, when a frozen track’s regions would change.
Sheets and key commands
Section titled “Sheets and key commands”Sheets (anything modal drawn over the window: Sheet in Dialogs.tsx, the Settings frame,
the name sheet, the Project Chooser) register with useSheetKeys (src/sheetKeys.ts).
installSheetKeys, called in main.tsx before any other listener, applies one rule
(sheetKeysModel.ts, heldBack and outsideKey): keys typed in a sheet’s fields stay there
(sheetKey in Form.tsx stops them); keys pressed with the focus outside it never reach the
app, except that Esc closes the top sheet, Return presses its default button and Tab moves the
focus. A "non-modal" sheet (Controller Assignments, a window in Logic) lets the transport and
Control Surfaces commands through (NON_MODAL_COMMANDS) and holds back the rest. onKey and
keys passed on from panel windows ask sheetBlocks(id). A new sheet should use Sheet or call
useSheetKeys; tests/sheetKeys.test.ts checks that every sheet does.
State shared between windows
Section titled “State shared between windows”Each window has its own store, and detached windows agree only on what is
listed in SYNCED_FIELDS (detachModel.ts), sent as ui-sync patches, plus the settings
(ui-settings) and the session’s snapshot (project-sync). Anything a user would expect to
be the same everywhere, such as a selection or a clipboard, belongs in the store as a synced
field rather than in a module variable: the region, channel strip, plug-in and Channel EQ
clipboards (clipboard, stripClip, pluginClip, eqClip) are store fields for that reason.
Synced values are compared by identity, so replace them, never mutate them. View state that is
per window (which bottom pane shows, zoom, scroll) stays out.
Guard tests
Section titled “Guard tests”Some rules are enforced by tests that read the sources rather than import them:
tests/storeSelectors.test.ts (a useStore selector must not build a new array or object,
or React re-renders forever and the window goes blank), tests/commands.test.ts (every key
command and menu id is handled, no two default keys clash), tests/tauriCommands.test.ts
(every command the frontend calls is registered, with its required arguments in camelCase),
tests/browserDialogs.test.ts (no window.prompt, confirm or alert) and
tests/sheetKeys.test.ts.