Key Design View's parked FX-offline state to the FX's own GUID, not its slot
view_state v2 writes identities beside the v1 slot array, so a downgrade keeps what it had. An FX gone at restore time is dropped and reported, never restored onto whatever took its place.
This commit is contained in:
@@ -34,7 +34,10 @@ decide membership or mode rules.
|
||||
value BEFORE parking; on toggle-back restore FROM the snapshot, never to a
|
||||
hardcoded "on." Round-trip (snapshot → park → restore) returns every driven flag
|
||||
to its captured value — the phase's trust anchor, the analog of the capture null
|
||||
test.
|
||||
test. Per-FX offline is snapshotted WITH each FX's identity (`TrackFX_GetFXGUID`)
|
||||
and restored through `core/view/fx_offline`, so a chain reordered while the track
|
||||
was parked cannot land one plugin's state on another; an FX gone at restore time
|
||||
is dropped and reported to the console, never restored onto its old slot.
|
||||
- **GUID-keyed, reorder-safe.** Membership/snapshot keys on track GUID
|
||||
(`GetTrackGUID`), never track index; tolerates unknown/stale GUIDs (pruned on
|
||||
reconcile).
|
||||
|
||||
+53
-20
@@ -14,6 +14,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "shell/capture/item_read.h"
|
||||
#include "core/view/fx_offline.h"
|
||||
#include "core/view/lane_keys.h"
|
||||
#include "core/view/solo_cache.h"
|
||||
#include "shell/capture/track_guid.h"
|
||||
@@ -27,9 +28,12 @@
|
||||
#define REAPERAPI_WANT_GetMediaTrackInfo_Value
|
||||
#define REAPERAPI_WANT_SetMediaTrackInfo_Value
|
||||
#define REAPERAPI_WANT_GetSetMediaTrackInfo_String
|
||||
#define REAPERAPI_WANT_ShowConsoleMsg
|
||||
#define REAPERAPI_WANT_TrackFX_GetCount
|
||||
#define REAPERAPI_WANT_TrackFX_GetFXGUID
|
||||
#define REAPERAPI_WANT_TrackFX_GetOffline
|
||||
#define REAPERAPI_WANT_TrackFX_SetOffline
|
||||
#define REAPERAPI_WANT_guidToString
|
||||
#define REAPERAPI_WANT_Undo_BeginBlock2
|
||||
#define REAPERAPI_WANT_Undo_EndBlock2
|
||||
#define REAPERAPI_WANT_TrackList_AdjustWindows
|
||||
@@ -123,6 +127,29 @@ MediaTrack* resolve(const std::vector<std::pair<std::string, MediaTrack*>>& hand
|
||||
return nullptr; // stale/deleted GUID — pruned by being skipped
|
||||
}
|
||||
|
||||
// The FX's own durable identity, braced exactly like the track GUID keys. Empty
|
||||
// when REAPER reports none — an FX we cannot name is one we cannot restore, and
|
||||
// fx_offline treats it that way rather than guessing at its slot.
|
||||
std::string fxGuidString(MediaTrack* tr, int fx) {
|
||||
GUID* g = TrackFX_GetFXGUID(tr, fx);
|
||||
if (!g) return {};
|
||||
char buf[64] = {0}; // guidToString needs a >=64-char destination (SDK contract)
|
||||
guidToString(g, buf);
|
||||
return std::string(buf);
|
||||
}
|
||||
|
||||
// The chain as it stands now: identity by current slot. Snapshot, park and
|
||||
// restore all address FX through this one plain 0..TrackFX_GetCount-1
|
||||
// enumeration — never the 0x1000000/0x2000000 input-FX or container forms — so
|
||||
// whatever it covers, all three cover identically.
|
||||
std::vector<std::string> liveFxGuids(MediaTrack* tr) {
|
||||
const int fxCount = TrackFX_GetCount(tr);
|
||||
std::vector<std::string> guids;
|
||||
guids.reserve(static_cast<std::size_t>(fxCount));
|
||||
for (int fx = 0; fx < fxCount; ++fx) guids.push_back(fxGuidString(tr, fx));
|
||||
return guids;
|
||||
}
|
||||
|
||||
// Captures prior driven-flag state before parking. Never reads B_MUTE/I_SOLO;
|
||||
// ints preserve whatever REAPER reported (TrackSnapshot's defensive contract).
|
||||
TrackSnapshot snapshotTrack(MediaTrack* tr) {
|
||||
@@ -132,12 +159,13 @@ TrackSnapshot snapshotTrack(MediaTrack* tr) {
|
||||
snap.mainSend = static_cast<int>(GetMediaTrackInfo_Value(tr, "B_MAINSEND"));
|
||||
snap.fxEnable = static_cast<int>(GetMediaTrackInfo_Value(tr, "I_FXEN"));
|
||||
|
||||
int fxCount = TrackFX_GetCount(tr);
|
||||
snap.fxOffline.reserve(static_cast<std::size_t>(fxCount));
|
||||
for (int fx = 0; fx < fxCount; ++fx) {
|
||||
snap.fxOffline.push_back(TrackFX_GetOffline(tr, fx) ? 1 : 0);
|
||||
const std::vector<std::string> guids = liveFxGuids(tr);
|
||||
snap.fxOffline.reserve(guids.size());
|
||||
for (std::size_t fx = 0; fx < guids.size(); ++fx) {
|
||||
snap.fxOffline.push_back(
|
||||
FxOfflineState{guids[fx], TrackFX_GetOffline(tr, static_cast<int>(fx)) ? 1 : 0});
|
||||
}
|
||||
return snap;
|
||||
return snap; // fxKeying stays Identity — a live capture always knows the chain
|
||||
}
|
||||
|
||||
void applyFlags(MediaTrack* tr, const std::vector<TrackFlagOp>& flags) {
|
||||
@@ -155,20 +183,13 @@ void parkFxOffline(MediaTrack* tr) {
|
||||
}
|
||||
}
|
||||
|
||||
// Restores per-FX offline from the snapshot verbatim — each slot back to its
|
||||
// captured value, never a blanket "online" — bounds-checked against the live
|
||||
// FX count (prune-safe if the chain changed while parked).
|
||||
//
|
||||
// HAZARD (open, tracked in docs/TODO.md): this remaps by slot INDEX, not
|
||||
// plugin identity. If the FX chain reshuffled while parked, snapshot slot k
|
||||
// restores onto whatever plugin now occupies slot k. Accepted for now;
|
||||
// identity-based reconciliation is future hardening.
|
||||
void restoreFxOffline(MediaTrack* tr, const std::vector<FxOfflineOp>& fxOffline) {
|
||||
int fxCount = TrackFX_GetCount(tr);
|
||||
for (const FxOfflineOp& op : fxOffline) {
|
||||
if (op.fxIndex < 0 || op.fxIndex >= fxCount) continue;
|
||||
TrackFX_SetOffline(tr, op.fxIndex, op.offline);
|
||||
}
|
||||
// Restores per-FX offline from the snapshot verbatim — never a blanket "online".
|
||||
// Which live FX each captured state belongs to is resolveFxRestore's call, and
|
||||
// what it could not place comes back for the caller to report.
|
||||
FxRestoreDrops restoreFxOffline(MediaTrack* tr, const std::vector<FxOfflineOp>& fxOffline) {
|
||||
const FxRestoreResolution res = resolveFxRestore(fxOffline, liveFxGuids(tr));
|
||||
for (const FxOfflineWrite& w : res.writes) TrackFX_SetOffline(tr, w.fxIndex, w.offline);
|
||||
return res.drops;
|
||||
}
|
||||
|
||||
// Managed-lane application: the pure planner keys LanePlayOps by the lane's
|
||||
@@ -451,6 +472,8 @@ bool applyMode(ViewModeModel& model, const std::string& targetModeId, ReaProject
|
||||
}
|
||||
|
||||
// RESTORE: apply verbatim, then drop the consumed snapshot.
|
||||
FxRestoreDrops fxDrops;
|
||||
int fxDropTracks = 0;
|
||||
for (const TrackPlan& tp : plan.restore) {
|
||||
if (tp.flags.empty()) continue;
|
||||
const std::string& guid = tp.flags.front().guid;
|
||||
@@ -458,10 +481,20 @@ bool applyMode(ViewModeModel& model, const std::string& targetModeId, ReaProject
|
||||
if (!tr) continue; // stale GUID — prune
|
||||
|
||||
applyFlags(tr, tp.flags);
|
||||
restoreFxOffline(tr, tp.fxOffline);
|
||||
const FxRestoreDrops drops = restoreFxOffline(tr, tp.fxOffline);
|
||||
if (drops.total() > 0) {
|
||||
fxDrops.add(drops);
|
||||
++fxDropTracks;
|
||||
}
|
||||
model.clearSnapshot(guid);
|
||||
}
|
||||
|
||||
// Captured FX state that could not be applied is REPORTED. Silence here would
|
||||
// read to the user as "restore worked" while an FX sat at whatever state the
|
||||
// park left it in.
|
||||
const std::string fxDropMsg = describeFxRestoreDrops(fxDrops, fxDropTracks);
|
||||
if (!fxDropMsg.empty()) ShowConsoleMsg(fxDropMsg.c_str());
|
||||
|
||||
// MANAGED LANES: drive C_LANEPLAYS so the active mode's lane plays+shows
|
||||
// and every other managed lane is silenced+hidden. Empty for a D1-only
|
||||
// project, leaving that behavior byte-identical.
|
||||
|
||||
Reference in New Issue
Block a user