1688de553b
Item-level mode separation via REAPER fixed lanes; new content auto-tags to the active mode; inactive lanes hidden and silenced; a managed-vs-manual lane index keeps toggles off user comp lanes; capture placement mode-aware. Precision invariants unaffected; REAPER 7 floor.
680 lines
38 KiB
Markdown
680 lines
38 KiB
Markdown
# 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). The **two-canvas direction** (item-level
|
|
mode separation via fixed lanes) is also settled with Daniel (2026-07-23) — see the
|
|
§Two-canvas direction section, which **supersedes** the "not a second canvas"
|
|
framing below. Open items for Daniel are listed above that section.
|
|
|
|
---
|
|
|
|
## 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 *literal* second canvas.** REAPER has exactly one arrange timeline.
|
|
Design View does not create a parallel surface, a separate window, or a duplicate
|
|
project — that stays true and is enforced in review. It is the *same timeline* with
|
|
a curated, filtered track set and the inactive content parked.
|
|
|
|
> **Superseded in one respect (2026-07-23).** The original claim here was that
|
|
> Design View separates only *tracks*, not *items*. That is now extended: the
|
|
> settled §Two-canvas direction adds **item-level** mode separation on shared tracks
|
|
> via REAPER fixed lanes — so each stance can own its own items as well as its own
|
|
> tracks. It is still not a literal second surface/window/project; it is a mode
|
|
> *projection* over the one timeline that now reaches items. Read §Two-canvas
|
|
> direction for the settled shape; the sentence "It does not separate items" no
|
|
> longer holds.
|
|
|
|
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:
|
|
|
|
1. **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.
|
|
2. **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.
|
|
3. **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_SHOWINMIXER` on 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 = 0` and `B_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_FOLDERDEPTH` across the track list to build the parent↔child tree; feeds
|
|
it to the pure model.
|
|
- Applies the planner's operations: `SetMediaTrackInfo_Value` for `B_SHOWINTCP` /
|
|
`B_SHOWINMIXER` / `B_MAINSEND` / `I_FXEN`; `TrackFX_GetCount` + loop
|
|
`TrackFX_SetOffline` per FX.
|
|
- Snapshots prior flag values before parking (reads the same flags it will drive).
|
|
- Resolves track GUIDs via `GetTrackGUID` / `guidToString` / `stringToGuid` for the
|
|
index; never uses track index (unstable across reorders).
|
|
- Never touches the master track's visibility flags; never touches `B_MUTE` /
|
|
`I_SOLO` on anything.
|
|
|
|
**`persist` slice:**
|
|
- Serialize/deserialize the view section (modes + membership + show-both + snapshots
|
|
+ active mode) into the `"reasampler"` ext-state namespace alongside the bank.
|
|
- 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
|
|
|
|
1. **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.
|
|
2. **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.
|
|
3. **SDK name correction.** The brief cited `B_SHOWINMCP`; the verified SDK flag
|
|
for mixer-panel visibility is **`B_SHOWINMIXER`** (SDK header line 2235). The
|
|
spec uses the verified name. Just flagging the discrepancy so it isn't a
|
|
surprise in review.
|
|
4. **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.
|
|
5. **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.
|
|
|
|
---
|
|
|
|
# Two-canvas direction — settled (2026-07-23)
|
|
|
|
> **Status: settled with Daniel (2026-07-23).** This section reopens and supersedes
|
|
> the "not a second canvas" decision above. Daniel's stated intent (verbatim): *"I
|
|
> wasn't the one who specified single-canvas, that emerged from REAPER's design. I
|
|
> want to come as close as possible to supporting two separate design and arrange
|
|
> canvases, which are actually the same project but have different items and
|
|
> leaves."* The single-canvas framing above was a **derivation from a REAPER
|
|
> constraint**, not a product choice — so it was reopened, and Daniel has now signed
|
|
> off on the mechanism (fixed item lanes) and resolved all five forks. Everything
|
|
> under this heading **supersedes** the "not a second canvas" language in *What it is
|
|
> (and what it is not)*: Design View is now a mode projection that reaches **both
|
|
> tracks and items**. This is an **additive sub-phase** (Phase D2 / Phase E) on top
|
|
> of the shipped track-parking work — not a rewrite of it.
|
|
|
|
## What actually changed in the ask
|
|
|
|
The shipped model separates modes by **track membership** on one shared timeline:
|
|
tag a leaf into Design, and when you flip to Arrange that leaf is parked — hidden,
|
|
out of the mix, FX offline. Because items ride their tracks, a Design track's items
|
|
already vanish from the Arrange view. **The "different leaves" half of Daniel's ask
|
|
is already delivered.** Flipping modes already gives you a different *track set*.
|
|
|
|
The genuinely new half is **"different items."** Daniel wants two canvases that
|
|
differ not only in which tracks are present but in which *items* are present —
|
|
including items that live on a track shared by both stances. Concretely: a track
|
|
that exists in both Arrange and Design (a show-both reference track, or a folder
|
|
carrying its own media) should be able to hold a design take that only appears in
|
|
Design and an arrangement take that only appears in Arrange, on the *same track*,
|
|
without the two colliding on the timeline. Track-parking can't express that,
|
|
because parking is all-or-nothing per track — it can hide a whole track, never a
|
|
subset of its items.
|
|
|
|
So the reframe is precise: **the feature grows from track-level mode separation to
|
|
track-level *plus* item-level mode separation.** "Two canvases" = each stance owns
|
|
its own item set, layered over the shared project, with tracks it doesn't want
|
|
parked as today.
|
|
|
|
## The hard constraint (and why it's not fatal)
|
|
|
|
REAPER has exactly one arrange timeline and one project item graph. There is no
|
|
API to instantiate a second arrange surface, and the "no second window/no
|
|
duplicate project" observation in the shipped framing is *structurally true* — you
|
|
cannot conjure a literal second canvas. What is reversed is only the **conclusion
|
|
drawn from it**: that item separation is therefore off the table. It isn't. Items
|
|
carry per-item state (`I_FIXEDLANE`, `C_LANEPLAYS`, `D_POSITION`, track
|
|
assignment) that the tool can drive to make an item present-or-absent *per mode*
|
|
without destroying it. "Two canvases" becomes an **item-visibility projection over
|
|
one timeline**, exactly as "two track sets" is a track-visibility projection over
|
|
one timeline today. Same discipline, one level deeper.
|
|
|
|
## Mechanism (settled) and the alternatives it beat
|
|
|
|
**The settled mechanism is Option A — fixed lanes as per-mode item layers.** The two
|
|
alternatives (B timebase-offset, C subproject) are kept below because *why they were
|
|
rejected* is load-bearing: both collide with settled invariants, and recording that
|
|
keeps them from being re-proposed.
|
|
|
|
### Option A — Fixed lanes as per-mode item layers (SETTLED)
|
|
|
|
REAPER's **fixed-lane** feature lets a single track hold multiple horizontal lanes
|
|
of items sharing one timebase, with **per-lane play/visibility control**. Map each
|
|
mode to a lane: Arrange items live in the Arrange lane, Design items in the Design
|
|
lane. Toggling a mode sets `C_LANEPLAYS` so only the active mode's lane plays and
|
|
shows; the collapse/hidden flags hide the inactive lane. Items keep their real
|
|
positions and their real track — nothing is moved off the timeline, nothing is
|
|
deleted.
|
|
|
|
**SDK surface (verified present in `vendor/reaper-sdk`).** Track-side:
|
|
`I_FREEMODE = 2` (fixed-lane mode), `I_NUMFIXEDLANES`, per-lane `C_LANEPLAYS:N`.
|
|
Item-side: `I_FIXEDLANE`, `C_LANEPLAYS`, `B_FIXEDLANE_HIDDEN`. Two operational
|
|
notes for the implementation spec: **`I_FREEMODE` changes require
|
|
`UpdateTimeline()`** to take visible effect, and fixed lanes shipped in **REAPER 7**,
|
|
which sets the runtime floor (see fork 5).
|
|
|
|
- **Fidelity to the two-canvas feel:** high. Same track, same time position, but the
|
|
design take and the arrange take are genuinely distinct item sets that never
|
|
co-occupy the view. This is exactly "same project, different items."
|
|
- **Non-destructiveness:** strong. Lane assignment and `C_LANEPLAYS` are
|
|
reversible flags; the item is never relocated in time or deleted. Snapshot the
|
|
prior lane state, restore on toggle — a direct extension of the existing
|
|
snapshot/restore contract to a new flag family.
|
|
- **Fit with park/restore:** clean. It's the same architecture — the planner gains
|
|
an item-lane op family alongside the track-flag op family. Pure model stays pure:
|
|
lane math (which lane = which mode, which `C_LANEPLAYS` value per mode) is
|
|
REAPER-free and unit-testable, mirroring the existing flag planner.
|
|
- **Capture-pillar precision:** untouched. Capture writes files to the bank and
|
|
never inserts into the timeline; lanes are a placement/view concern downstream of
|
|
capture. The null test and bit-identical invariants are unaffected because no
|
|
capture path changes.
|
|
- **Portability/persistence:** native. Fixed lanes and lane-plays are stored in the
|
|
`.rpp` by REAPER itself; the tool only persists which lane maps to which mode
|
|
(a small addition to the `"reasampler"` view section).
|
|
- **Cost / caveats:** fixed lanes are a real REAPER mode on a track — enabling
|
|
`I_FREEMODE=2` changes how that track behaves for the user (lane buttons, comp
|
|
behavior), and users unfamiliar with fixed lanes see new UI on managed tracks.
|
|
Under the settled **auto-tag** membership rule (see *Membership — settled* below),
|
|
a track becomes lane-split when it first needs to hold content for a second mode,
|
|
not by a manual "make this a two-canvas track" opt-in. Requires **REAPER 7**, where
|
|
fixed lanes shipped — this sets the runtime floor.
|
|
|
|
### Option B — Timebase-region offset (each mode owns a stretch of the timeline)
|
|
|
|
Give each mode a disjoint region of the single timeline — Arrange from 0, Design
|
|
starting at some large offset (say bar 1000). Items are authored in their mode's
|
|
region; toggling a mode scrolls/zooms the arrange view to that region
|
|
(`GetSet_ArrangeView2`) and optionally sets the loop/time selection there.
|
|
Different items appear because you're *looking at a different part of the timeline*.
|
|
|
|
- **Fidelity:** medium. It reads as two workspaces if the scroll is snappy, but
|
|
they share tracks vertically — a Design track is still a lane on the same track
|
|
strip, just at a different horizontal offset. It doesn't give per-track item
|
|
layering; a shared track shows both stances' items if you scroll between them.
|
|
- **Non-destructiveness:** high (items just have positions), but **positions become
|
|
semantically loaded** — an item's `D_POSITION` now encodes its mode. That
|
|
collides with the capture pillar's null test, which re-inserts a capture *at its
|
|
source position* to verify it nulls. If source positions live in the Design
|
|
region, the null-test placement logic has to be region-aware. Friction with a
|
|
load-bearing invariant.
|
|
- **Fit with park/restore:** awkward. Parking is per-track; this is per-region.
|
|
Two orthogonal separation axes that don't compose cleanly.
|
|
- **Portability:** fine (just positions + a stored offset), but the offset is a
|
|
magic constant that leaks into every position calculation.
|
|
- **Verdict:** clever, but it overloads timeline position with mode semantics and
|
|
rubs against the capture invariants. Not recommended.
|
|
|
|
### Option C — Dual sub-projects / project tabs (true separation, heavy)
|
|
|
|
REAPER supports project tabs and subprojects (a project rendered as an item in a
|
|
parent). Model Design as a genuinely separate project surface, imported/rendered
|
|
back into Arrange. This is the *only* option that yields a literally separate
|
|
canvas.
|
|
|
|
- **Fidelity:** highest — it *is* two canvases.
|
|
- **Cost:** enormous, and it fights the whole premise. "Same project" is Daniel's
|
|
explicit constraint; a subproject is a *different* project file with its own
|
|
item graph, its own render boundary, and a copy/sync problem between the two. The
|
|
capture pillar's "one bank per project, GUID-keyed project identity, travels with
|
|
the .rpp" assumption breaks — which project owns the bank? Persistence,
|
|
reconcile, and the show-both/derivation model all have to be rethought across a
|
|
project boundary. This is a different product.
|
|
- **Verdict:** rejected. It satisfies "two canvases" literally by violating "same
|
|
project." Daniel asked for *close as possible* within one project, not a second
|
|
project.
|
|
|
|
## The settled shape
|
|
|
|
**Option A — fixed lanes as per-mode item layers — layered on top of the shipped
|
|
track-parking model.** The shipped model already delivers "different leaves"; this
|
|
adds "different items" using a native REAPER mechanism that (a) preserves every
|
|
precision invariant, (b) extends the existing snapshot/restore architecture rather
|
|
than replacing it, (c) keeps the pure/shell split intact (lane math is pure, lane
|
|
writes are shell), and (d) persists natively. It is the transplant-a-known-pattern
|
|
move: REAPER already solved "multiple item sets, one track, one timebase,
|
|
switchable visibility" for comping — we repurpose comping lanes as mode lanes.
|
|
|
|
The mental model: **modes are a projection, and now the projection reaches items as
|
|
well as tracks.** A track can be Arrange-only (parked in Design as today),
|
|
Design-only (parked in Arrange as today), or **shared-and-lane-split** (present in
|
|
both, but showing each mode its own lane of items). The third case is the new
|
|
capability and the concrete meaning of "two canvases."
|
|
|
|
Phase namespace: **Phase D2 / Phase E** (per the lettered-phase convention for
|
|
parallel features) — additive to the shipped D1 track-mode work, not a rewrite.
|
|
|
|
## Membership — settled (auto-tag by active mode at creation)
|
|
|
|
The earlier draft floated a manual per-track opt-in ("make this a two-canvas
|
|
track"). **That is superseded.** The settled rule, verbatim from Daniel:
|
|
|
|
> *"Items AND tracks added to the timeline in arrange mode or design mode should be
|
|
> tagged as for that mode only. Existing tracks and items default to arrange. If an
|
|
> item is added to arrange mode, it does not appear in design mode. If an item is
|
|
> added in design mode, it is NOT part of the arrange mode."*
|
|
|
|
Unpacked into the settled model:
|
|
|
|
- **Auto-tag at creation, not manual opt-in.** New content — **both new tracks and
|
|
new items** — is tagged to whatever mode is **active at the moment it is created**.
|
|
There is no separate "convert this track" gesture; membership follows the active
|
|
stance automatically.
|
|
- **Pre-existing content defaults to Arrange.** Every track and item that existed
|
|
before the mode system (or before this sub-phase) is an Arrange member, exactly as
|
|
the track-level default already established.
|
|
- **Membership is exclusive.** Design-created content never appears in Arrange, and
|
|
Arrange-created content never appears in Design — the one exception is the existing
|
|
**show-both** escape hatch, which continues to pin a leaf visible across modes.
|
|
- **Track level unchanged; item level is the new reach.** At the track level this is
|
|
the same tag-to-active-mode rule already settled. What is new: when a **shared**
|
|
track (present in both stances — a show-both track, or a folder carrying its own
|
|
media) gains an item in Design mode, that item lands in the track's **Design lane**
|
|
and is invisible/silent in Arrange, and vice versa. That per-item split is the
|
|
fixed-lane mechanism doing its job.
|
|
|
|
This resolves the old fork 2 (opt-in vs. automatic) in favor of **automatic**, and
|
|
it is the through-line that also settles fork 4 (capture placement) below.
|
|
|
|
## Inactive-mode items — settled (hidden AND silenced)
|
|
|
|
When you are in one mode, the other mode's items on a shared track are **both hidden
|
|
and silenced**: the off-mode lane is set `C_LANEPLAYS = 0` — neither shown nor
|
|
played. This matches the exclusive-membership rule (Design-created content is *not
|
|
part of* Arrange, so it must not sound there) and the track-level principle already
|
|
settled: **flipping modes is a real change, not a cosmetic one.** A user who wants
|
|
the design lane audible under the arrangement uses show-both, which is the deliberate
|
|
opt-out; the default is full separation.
|
|
|
|
## Managed vs. manual lanes — settled (2026-07-23)
|
|
|
|
Fixed lanes are not only the mechanism we borrow for mode-item layering — they are
|
|
also **REAPER's native comping surface.** A user may want *their own* multiple lanes
|
|
on a track (manual comp takes, alternate reads) that have nothing to do with the mode
|
|
system. So a fixed-lane track can host **both** kinds of lane at once, and the tool
|
|
must not confuse them. Daniel's constraint, verbatim intent: *"as long as I can still
|
|
opt in manually to put multiple lanes on a track. we must index which lanes are
|
|
managed and which lanes are manual."*
|
|
|
|
The settled distinction:
|
|
|
|
- **Managed lanes** — minted and owned by the mode system to represent a mode's item
|
|
set on that track. Mode toggles drive these: the active mode's managed lane shows
|
|
and plays (`C_LANEPLAYS`), inactive-mode managed lanes are hidden and silenced.
|
|
These are the lanes the two-canvas mechanism creates and controls.
|
|
- **Manual lanes** — lanes the user created outside the mode system (native REAPER
|
|
comping, alternate takes). The mode system did **not** mint these and **must never
|
|
touch them**: a mode toggle never shows, hides, silences, re-lanes, or re-plays a
|
|
manual lane. Its `C_LANEPLAYS` state is the user's, left exactly as they set it.
|
|
|
|
This is the fixed-lane analog of the two invariants already load-bearing in D1 —
|
|
*never touch `B_MUTE`/`I_SOLO`* and *never touch the master* — extended to a third
|
|
surface: **never drive a lane the tool did not mint.** It is the same non-destructive
|
|
promise (the tool owns only what it created) reaching one level deeper, into the lane
|
|
dimension.
|
|
|
|
### Lane-ownership index (the new data)
|
|
|
|
The tool maintains a **lane-ownership index**: per (track GUID, lane) it records
|
|
whether the lane is **managed** (and if so, which mode owns it) or **manual**. This
|
|
rides in the `"reasampler"` `view_state` alongside the membership index, GUID-keyed
|
|
and portable — same travel-with-the-`.rpp` guarantee as everything else in the view
|
|
section. Deciding *which lanes a toggle may touch* (managed only) is a **pure,
|
|
testable** query over this index; reading REAPER's live lane state is **shell**. The
|
|
seam is the same as everywhere else in the phase.
|
|
|
|
> **Lane-identity fragility — flag for the implementation spec, not solved here.**
|
|
> REAPER's `I_FIXEDLANE` is the lane's identity and is how the ownership index keys to
|
|
> a specific lane. Whether `I_FIXEDLANE` values survive lane reorder / renumber /
|
|
> deletion without the index going stale is a **design point the technical spec must
|
|
> resolve** (the same class of concern as GUID-keyed reorder-safety for tracks). The
|
|
> product decision is only that managed and manual lanes are indexed and distinct; how
|
|
> the index stays correct under lane reordering is an implementation call, not a
|
|
> product one.
|
|
|
|
### What a mode toggle does to manual lanes (intended behavior)
|
|
|
|
A mode toggle **leaves every manual lane exactly as the user set it** — its
|
|
`C_LANEPLAYS` (play/show) state is untouched across the toggle. Only managed lanes are
|
|
partitioned into active (shown/played) and inactive (hidden/silenced). Concretely: if
|
|
a shared track has an Arrange managed lane, a Design managed lane, and two manual comp
|
|
lanes the user is auditioning, flipping Arrange↔Design swaps which managed lane plays
|
|
and leaves both manual lanes precisely as they were.
|
|
|
|
**Interaction with auto-tag.** Auto-tag applies to content the user adds as **normal
|
|
timeline content** — items dropped, recorded, or placed onto the arrange surface in
|
|
the active mode. It does **not** reach into a lane the user is hand-managing: items a
|
|
user adds to a **manual** lane are **not** auto-tagged to the active mode, because the
|
|
user has taken explicit lane control there and the tool respects that boundary — the
|
|
manual/managed distinction is exactly the signal that this content is outside the mode
|
|
system.
|
|
|
|
> **Auto-tag/manual-lane boundary heuristic — flag for the implementation spec.**
|
|
> Distinguishing "item added as normal timeline content (auto-tag it)" from "item added
|
|
> to a manual lane the user hand-manages (leave it alone)" may need a heuristic at the
|
|
> detection layer — e.g., an item landing in a lane the ownership index marks manual is
|
|
> exempt from auto-tag; an item landing outside any managed lane on a mode-managed
|
|
> track follows the active-mode rule. The precise rule is an **open implementation
|
|
> design point**, not invented here. The product decision is the boundary itself:
|
|
> manual-lane content is off-limits to auto-tag.
|
|
|
|
## Capture placement — settled (mode-aware)
|
|
|
|
Daniel's rule is general over *"items added to the timeline."* So an **explicit**
|
|
placement while in Design mode — including capture-and-place — lands the item in the
|
|
**Design lane**; the same rule governs manual insertion. This is the same auto-tag
|
|
rule reaching the placement pillar, not a new special case.
|
|
|
|
**The capture load-bearing principle is untouched.** Capture still writes a file to
|
|
the bank and an index entry and **never** auto-inserts into the timeline. This rule
|
|
governs only *where an explicit placement lands* once the user chooses to place —
|
|
it does not create any auto-insertion path. The precision invariants (null test,
|
|
bit-identical repeats, non-destructive, exact bounds, relative paths) are entirely
|
|
unaffected: no capture path changes; lanes are a placement/view concern downstream
|
|
of the file.
|
|
|
|
## REAPER version floor — settled (v7)
|
|
|
|
Fixed lanes shipped in **REAPER 7**, so this sub-phase sets the runtime floor at
|
|
**v7**. The full API surface (track-side `I_FREEMODE=2` / `I_NUMFIXEDLANES` /
|
|
`C_LANEPLAYS:N`, item-side `I_FIXEDLANE` / `C_LANEPLAYS` / `B_FIXEDLANE_HIDDEN`) is
|
|
**verified present in the vendored SDK header**. No version gate branch is needed —
|
|
below v7 the sub-phase is simply unavailable.
|
|
|
|
## Technical consideration — detecting new content (for the implementation spec)
|
|
|
|
**REAPER exposes no clean "item added" / "track added" event callback.** The auto-tag
|
|
rule therefore requires the shell to detect new track/item GUIDs by **diffing project
|
|
state on the panel's existing timer**. The `bank_panel` already polls and fingerprints
|
|
the bank; this extends that same machinery to the timeline's tracks and items.
|
|
|
|
- **Cadence / correctness.** Each poll, the shell compares the live track/item GUID
|
|
set against the previous poll's set; **any GUID new since the last poll is tagged to
|
|
the then-active mode.** The diffing cadence and the "new-since-last-poll ⇒ tag"
|
|
correctness rule are a design point the implementation must handle (e.g., first poll
|
|
after project open must not mass-tag pre-existing content — pre-existing defaults to
|
|
Arrange, per the membership rule).
|
|
- **Pure/shell split.** The **tagging decision** — given a set of new GUIDs plus the
|
|
active mode, produce the membership writes — is **pure and unit-testable**, mirroring
|
|
the existing planner. The **detection** — diffing REAPER's live item/track set each
|
|
timer tick — is **shell**. Keep the seam here: the shell hands the pure model "these
|
|
GUIDs are new, the active mode is M," and the model returns the membership mutations.
|