Extend the park trust test to the FX chain, name the refused tracks, and stop reprinting an unchanged refusal

This commit is contained in:
2026-08-05 17:10:19 -04:00
parent 203961f41c
commit 5376ab085c
8 changed files with 318 additions and 86 deletions
+54 -6
View File
@@ -5,6 +5,7 @@
#include "shell/view/view_fx_park.h"
#include <cstring> // strnlen — bounded read of GetTrackName's buffer
#include <string>
#include <unordered_map>
#include <vector>
@@ -16,6 +17,7 @@
#define REAPERAPI_WANT_EnumProjects
#define REAPERAPI_WANT_GetMediaTrackInfo_Value
#define REAPERAPI_WANT_GetTrack
#define REAPERAPI_WANT_GetTrackName
#define REAPERAPI_WANT_ShowConsoleMsg
#define REAPERAPI_WANT_TrackFX_GetCount
#define REAPERAPI_WANT_TrackFX_GetFXGUID
@@ -33,6 +35,10 @@ FxParkQueue g_queue;
ReaProject* g_owner = nullptr; // the project the pending intents were enqueued against
bool g_draining = false;
// The last refusal report, so an unchanged one is not reprinted on every reapply.
ReaProject* g_reportedOwner = nullptr;
std::vector<std::string> g_reportedRefusals;
// Makes "one drain at a time" explicit rather than implied by the call sites.
// RAII because an apply can throw and a stuck flag would silence the queue for
// the rest of the session.
@@ -98,14 +104,56 @@ std::string fxGuidString(MediaTrack* tr, int fx) {
return std::string(buf);
}
// ANY offline FX, not every one: a park offlines the whole chain, but a partial
// undo or a half-finished hand recovery leaves some back online, and there is no
// read that tells a park's leftover offline from one the user set. So the test
// refuses in the direction that commits nothing false — the cost is a chain
// carrying a hand-offlined plugin that the tool then declines to park.
bool anyFxOffline(MediaTrack* tr) {
const int fxCount = TrackFX_GetCount(tr);
for (int fx = 0; fx < fxCount; ++fx)
if (TrackFX_GetOffline(tr, fx)) return true;
return false;
}
// "Track N" for an unnamed track, matching REAPER's own convention (GetTrackName,
// not P_NAME, for the same reason capture::trackName picks it). The buffer is
// read bounded — the SDK documents no NUL within bufOut_sz. A track the report
// cannot name still has to appear in it, so the read failing yields a placeholder
// rather than dropping the entry.
std::string trackDisplayName(MediaTrack* tr) {
std::vector<char> buf(1024, '\0');
if (!GetTrackName || !GetTrackName(tr, buf.data(), static_cast<int>(buf.size())))
return "(name unavailable)";
return std::string(buf.data(), strnlen(buf.data(), buf.size()));
}
} // namespace
std::vector<int> readParkFlagValues(MediaTrack* tr, const std::vector<TrackFlagOp>& parkFlags) {
std::vector<int> live;
live.reserve(parkFlags.size());
for (const TrackFlagOp& op : parkFlags)
live.push_back(static_cast<int>(GetMediaTrackInfo_Value(tr, trackFlagParm(op.flag))));
return live;
ParkAction decideParkForTrack(MediaTrack* tr, bool haveSnapshot,
const std::vector<TrackFlagOp>& parkFlags) {
if (haveSnapshot) return decidePark(true, false); // second term unread — skip the reads
const bool flagsRead = parkFlagsAlreadyApplied(parkFlags, [tr](Flag f) {
return static_cast<int>(GetMediaTrackInfo_Value(tr, trackFlagParm(f)));
});
return decidePark(false, chainReadsParked(flagsRead, anyFxOffline(tr)));
}
void reportRefusedParks(ReaProject* proj, const std::vector<MediaTrack*>& refused) {
ReaProject* const owner = proj ? proj : currentProject();
std::vector<std::string> names;
names.reserve(refused.size());
for (MediaTrack* tr : refused) names.push_back(trackDisplayName(tr));
if (owner == g_reportedOwner && names == g_reportedRefusals) return;
g_reportedOwner = owner;
g_reportedRefusals = names;
const std::string msg = describeRefusedParks(names);
// "!SHOW:" so an unattended project-load reapply cannot force the console open
// (reaper_plugin_functions.h:6536), same as the drain's drop report.
if (!msg.empty()) ShowConsoleMsg(("!SHOW:" + msg).c_str());
}
std::vector<std::string> liveFxGuids(MediaTrack* tr) {