Add the FX reclaimer: a debounced, time-boxed convergence loop that offlines parked chains and restores returning ones off the switch path

This commit is contained in:
2026-08-06 19:07:17 -04:00
parent a9d2397586
commit 5a35b71dc0
8 changed files with 840 additions and 22 deletions
+19 -22
View File
@@ -22,28 +22,6 @@ namespace reasampler {
namespace {
// 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. Lifetime is
// settled: the string copy is taken immediately and the GUID* is never held past
// this call (reaper_plugin_functions.h:7348 documents no null contract for
// TrackFX_GetFXGUID; treating null as "no identity" is the safe read).
//
// [verify — DAW] STABILITY across a chain mutation is not settled the same way:
// confirm the GUID for one FX instance survives a native drag-reorder, an SWS move
// (SNM_MoveOrRemoveTrackFX — SWS issue #802 reports the FXID lines do not follow
// the plugin after that call, i.e. wrong-plugin restores or mass drops through
// fx_offline on that path specifically), a save/reload round trip, and two live
// instances of one plugin type staying distinguishable. See src/shell/view/CLAUDE.md's
// Gotchas for the SWS-path risk this leaves open.
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);
}
// Captures prior driven-flag state, and the chain's per-FX offline state, before
// parking. Never reads B_MUTE/I_SOLO; ints preserve whatever REAPER reported
// (TrackSnapshot's defensive contract). The plain 0..TrackFX_GetCount-1 enumeration
@@ -96,6 +74,25 @@ std::vector<std::string> g_reportedExposed;
const int kViewUndoMask = UNDO_STATE_TRACKCFG | UNDO_STATE_ITEMS | UNDO_STATE_MISCCFG;
// Lifetime is settled: the string copy is taken immediately and the GUID* is never held
// past this call (reaper_plugin_functions.h:7348 documents no null contract for
// TrackFX_GetFXGUID; treating null as "no identity" is the safe read).
//
// [verify — DAW] STABILITY across a chain mutation is not settled the same way: confirm
// the GUID for one FX instance survives a native drag-reorder, an SWS move
// (SNM_MoveOrRemoveTrackFX — SWS issue #802 reports the FXID lines do not follow the
// plugin after that call, i.e. wrong-plugin restores or mass drops through fx_offline on
// that path specifically), a save/reload round trip, and two live instances of one plugin
// type staying distinguishable. See src/shell/view/CLAUDE.md's Gotchas for the SWS-path
// risk this leaves open.
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);
}
bool writeIfChanged(MediaTrack* tr, const char* parm, double value) {
if (GetMediaTrackInfo_Value(tr, parm) == value) return false;
SetMediaTrackInfo_Value(tr, parm, value);