9.8 KiB
src/shell/view — Design View mode application shell
Scope
The REAPER-facing half of Design View: applying a mode's visibility/processing
state to live tracks (park/restore), snapshotting flag values before parking, and
restoring from snapshot on toggle-back. The mode registry, membership derivation,
and the pure park/restore planner are owned by core/view (view_mode_model) —
this directory is the shell that reads/writes REAPER track flags, it does not
decide membership or mode rules.
Invariants
- Never touches master or
B_MUTE; never LOSES solo. The tool owns visibility,B_MAINSEND,I_FXEN, per-FX offline, and — on a real mode switch only —I_SOLO, on every managed leaf, tagged or untagged.B_MUTEis untouched absolutely. The master track is untouched absolutely: it is outsideGetTrack's index space, so it never enters the enumeration any of these writes iterate, and its visibility flags are never driven (the SDK forbidsB_SHOWINTCP/B_SHOWINMIXERon master). - Solo surfaces are disjoint per mode, cached not destroyed. A real switch
(target != active) reads every live track's raw
I_SOLO, banks the non-zero values against the OUTGOING mode, clears them, and replays the incoming mode's banked values verbatim — solo-in-place and safe-solo variants included, never collapsed to a boolean. This is the same snapshot sense of non-destructive that park/restore already gives visibility and FX state: the user's solo is never lost, only parked with the mode it belongs to. A REAPPLY (target == active — tag/untag/show-both, project load) touches solo not at all. - Parking a track (inactive-mode leaf) drives
B_SHOWINTCP=0,B_SHOWINMIXER=0(hide both panels),B_MAINSEND=0(out of mix),I_FXEN=0(FX bypassed), andTrackFX_SetOffline(track, fx, true)for each FX (reclaim CPU) — full CPU-park, not mix-removal-only. - Non-destructive restore. For every flag the tool drives, snapshot the prior
value BEFORE parking; on toggle-back restore FROM the snapshot, never to a
hardcoded "on." Round-trip (snapshot → park → restore) returns every driven flag
to its captured value — the phase's trust anchor, the analog of the capture null
test. Per-FX offline is snapshotted WITH each FX's identity (
TrackFX_GetFXGUID) and restored throughcore/view/fx_offline, so a chain reordered while the track was parked cannot land one plugin's state on another; an FX gone at restore time is dropped and reported to the console, never restored onto its old slot. - GUID-keyed, reorder-safe. Membership/snapshot keys on track GUID
(
GetTrackGUID), never track index; tolerates unknown/stale GUIDs (pruned on reconcile). - Relative/portable state only in the persisted view section (GUID strings, mode ids — no absolute paths, no index positions).
- Documented caveat: offlined FX re-instantiate when a track returns to the
active mode — stateful plugins (convolution, loaded samplers, tail-holding
effects) re-initialize on return (load hitch, un-persisted internal state lost).
Accepted cost of the CPU reclaim; surfaced at the toggle affordance (tooltip).
The hitch no longer sits on the switch's synchronous path: per-FX
offline/online is enqueued and applied on a later idle tick (
view_fx_park), so the new mode paints first. The deferral changes only WHEN the plugins move — they still unload and re-instantiate, and un-persisted internal state is still lost. What it does change is the undo record: the offline writes land outside the switch's undo block, so the tool no longer re-drives them on an undo or a redo — what a Ctrl-Z then leaves the chain at is REAPER's own FX-state record,[verify — DAW]. - Stated DEVIATION — the undo mask does not keep FX out of a real switch. The
apply mask (
kApplyUndoMask) dropsUNDO_STATE_FXand ORs it back in when a driven flag in that domain moved; the only such flag isI_FXEN, which every park writes. So any switch that parks at least one track still makes REAPER marshal the project-wide FX chunk into its undo record. The saving is real only on a reapply and on a no-op switch. Narrowing it further would mean not carryingI_FXENin the undo record at all, which would break "one switch is one Ctrl-Z." - Show-both semantics: a per-track "pin visible across modes" flag re-enables processing whenever shown. A show-both leaf appears in every mode's visible set and is never parked — its driven flags stay at snapshot/restored values, FX online, in the mix. ("Show but keep parked" is not offered.) Stored on the membership record; persists; togglable per selection.
- No literal second canvas. A literal second arrange surface, a second window, or a duplicated project stays rejected — reject any such path in review.
Two-canvas sub-phase (Phase D2/E) — settled and landed parts, DAW-application half:
- Fixed-lane item-level separation mechanics. Map mode → lane; toggle drives
per-lane play/show so only the active mode's lane is present. Items keep their
real position and real track — nothing is moved in time or deleted. SDK surface
(verified present in
vendor/reaper-sdk): track-sideI_FREEMODE = 2,I_NUMFIXEDLANES,C_LANEPLAYS:N; item-sideI_FIXEDLANE,C_LANEPLAYS,B_FIXEDLANE_HIDDEN.I_FREEMODEchanges requireUpdateTimeline()to take visible effect. - Inactive-mode content is hidden AND silenced. The off-mode lane is set
C_LANEPLAYS = 0— neither shown nor played — consistent with exclusive membership and with D1's "flipping modes is a real change, not cosmetic." Show-both is the deliberate opt-out for a lane that must stay audible across modes. - Capture placement is mode-aware. An explicit placement while in Design mode — including capture-and-place — lands the item in the Design lane; the same rule governs manual insertion. The capture load-bearing principle is untouched: capture still writes a file + index entry and never auto-inserts; this governs only where an explicit placement lands.
- REAPER floor: v7 for this sub-phase (fixed lanes shipped in v7); no version-gate branch — below v7 the sub-phase is simply unavailable.
Item→lane membership rules (the adoption rule, exclusive-per-item membership, the
managed/manual lane distinction, and the lane-ownership index) are model concepts
owned by core/view — see that directory's Invariants; this directory only
applies the resulting lane state to live tracks.
Modules
view— Design View shell: snapshots flag values before parking, drives hide + CPU-park on inactive-mode leaves (B_SHOWINTCP/B_SHOWINMIXER/B_MAINSEND/I_FXEN, with per-FX offline deferred toview_fx_park), restores from snapshot. Owns the one discriminator (target != active) that separates a real switch from a reapply, and with it both the playback gate (transportBlocksModeSwitch) and the solo cache/clear/restore seams. Never touches master orB_MUTE.view_fx_park— the per-FX offline surface: theTrackFX_GetFXGUIDidentity read snapshot/park/restore share, the deferred intent queue that keepsTrackFX_SetOfflineoff the switch's synchronous path (at most one intent per track GUID, latest wins, an intent landing on its own pending inverse cancels it), and the idle-tick drainmain.cpp'sOnTimercalls. The drain owns no model state. A snapshot is dropped where the restore is PLANNED — the flags are back at their captured values from that moment, and a model that still described the track as parked would let a persist or a reapply inside the drain window replan a restore over whatever the user changed since. What the deferral costs instead is that the live FX chain stops being a trustworthy snapshot source while an intent is pending: a park that CANCELS a pending restore takes the pre-park FX states from that restore's ops (preParkFxFromCancelledRestore), because the chain still reads the parked values and the cancel means no drain will ever fix them.view_solo— theI_SOLOread/write pair behind the per-mode solo surface, plusclearTrackSolos/restoreTrackSolos, the outgoing-clear and incoming-replay entry pointsviewdrives them through. Holds no policy: what to cache, clear, or replay iscore/view/solo_cache.
Gotchas
- The pure mode model (
ViewModeModel, membership,reconcile(liveGuids), the snapshot-based park/restore planner) lives incore/view— reference it, do not duplicate its spec here. - The Two-canvas sub-phase (Phase D2/E)'s settled DAW-application rules
(fixed-lane mechanics, mode-aware capture placement, hidden-AND-silenced) are
reflected in Invariants above; the membership/lane-ownership model concepts
it also covers live in
core/view's Invariants. - REAPER's own undo restores live
I_SOLObut not the model's solo cache oractiveModeId— neither rolls back with a Ctrl-Z. An undo after a mode switch leaves the two out of step, and the next switch banks the undo-restored solos under whatever mode id is active at that point, not the one the user undid back to. Pre-existing:snapshots_already carries this same model-vs-undo split; the solo cache inherits it rather than introducing it. Not fixed here. fx_offline's identity keying (TrackFX_GetFXGUID) assumes the GUID stays attached to its plugin across a chain mutation while parked. That is[verify — DAW](seefxGuidStringinview_fx_park.cpp) and SWS issue #802 is a known reason it might not hold:SNM_MoveOrRemoveTrackFXreportedly leaves the FXID lines behind on reorder rather than moving them with the plugin. If confirmed, an SWS-driven reorder of a parked track's chain — not a native drag-reorder — can produce wrong-plugin restores or mass drops throughresolveFxRestore. Do not design around this pre-emptively; if native reorder is clean (the likely case), only the SWS path degrades.