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:
+34
-9
@@ -566,13 +566,22 @@ bool refreshFingerprint() {
|
|||||||
// enumeration is READ-ONLY on the project; the only mutation is to the in-memory
|
// 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).
|
// 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
|
// True iff `tr` has I_FREEMODE==2 (fixed lanes enabled). The SDK value is verified
|
||||||
// fixed-lane mode (⇒ not a lane at all, treated as non-manual normal content). Mirrors
|
// in view.cpp (kFreeModeFixedLanes=2); reproduced here as a local constant so
|
||||||
// view.cpp's laneName but item-side: read the item's I_FIXEDLANE ordinal, then that
|
// bank_panel.cpp stays self-contained without pulling in view.cpp's private namespace.
|
||||||
// lane's P_LANENAME:n off the owning track.
|
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) {
|
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"));
|
const int laneIdx = static_cast<int>(GetMediaItemInfo_Value(it, "I_FIXEDLANE"));
|
||||||
char parm[32];
|
char parm[32];
|
||||||
std::snprintf(parm, sizeof(parm), "P_LANENAME:%d", laneIdx);
|
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,
|
// 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
|
// 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.
|
// 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,
|
void enumerateLiveGuids(ReaProject* proj, std::set<std::string>& allGuids,
|
||||||
std::map<std::string, bool>& itemOnManualLane) {
|
std::map<std::string, bool>& itemOnManualLane) {
|
||||||
const int trackCount = CountTracks(proj);
|
const int trackCount = CountTracks(proj);
|
||||||
@@ -601,6 +614,10 @@ void enumerateLiveGuids(ReaProject* proj, std::set<std::string>& allGuids,
|
|||||||
std::string tg = guidString(tr);
|
std::string tg = guidString(tr);
|
||||||
if (!tg.empty()) allGuids.insert(tg);
|
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);
|
const int itemCount = CountTrackMediaItems(tr);
|
||||||
for (int i = 0; i < itemCount; ++i) {
|
for (int i = 0; i < itemCount; ++i) {
|
||||||
MediaItem* it = GetTrackMediaItem(tr, i);
|
MediaItem* it = GetTrackMediaItem(tr, i);
|
||||||
@@ -608,9 +625,11 @@ void enumerateLiveGuids(ReaProject* proj, std::set<std::string>& allGuids,
|
|||||||
std::string ig = itemGuid(it);
|
std::string ig = itemGuid(it);
|
||||||
if (ig.empty()) continue;
|
if (ig.empty()) continue;
|
||||||
allGuids.insert(ig);
|
allGuids.insert(ig);
|
||||||
// Manual iff the item is on a fixed lane whose name is NOT tool-managed.
|
// Classify via the single shared predicate. For a fixed-lane track we read
|
||||||
const std::string ln = itemLaneName(tr, it);
|
// the item's lane name; for a normal track we pass "" (isOnManualLane returns
|
||||||
itemOnManualLane[ig] = (!ln.empty() && !isManagedLaneName(ln));
|
// 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
|
// 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
|
// is created in the arrange). READ-ONLY on the project; mutates only the in-memory
|
||||||
// membership index.
|
// 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() {
|
void detectNewContent() {
|
||||||
if (!g_panel.session) return;
|
if (!g_panel.session) return;
|
||||||
|
|
||||||
|
|||||||
@@ -31,4 +31,14 @@ std::string laneNameForMode(const std::string& modeId) {
|
|||||||
return std::string(kManagedLanePrefix) + modeId;
|
return std::string(kManagedLanePrefix) + modeId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isOnManualLane(bool isFixedLaneTrack, const std::string& laneName) {
|
||||||
|
// On a normal (non-fixed-lane) track there is no concept of a manual lane; the
|
||||||
|
// item follows the normal auto-tag rule.
|
||||||
|
if (!isFixedLaneTrack) return false;
|
||||||
|
// On a fixed-lane track: a managed lane (prefixed) is NOT manual; everything else
|
||||||
|
// — including the empty/unnamed lane that REAPER creates by default — IS manual
|
||||||
|
// (user-minted, off-limits to auto-tag and to the lane-drive path).
|
||||||
|
return !hasManagedPrefix(laneName);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace reasampler
|
} // namespace reasampler
|
||||||
|
|||||||
@@ -58,4 +58,20 @@ std::optional<std::string> managedLaneKey(const std::string& laneName);
|
|||||||
// contract is asserted here so minting and reading cannot drift.
|
// contract is asserted here so minting and reading cannot drift.
|
||||||
std::string laneNameForMode(const std::string& modeId);
|
std::string laneNameForMode(const std::string& modeId);
|
||||||
|
|
||||||
|
// True iff an item on a fixed-lane track with the given lane name is on a MANUAL lane
|
||||||
|
// (i.e. exempt from auto-tag). The two inputs are:
|
||||||
|
// isFixedLaneTrack — whether the item's track has I_FREEMODE==2. On a normal
|
||||||
|
// (non-fixed-lane) track the concept of a "manual lane" does not
|
||||||
|
// apply; the item follows the normal auto-tag rule (return false).
|
||||||
|
// laneName — the durable P_LANENAME of the lane the item sits on. A lane
|
||||||
|
// that carries kManagedLanePrefix is a tool-minted managed lane
|
||||||
|
// (not manual); any other name — including empty (unnamed) — is
|
||||||
|
// a user-minted manual lane (exempt from auto-tag).
|
||||||
|
//
|
||||||
|
// This is the SINGLE predicate that governs BOTH the apply path (which lanes may be
|
||||||
|
// driven) and the auto-tag exemption path (which items are exempt). It is unit-tested
|
||||||
|
// here so both paths share exactly one definition; the shell supplies the two REAPER
|
||||||
|
// inputs (I_FREEMODE result, P_LANENAME string) and never re-derives this logic.
|
||||||
|
bool isOnManualLane(bool isFixedLaneTrack, const std::string& laneName);
|
||||||
|
|
||||||
} // namespace reasampler
|
} // namespace reasampler
|
||||||
|
|||||||
+11
-19
@@ -31,10 +31,6 @@
|
|||||||
#define REAPERAPI_WANT_TrackFX_GetCount
|
#define REAPERAPI_WANT_TrackFX_GetCount
|
||||||
#define REAPERAPI_WANT_TrackFX_GetOffline
|
#define REAPERAPI_WANT_TrackFX_GetOffline
|
||||||
#define REAPERAPI_WANT_TrackFX_SetOffline
|
#define REAPERAPI_WANT_TrackFX_SetOffline
|
||||||
#define REAPERAPI_WANT_CountTrackMediaItems
|
|
||||||
#define REAPERAPI_WANT_GetTrackMediaItem
|
|
||||||
#define REAPERAPI_WANT_GetMediaItemInfo_Value
|
|
||||||
#define REAPERAPI_WANT_SetMediaItemInfo_Value
|
|
||||||
#define REAPERAPI_WANT_Undo_BeginBlock2
|
#define REAPERAPI_WANT_Undo_BeginBlock2
|
||||||
#define REAPERAPI_WANT_Undo_EndBlock2
|
#define REAPERAPI_WANT_Undo_EndBlock2
|
||||||
#define REAPERAPI_WANT_TrackList_AdjustWindows
|
#define REAPERAPI_WANT_TrackList_AdjustWindows
|
||||||
@@ -180,25 +176,21 @@ std::map<std::string, int> managedLaneOrdinals(MediaTrack* tr) {
|
|||||||
return byKey;
|
return byKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Drives one managed lane on `tr` to `lanePlays` (C_LANEPLAYS value): the track-side
|
// Drives one managed lane on `tr` to `lanePlays` (C_LANEPLAYS value) via the
|
||||||
// C_LANEPLAYS:N for lane ordinal `laneIdx`, plus every ITEM on that lane (item-side
|
// TRACK-SIDE C_LANEPLAYS:N write. Track-side C_LANEPLAYS:N alone produces the
|
||||||
// C_LANEPLAYS). Items are matched to the lane by their read-only I_FIXEDLANE ordinal.
|
// hide+silence effect for all items on lane N — no per-item write is needed or
|
||||||
// B_FIXEDLANE_HIDDEN is READ-ONLY (SDK) — hide/show follows from C_LANEPLAYS=0/1, never
|
// possible (item-side C_LANEPLAYS is marked read-only in the SDK).
|
||||||
// written directly. Non-destructive: only reversible play/show flags; no item is moved
|
// B_FIXEDLANE_HIDDEN is READ-ONLY (SDK) — hide/show follows from C_LANEPLAYS=0/1,
|
||||||
// or deleted.
|
// never written directly. Non-destructive: only reversible play/show flags; no item
|
||||||
|
// is moved or deleted.
|
||||||
|
//
|
||||||
|
// DAW-VERIFY: confirm that track-side C_LANEPLAYS:N alone hides+silences all items
|
||||||
|
// on lane N without a per-item write. (SDK marks item-side C_LANEPLAYS as read-only;
|
||||||
|
// the track-side write is the documented mechanism.)
|
||||||
void applyLanePlays(MediaTrack* tr, int laneIdx, int lanePlays) {
|
void applyLanePlays(MediaTrack* tr, int laneIdx, int lanePlays) {
|
||||||
char parm[32];
|
char parm[32];
|
||||||
std::snprintf(parm, sizeof(parm), "C_LANEPLAYS:%d", laneIdx);
|
std::snprintf(parm, sizeof(parm), "C_LANEPLAYS:%d", laneIdx);
|
||||||
SetMediaTrackInfo_Value(tr, parm, static_cast<double>(lanePlays));
|
SetMediaTrackInfo_Value(tr, parm, static_cast<double>(lanePlays));
|
||||||
|
|
||||||
const int itemCount = CountTrackMediaItems(tr);
|
|
||||||
for (int i = 0; i < itemCount; ++i) {
|
|
||||||
MediaItem* it = GetTrackMediaItem(tr, i);
|
|
||||||
if (!it) continue;
|
|
||||||
const int itemLane = static_cast<int>(GetMediaItemInfo_Value(it, "I_FIXEDLANE"));
|
|
||||||
if (itemLane != laneIdx) continue; // item is on a different lane
|
|
||||||
SetMediaItemInfo_Value(it, "C_LANEPLAYS", static_cast<double>(lanePlays));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Applies the plan's managed-lane ops. Groups ops by track, resolves each op's durable
|
// Applies the plan's managed-lane ops. Groups ops by track, resolves each op's durable
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include <climits>
|
#include <climits>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
#include <set>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
// view_mode_model implementation.
|
// view_mode_model implementation.
|
||||||
|
|||||||
@@ -46,6 +46,29 @@ static void testManagedLaneKey() {
|
|||||||
CHECK(!managedLaneKey("guitar-double").has_value());
|
CHECK(!managedLaneKey("guitar-double").has_value());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void testIsOnManualLane() {
|
||||||
|
// Non-fixed-lane track: concept does not apply regardless of name.
|
||||||
|
CHECK(!isOnManualLane(false, "")); // normal track, unnamed ⇒ not manual
|
||||||
|
CHECK(!isOnManualLane(false, "Comp 1")); // normal track, user name ⇒ not manual
|
||||||
|
CHECK(!isOnManualLane(false, "reasampler:design")); // normal track, managed name ⇒ not manual
|
||||||
|
|
||||||
|
// Fixed-lane track: managed lane (tool-prefixed) ⇒ NOT manual (tool drives it).
|
||||||
|
CHECK(!isOnManualLane(true, "reasampler:design"));
|
||||||
|
CHECK(!isOnManualLane(true, "reasampler:arrange"));
|
||||||
|
CHECK(!isOnManualLane(true, "reasampler:")); // prefix-only: still managed
|
||||||
|
|
||||||
|
// Fixed-lane track: unnamed lane (empty P_LANENAME) ⇒ manual.
|
||||||
|
// REAPER starts fixed lanes unnamed; an item on an unnamed fixed lane is a user
|
||||||
|
// comp lane and must be exempt from auto-tag.
|
||||||
|
CHECK(isOnManualLane(true, ""));
|
||||||
|
|
||||||
|
// Fixed-lane track: user-named but non-managed ⇒ manual.
|
||||||
|
CHECK(isOnManualLane(true, "Comp 1"));
|
||||||
|
CHECK(isOnManualLane(true, "Lead vocal"));
|
||||||
|
CHECK(isOnManualLane(true, "reasample")); // near-miss, no colon ⇒ manual
|
||||||
|
CHECK(isOnManualLane(true, "Reasampler:x")); // wrong case ⇒ manual
|
||||||
|
}
|
||||||
|
|
||||||
static void testRoundTrip() {
|
static void testRoundTrip() {
|
||||||
// Minting then reading must agree: managedLaneKey(laneNameForMode(m)) recovers the
|
// Minting then reading must agree: managedLaneKey(laneNameForMode(m)) recovers the
|
||||||
// prefixed name for every mode id.
|
// prefixed name for every mode id.
|
||||||
@@ -62,6 +85,7 @@ static void testRoundTrip() {
|
|||||||
int main() {
|
int main() {
|
||||||
testIsManagedLaneName();
|
testIsManagedLaneName();
|
||||||
testManagedLaneKey();
|
testManagedLaneKey();
|
||||||
|
testIsOnManualLane();
|
||||||
testRoundTrip();
|
testRoundTrip();
|
||||||
|
|
||||||
if (g_fail == 0) std::printf("All tests passed.\n");
|
if (g_fail == 0) std::printf("All tests passed.\n");
|
||||||
|
|||||||
Reference in New Issue
Block a user