Make tool-managed lane splits visually transparent: collapse display + lazy-mint

Drive C_LANESCOLLAPSED=2 and hide lane buttons only on tracks the tool itself splits
(gated on the pre-write I_FREEMODE read, so user comp-lane display prefs are never
stomped). Lazy-mint managed lanes only for modes with own content, never an empty
reserved lane; confinement holds via C_LANEPLAYS on the lone lane.
This commit is contained in:
2026-07-24 05:44:19 -04:00
parent ff2c3ebce5
commit 314cd7bf36
4 changed files with 131 additions and 23 deletions
+46 -2
View File
@@ -53,6 +53,39 @@ namespace {
// positioning, 2=fixed lanes.
constexpr int kFreeModeFixedLanes = 2;
// C_LANESCOLLAPSED display value (char*). SDK: 1=lanes collapsed,
// 2=track displays as non-fixed-lanes but hidden lanes exist. Value 2 is the lever that
// makes a tool-split track read like a NORMAL single-lane track showing only the playing
// lane — the inactive/silenced managed lanes are present but not drawn as separate rows.
constexpr int kLanesDisplayAsNormal = 2;
// C_LANESETTINGS bit (char* bitmask). SDK: &32=hide lane buttons. We OR this in (never
// clobber the whole mask) to strip the per-lane button chrome from a tool-split track, so
// it reads as an ordinary track. We deliberately do NOT set &1 (auto-remove empty lanes at
// bottom): a managed lane whose item is later deleted would be silently removed out from
// under the ownership index. The lazy-mint decision already avoids ever minting an empty
// lane, so &1 buys nothing and risks a reconcile hazard.
constexpr int kLaneSettingsHideButtons = 32;
// Drives a TOOL-SPLIT track's display transparent: C_LANESCOLLAPSED=2 (render like a normal
// single-lane track showing only the playing lane) + OR C_LANESETTINGS &32 (hide lane
// buttons). Both are char* params driven through the double API, same convention as
// C_LANEPLAYS:N. C_LANESETTINGS is read-modify-write so any pre-existing bit is preserved.
//
// MANAGED-VS-MANUAL BOUNDARY (load-bearing): these are TRACK-LEVEL settings that affect the
// whole track including a user's own manual comp lanes. Every caller gates this on the
// tool-driven transition INTO fixed lanes (freeMode != 2 before the flip), so a track the
// user already had in fixed-lane mode never reaches it and the user's comp-lane display
// prefs are never stomped. Idempotent: a re-run finds the track already at I_FREEMODE==2,
// the transition branch is skipped, and these writes do not fire again.
void applyTransparentLaneDisplay(MediaTrack* tr) {
SetMediaTrackInfo_Value(tr, "C_LANESCOLLAPSED",
static_cast<double>(kLanesDisplayAsNormal));
const int settings = static_cast<int>(GetMediaTrackInfo_Value(tr, "C_LANESETTINGS"));
SetMediaTrackInfo_Value(tr, "C_LANESETTINGS",
static_cast<double>(settings | kLaneSettingsHideButtons));
}
// The parmname for each planner Flag. All four are documented bool*/int* track
// info params driven through the double-valued Get/SetMediaTrackInfo_Value API.
const char* flagParm(Flag f) {
@@ -224,11 +257,16 @@ bool applyLaneOps(const std::vector<std::pair<std::string, MediaTrack*>>& handle
// Ensure fixed-lane mode is on before driving lane play state. A track carrying
// a managed lane must be in I_FREEMODE=2; set it only if not already, and flag
// that a timeline refresh is owed.
// that a timeline refresh is owed. Every track reaching this loop is already in the
// managed-lane ownership index (planToggle only emits ops for managed lanes), so a
// track here is one the TOOL split — a re-assert of fixed-lane mode is a tool-driven
// (re)split and must carry the same transparent display, mirroring applyMintPlan's
// transition branch. It is never a user's untouched manual-fixed-lane track.
const int freeMode = static_cast<int>(GetMediaTrackInfo_Value(tr, "I_FREEMODE"));
if (freeMode != kFreeModeFixedLanes) {
SetMediaTrackInfo_Value(tr, "I_FREEMODE",
static_cast<double>(kFreeModeFixedLanes));
applyTransparentLaneDisplay(tr); // tool-managed track ⇒ read like a normal track
touchedFreeMode = true;
}
@@ -371,10 +409,16 @@ bool applyMintPlan(ViewModeModel& model, const LaneMintPlan& plan,
MediaTrack* tr = resolve(handleByGuid, split.trackGuid);
if (!tr) continue; // stale GUID — prune
// Enable fixed-lane mode if not already (SDK: UpdateTimeline() owed after).
// Enable fixed-lane mode if not already (SDK: UpdateTimeline() owed after). The
// pre-write freeMode read is ALSO the managed-vs-manual boundary signal: a track that
// was NOT in fixed-lane mode here is one the TOOL is splitting now, so the tool owns
// its lane display and drives it transparent. A track already at I_FREEMODE==2 (user
// had fixed lanes, or a prior tool run) skips this branch — its C_LANESCOLLAPSED /
// C_LANESETTINGS are left exactly as the user set them.
const int freeMode = static_cast<int>(GetMediaTrackInfo_Value(tr, "I_FREEMODE"));
if (freeMode != kFreeModeFixedLanes) {
SetMediaTrackInfo_Value(tr, "I_FREEMODE", static_cast<double>(kFreeModeFixedLanes));
applyTransparentLaneDisplay(tr); // tool-split track ⇒ read like a normal track
changed = true;
}
+12 -8
View File
@@ -220,15 +220,19 @@ LaneMintPlan planLaneMinting(const ViewModeModel& model, const FolderTree& tree,
// this is the load-bearing "don't lane-split single-mode tracks" rule.
if (!multiMode) continue;
// Lanes to mint = the UNION of the modes the own items belong to and the modes the
// track is visible in. A folder whose own item is Design-only but which is derived-
// visible in Arrange too mints BOTH a Design lane (holding the item) and an Arrange
// lane (reserving Arrange's stance slot), matching "each mode owns a fixed lane."
std::set<std::string> laneModes = ownItemModes;
if (visIt != visibleModesOf.end())
laneModes.insert(visIt->second.begin(), visIt->second.end());
// Lazy-mint: lanes to mint = ONLY the modes the track's OWN items actually occupy —
// never an empty reserved lane for a mode the track is merely derived-visible in.
// A folder whose own item is Design-only but which is derived-visible in Arrange too
// mints a Design lane ONLY (holding the item); it mints NO Arrange lane. Confinement
// still holds: with only a Design lane present, toggling to Arrange drives that lane's
// C_LANEPLAYS to 0 (it hides+silences) and no lane plays, so the track reads as an
// empty normal track — the Design item does not leak. The Arrange lane is minted on
// demand the moment an Arrange item first lands (a later mint tick sees ownItemModes
// gain Arrange). The visibility trigger above still decides WHETHER to split; it no
// longer inflates WHICH lanes are minted.
const std::set<std::string>& laneModes = ownItemModes;
// Transition to lane-split: one managed lane per involved mode (durable key =
// Transition to lane-split: one managed lane per own-content mode (durable key =
// laneNameForMode(mode)), owned by that mode.
plan.splits.push_back(LaneMintPlan::TrackSplit{
track.trackGuid, static_cast<int>(laneModes.size())});
+13 -8
View File
@@ -641,14 +641,19 @@ struct LaneMintPlan {
// * A track visible in exactly ONE mode (single-mode leaf, single-mode folder) stays
// whole-track-parked (D1) — NO split. This is the single-mode-track rule.
// * On a split: one TrackSplit (laneCount == number of lanes to mint), one LaneMint per
// involved mode, and one LaneAssign per managed-eligible OWN item onto ITS tagged
// mode's lane — INCLUDING pre-existing items, so a folder carrying one own Design item
// while derived-visible in Arrange too still lanes that item to the Design lane (it
// then hides+silences whenever Arrange is active). The lanes minted are the union of:
// the modes the track's own items belong to, PLUS the modes the track is visible in
// so a folder whose own item is Design-only but which is derived-visible in Arrange
// mints BOTH a Design lane (holding the item) and an Arrange lane (empty, reserving
// the Arrange stance's slot), matching "each mode owns a fixed lane."
// mode the track's OWN items occupy, and one LaneAssign per managed-eligible OWN item
// onto ITS tagged mode's lane — INCLUDING pre-existing items, so a folder carrying one
// own Design item while derived-visible in Arrange too still lanes that item to the
// Design lane (it then hides+silences whenever Arrange is active).
// * LAZY-MINT: lanes are minted ONLY for modes the track's own items actually occupy
// never an empty reserved lane for a mode the track is merely derived-visible in. So a
// folder whose own item is Design-only but which is derived-visible in Arrange mints a
// Design lane ONLY (holding the item), NOT an empty Arrange lane. Confinement still
// holds: with only a Design lane present, toggling to Arrange drives that lane's
// C_LANEPLAYS to 0 (hide+silence) and no lane plays, so the track reads as an empty
// normal track and the Design item does not leak. The Arrange lane is minted on demand
// when an Arrange item first lands. The derived-visibility trigger still decides WHETHER
// to split; it no longer inflates WHICH lanes are minted.
//
// Items with an empty GUID or empty modeId are skipped (defensive; a real item always
// resolves to a mode). The function mutates nothing — it returns a plan the shell