fix(D2-W2): correct manual-lane exemption, drop read-only item C_LANEPLAYS write

isOnManualLane predicate now correctly treats unnamed fixed-lane lanes as manual;
item-side C_LANEPLAYS loop removed (SDK read-only); explicit #include <set> in
view_mode_model.cpp; undo-block omission in detectNewContent documented as intentional.
This commit is contained in:
2026-07-23 17:51:55 -04:00
parent af34143107
commit ebdb4eae66
6 changed files with 96 additions and 28 deletions
+34 -9
View File
@@ -566,13 +566,22 @@ bool refreshFingerprint() {
// enumeration is READ-ONLY on the project; the only mutation is to the in-memory
// membership index (persisted by persist on the next save, same as an action-driven tag).
// The durable name of item `it`'s fixed lane, or empty if the item's track is not in
// fixed-lane mode (⇒ not a lane at all, treated as non-manual normal content). Mirrors
// view.cpp's laneName but item-side: read the item's I_FIXEDLANE ordinal, then that
// lane's P_LANENAME:n off the owning track.
// True iff `tr` has I_FREEMODE==2 (fixed lanes enabled). The SDK value is verified
// in view.cpp (kFreeModeFixedLanes=2); reproduced here as a local constant so
// bank_panel.cpp stays self-contained without pulling in view.cpp's private namespace.
constexpr int kFreeModeFixedLanes = 2;
bool isFixedLaneTrack(MediaTrack* tr) {
return static_cast<int>(GetMediaTrackInfo_Value(tr, "I_FREEMODE")) == kFreeModeFixedLanes;
}
// Reads the durable P_LANENAME for the lane that item `it` sits on. Returns empty if
// the lane is unnamed or P_LANENAME is unavailable. Callers must already know the track
// is a fixed-lane track (I_FREEMODE==2) before calling this — the manual/non-manual
// distinction only applies there. On a non-fixed-lane track `I_FIXEDLANE` is
// meaningless; the isOnManualLane predicate handles that case via its isFixedLaneTrack
// argument, so callers should not call this at all for non-fixed-lane tracks.
std::string itemLaneName(MediaTrack* tr, MediaItem* it) {
if (static_cast<int>(GetMediaTrackInfo_Value(tr, "I_FREEMODE")) != 2)
return {}; // not a fixed-lane track ⇒ no lane name (normal timeline content)
const int laneIdx = static_cast<int>(GetMediaItemInfo_Value(it, "I_FIXEDLANE"));
char parm[32];
std::snprintf(parm, sizeof(parm), "P_LANENAME:%d", laneIdx);
@@ -592,6 +601,10 @@ std::string itemGuid(MediaItem* it) {
// Enumerates the live project's track + item GUIDs. Fills `allGuids` (the full live set,
// baseline input) and, for each item, records whether it sits on a manual lane so a
// newly-detected item can be exempted from auto-tag without a second project walk.
//
// Manual-lane classification uses the single pure predicate isOnManualLane(isFixedLaneTrack,
// laneName) from lane_keys — the same predicate the apply path consults — so the exemption
// rule is defined in exactly one place and is unit-tested there.
void enumerateLiveGuids(ReaProject* proj, std::set<std::string>& allGuids,
std::map<std::string, bool>& itemOnManualLane) {
const int trackCount = CountTracks(proj);
@@ -601,6 +614,10 @@ void enumerateLiveGuids(ReaProject* proj, std::set<std::string>& allGuids,
std::string tg = guidString(tr);
if (!tg.empty()) allGuids.insert(tg);
// Compute the fixed-lane status once per track (not per item) — I_FREEMODE is a
// track-level attribute and is the same for every item on the track.
const bool fixedLane = isFixedLaneTrack(tr);
const int itemCount = CountTrackMediaItems(tr);
for (int i = 0; i < itemCount; ++i) {
MediaItem* it = GetTrackMediaItem(tr, i);
@@ -608,9 +625,11 @@ void enumerateLiveGuids(ReaProject* proj, std::set<std::string>& allGuids,
std::string ig = itemGuid(it);
if (ig.empty()) continue;
allGuids.insert(ig);
// Manual iff the item is on a fixed lane whose name is NOT tool-managed.
const std::string ln = itemLaneName(tr, it);
itemOnManualLane[ig] = (!ln.empty() && !isManagedLaneName(ln));
// Classify via the single shared predicate. For a fixed-lane track we read
// the item's lane name; for a normal track we pass "" (isOnManualLane returns
// false immediately for non-fixed-lane tracks regardless of name).
const std::string ln = fixedLane ? itemLaneName(tr, it) : std::string{};
itemOnManualLane[ig] = isOnManualLane(fixedLane, ln);
}
}
}
@@ -619,6 +638,12 @@ void enumerateLiveGuids(ReaProject* proj, std::set<std::string>& allGuids,
// into the active mode. Runs every timer tick regardless of panel open/close (content
// is created in the arrange). READ-ONLY on the project; mutates only the in-memory
// membership index.
//
// INTENTIONAL: membership mutation happens OUTSIDE any Undo block. Auto-tag is a
// background metadata update (like setting a label), not a destructive project edit.
// persist.cpp writes it on the next project save alongside the bank and view state, the
// same way an action-driven tag is persisted. Wrapping this in an Undo block would flood
// the REAPER undo history with a new entry for every timer tick that sees new content.
void detectNewContent() {
if (!g_panel.session) return;