Untagged folder carrying its own FX/media shows in Arrange (its default) as well as any mode derived from its children. Parents still never parked.
18 KiB
Design View — product notes
Framing, rationale, and design-direction calls behind the Design View phase.
The tickable spec lives in PLAN.md (Phase D) and the authoritative technical
detail in CONTEXT.md (§Design View). This doc holds the why — the workflow
narrative, the N-mode reasoning, the screenset differentiation, and the
design-direction recommendations — so those don't clutter the build docs.
Status: settled with Daniel (2026-07-22). Open items for Daniel are listed at the bottom.
What it is (and what it is not)
Design View is a track-visibility-plus-processing "mode" system. Tracks used purely for sound design — scratch oscillators, FX-mangling chains, resampling sources — get tagged into Design mode. The arrangement's real tracks stay in Arrange mode (the default). Toggling to a mode hides and disables the tracks that don't belong to it.
The value is mental separation plus clutter elimination, tuned to the resample workflow: be in Design view, resample something into the bank, flip to Arrange, place it. The design scaffolding never touches the arrangement while you're arranging, and the arrangement never buries the design bench while you're designing.
It is not a second canvas. REAPER has exactly one arrange timeline. Design View does not create a parallel surface, a separate window, or a duplicate project. It is the same timeline with a curated, filtered track set and the inactive tracks parked. Anyone expecting a second arrange view will be disappointed — and that expectation must be headed off in the spec, not discovered in review.
This is the visibility/processing analog of the capture pillar's load-bearing rule. The capture pillar says capture and placement are separate acts. Design View says designing and arranging are separate stances on one timeline — and the tool enforces the separation without ever destroying the user's real state.
Why not REAPER screensets? (pre-answered)
REAPER already ships screensets, which save and recall track visibility. This is deliberately different on three axes:
- Live membership, not a flat snapshot. A screenset is a re-saved picture of "these tracks visible, those hidden" — you re-capture it every time the track set changes. Design View is a membership index: tag a track into a mode once, and it is handled forever, including tracks added later. You maintain membership, not snapshots.
- It disables processing, not just visibility. Screensets hide tracks; the hidden tracks still run their FX and still feed the mix. Design View parks the inactive mode's tracks: out of the mix, FX bypassed, FX taken offline to reclaim CPU. Flipping modes is a real load change, not a cosmetic one.
- It is integrated with the resample workflow. The toggle lives in the ReaSampler window, next to the bank, driven by the same actions that capture and place. It is part of the tool's loop, not a general window-management feature parked in a menu.
N-mode reasoning (why the model is not a boolean)
Daniel's instruction was explicit: design the data model as N-mode, ship the UI with two. "Design Mode, or any other future mode."
The cost of a boolean is a backfill cliff: the moment a third stance appears
("Mixdown" that hides everything but buses; "Print" that isolates a stem chain),
a boolean model forces a migration of the persisted index and a rewrite of every
call site that assumed two states. The cost of N-mode now is trivial: a mode is
an identifier, membership is a GUID → set<ModeId> relation instead of a
GUID → bool. The visibility/parking logic is already "is this track in the
active mode?" — which is a set-membership test regardless of how many modes exist.
So: the model carries arbitrarily many modes; the UI exposes two. Adding a third mode later is a UI-and-naming task, not a data-migration task. This is the defer-the-feature, design-the-seam principle applied.
A mode is a small record: a stable id, a display name, and an ordinal for tab order. Two seed modes ship: Arrange (the implicit default; every untagged leaf belongs to it) and Design. Arrange is special only in that it is the fallback home for untagged leaves — structurally it is just another mode.
Membership model (the settled rules, in prose)
- Default is Arrange. Every leaf track belongs to Arrange unless it opts in elsewhere. Untagged = Arrange.
- Leaves opt in. You tag a leaf track into Design (or any mode). You never tag a parent.
- Parents are derived, never tagged. A folder/parent track's visibility is the OR of two sources: (a) descendant-derived — it appears in every mode that any of its descendant leaves belongs to; or (b) its own membership — an untagged parent is an Arrange member by default, exactly like a leaf. If a folder holds two Design leaves and one Arrange leaf, the folder shows in both modes by descendant derivation. The case own-membership fixes: an untagged folder that carries its own FX/media above leaves that are all tagged Design still shows in Arrange (its own default) as well as Design (derived from its children) — without the own-membership clause it would vanish from Arrange even though the folder itself is an Arrange member. A parent is never parked in any mode it is visible in. Rule of thumb: tag leaves; parents follow — still the common case.
- Master is always visible, in every mode, and the tool never touches its
flags. (REAPER's own SDK forbids driving
B_SHOWINTCP/B_SHOWINMIXERon the master track — the invariant and the API agree.) - One mode at a time, with two exceptions: a leaf can appear in more than one mode only via (a) the explicit show-both toggle, or (b) the parent-derivation rule above. Otherwise a leaf lives in exactly one mode.
The descendant-derived half of the parent rule is why the pure model must be
folder-tree aware. Own-membership (part b) is answerable from the membership
index alone, but descendant derivation (part a) is not — to know whether any child
leaf is visible in mode M, the model needs the current parent↔child structure, which
the shell reads from REAPER's I_FOLDERDEPTH and feeds in. The tree is an input to the pure model's
visibility query, not something the model stores (folder structure is REAPER's
truth and changes underneath us).
What a toggle actually does (the parking contract)
Toggling to mode M partitions every leaf into active (belongs to M, or show-both) and inactive (belongs to some other mode — tagged elsewhere, or untagged/Arrange when M is not Arrange). Untagged leaves are Arrange members and park identically to tagged ones; there is no unmanaged leaf. For each track:
Active leaves and derived-visible parents → restored to their snapshot values (see restore contract) and shown.
Inactive leaves → parked:
B_SHOWINTCP = 0andB_SHOWINMIXER = 0— hidden in both panels.B_MAINSEND = 0— removed from the mix (no audio to parent).I_FXEN = 0— FX chain bypassed.TrackFX_SetOffline(track, fx, true)for each FX — taken offline to reclaim CPU.
Daniel chose the full CPU-park option over mix-removal-only, deliberately. The point of Design mode is a heavy FX bench you don't want taxing the CPU while you arrange, and vice versa. Half-parking (mix out, FX still resident) would leave the tax in place.
Documented caveat: offlined FX re-instantiate when the track returns to the active mode. Stateful plugins (convolution reverbs, samplers with loaded content, anything holding a tail or a big buffer) re-initialize on return — there may be a load hitch and any un-persisted internal state is lost. This is an accepted cost of the CPU reclaim, not a bug. It must be documented at the toggle affordance so the user isn't surprised.
Never touched: B_MUTE and I_SOLO. The tool owns visibility, B_MAINSEND,
I_FXEN, and FX-offline — nothing else — across every managed leaf, tagged or
untagged. The user's mute/solo survives every toggle, untouched. This is the exact
analog of the
capture pillar's non-destructive invariant: the tool never destroys the user's
real state to do its job.
Non-destructive restore contract (the testable invariant)
For every flag the tool drives on a track, it snapshots the prior value before
parking and restores exactly on toggle-back — restore from the snapshot,
never to a hardcoded "on." If the user had B_MAINSEND = 0 on a track for their
own reasons before it was parked, it comes back as 0, not 1.
While a track is parked/hidden, its tool-owned flags (B_SHOWINTCP,
B_SHOWINMIXER, B_MAINSEND, I_FXEN, per-FX offline) are tool-owned: the
user is not expected to hand-edit a hidden track's routing mid-mode, and the
contract does not promise to merge concurrent hand-edits of a hidden track. The
snapshot is the source of truth for restore.
This is enforceable and testable in the pure layer: given a snapshot record and a
target mode, the model produces the exact set of (track, flag, value) restore
operations, and a round-trip (snapshot → park → restore) returns every driven flag
to its captured value. The shell's job is only to apply those operations to
REAPER; the decision of what to restore to is pure and unit-tested — mirror of
how bank_model owns the index logic and the shell only touches the DAW.
Persistence
The mode-membership index and the last-active mode ride in the existing
"reasampler" project ext-state namespace and travel with the .rpp. On project
open, the tool reapplies the active mode's visibility + processing state. Same
namespace, same travel-with-the-project guarantee as the bank index — one
"reasampler" blob, two logical sections (bank + view).
Snapshots (prior flag values for currently-parked tracks) also persist: if a project is saved while in Design mode with Arrange tracks parked, reopening it must be able to restore those Arrange tracks correctly later. A saved-while-parked project that lost its snapshots would restore parked tracks to a guessed "on" — violating the restore contract across a save boundary.
Design-direction recommendations (opinionated)
Naming: keep "Design View" as the feature; name modes for stances, not tracks
The feature is Design View. The modes are Arrange and Design — verbs- as-stances the user adopts, not labels for what's in them. "Arrange" and "Design" read as what you are doing right now, which is the whole mental-separation pitch. Resist "Main / Scratch" or "A / B" — they describe the tracks, not the stance, and they don't scale to a named third mode. When a third mode arrives it gets a stance name too ("Mixdown," "Print").
The toggle affordance: a segmented mode switch, not a checkbox
Recommendation: a segmented control (pill/tab switch) in the ReaSampler window
header — [ Arrange | Design ] — with the active segment lit. Not a checkbox, not
a menu item as the primary affordance.
Why segmented over the alternatives:
- vs. a checkbox ("Design mode on/off"): a checkbox hardcodes the binary the
data model is explicitly built to avoid. A segmented control grows to
[ Arrange | Design | Mixdown ]with no redesign — the UI mirrors the N-mode model. Borrowed from DAW/plugin A/B/C compare switches and browser device-toolbar responsive-mode pickers, both of which are "pick one active view from a small set." - vs. a menu: a mode switch is a frequent, glanceable action in this workflow — you flip it many times a session. It wants to be always-visible and one-click, showing current state at rest. A menu hides both the state and the switch.
The segment also carries the current-mode indicator "for free" (the lit segment is the state), and it is the natural home for the offlined-FX caveat as a tooltip ("switching parks the other mode's FX offline; stateful plugins re-init on return").
Actions (below) drive the same toggle for keyboard/MIDI binding; the segmented control is the visible, discoverable surface over those actions.
Tagging affordance: act on the track selection, not a per-track widget
Tag/untag operates on the current REAPER track selection via an action (and a context entry), not a per-track button in ReaSampler's window. "Select your design tracks, hit Tag selected → Design." This matches how a user thinks ("these tracks are my design bench") and avoids ReaSampler having to render a full track list mirror. The bank_panel window shows a small membership summary (counts per mode) rather than a per-track control surface.
Action set (proposed)
Stable command-id strings follow the sampler family prefix chosen in M0. Default names below; all bindable, the toggle and mode-jumps also MIDI-bindable to suit the performance-adjacent workflow.
- Toggle active mode — cycle Arrange ↔ Design (extensible to cycle-through-all when >2 modes). The segmented control's click target.
- Activate mode: Arrange / Activate mode: Design — jump directly to a named mode (distinct from cycle; needed once there are >2, and nicer for bindings).
- Tag selected tracks → Design / Tag selected tracks → Arrange — move the current selection's leaves into a mode. (Arrange = untag from any other mode.)
- Untag selected tracks — return selection to the default (Arrange), explicit alias of "Tag → Arrange" for discoverability.
- Show both for selected tracks (toggle) — see show-both semantics below.
Show-both semantics (resolved)
Show-both is a per-track "pin visible across modes" flag, and it re-enables processing whenever the track is shown. A show-both leaf:
- appears in every mode's visible set (like a derived parent, but by explicit request on a leaf);
- is never parked — its driven flags stay at their snapshot/restored values in all modes, FX online, in the mix.
Rationale: the use case is a track you need audible in both stances — a reference oscillator you design against and also hear in the arrangement, or a master-bus FX chain living on a normal track. "Show but keep parked" is not a real need (a track you can see but that's silent and offline is just clutter with a thumbnail), so show-both means show-and-run. It is the deliberate escape hatch from one-mode-at-a-time, and it costs CPU by design — that's the user's explicit call per track.
Show-both is stored on the membership record for the track, persists with the index, and is togglable per selection.
Module breakdown (pure / shell / persist / actions / UI)
Mirrors the capture pillar's split exactly.
Pure view_mode_model (REAPER-free, unit-tested — the mirror of bank_model):
- Mode registry: ordered set of modes (id, display name, ordinal); Arrange + Design seeded; add/query more.
- Membership index:
track GUID → { mode ids }(normally one; multiple only via show-both), plus the per-track show-both flag. add / remove / retag / query. - Folder-tree-aware visibility derivation: given the current parent↔child tree (supplied by the shell) and the active mode, compute the visible set — active leaves, derived-visible parents, show-both leaves, master always in.
- Parking/restore planner: given the active mode + a snapshot record, emit the exact (track, flag, value) operation lists for park and for restore. Pure and fully testable — this is where the restore-contract invariant is enforced.
- JSON round-trip of modes + membership + show-both + snapshots + active mode.
REAPER-facing view shell:
- Reads
I_FOLDERDEPTHacross the track list to build the parent↔child tree; feeds it to the pure model. - Applies the planner's operations:
SetMediaTrackInfo_ValueforB_SHOWINTCP/B_SHOWINMIXER/B_MAINSEND/I_FXEN;TrackFX_GetCount+ loopTrackFX_SetOfflineper FX. - Snapshots prior flag values before parking (reads the same flags it will drive).
- Resolves track GUIDs via
GetTrackGUID/guidToString/stringToGuidfor the index; never uses track index (unstable across reorders). - Never touches the master track's visibility flags; never touches
B_MUTE/I_SOLOon anything.
persist slice:
- Serialize/deserialize the view section (modes + membership + show-both + snapshots
- active mode) into the
"reasampler"ext-state namespace alongside the bank.
- active mode) into the
- On project open, rebuild the tree, reapply the active mode.
actions entries: the set listed above, registered with the
command_id / gaccel / hookcommand pattern; toggle + mode-jumps MIDI-bindable.
UI (in the ReaSampler / bank_panel window): the segmented mode switch in the window header; a small per-mode membership count; the offlined-FX caveat as a tooltip on the switch.
Open items for Daniel
- Product-notes location. This doc was created at
docs/product/— the first product-notes directory in the repo. If notes belong elsewhere (notes/,docs/design/), say so and it moves. Flagging because establishing the directory is a project convention, not mine to assume silently. - Phase namespace. Proposed Phase D (a lettered namespace) rather than M12 — see PLAN.md rationale. Confirm you're happy with letters for parallel, non-capture phases.
- SDK name correction. The brief cited
B_SHOWINMCP; the verified SDK flag for mixer-panel visibility isB_SHOWINMIXER(SDK header line 2235). The spec uses the verified name. Just flagging the discrepancy so it isn't a surprise in review. - Snapshot-across-save durability. I ruled that per-track snapshots must persist so a saved-while-parked project restores correctly. This adds snapshot data to the persisted blob. If you'd rather keep the persisted state lean and accept that saving-while-in-Design-mode is an edge case (e.g., force-restore-to- Arrange on save), that's a viable smaller alternative — flag if you prefer it.
- Reorder/delete robustness (deferred, not blocking). If a tagged leaf is deleted or a folder is restructured while parked, the index holds a stale GUID. The pure model should tolerate unknown GUIDs (ignore-and-prune on next reconcile) — noted as a hardening item for the build, not a phase-defining decision.