docs: settle Design View two-canvas sub-phase (fixed-lane per-item modes)
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.
This commit is contained in:
+340
-8
@@ -6,8 +6,10 @@ detail in `CONTEXT.md` (§Design View). This doc holds the *why* — the workflo
|
||||
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.
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
@@ -25,12 +27,19 @@ 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.
|
||||
**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
|
||||
@@ -345,3 +354,326 @@ tooltip on the switch.
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user