b2f6b3f1d0
Every opener ends the span first, so a tag fired mid-drain no longer hijacks the switch's point. One stated criterion governs both lane sites; no-op actions discard.
179 lines
7.6 KiB
C++
179 lines
7.6 KiB
C++
// See view_park.h. Compiled into the reaper_reasampler module; includes
|
|
// reaper_plugin_functions.h without REAPERAPI_IMPLEMENT (main.cpp owns that).
|
|
|
|
#include "shell/view/view_park.h"
|
|
|
|
#include <cstring> // strnlen — bounded read of GetTrackName's buffer
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#define REAPERAPI_MINIMAL
|
|
#define REAPERAPI_WANT_GetMediaTrackInfo_Value
|
|
#define REAPERAPI_WANT_GetTrackName
|
|
#define REAPERAPI_WANT_SetMediaTrackInfo_Value
|
|
#define REAPERAPI_WANT_ShowConsoleMsg
|
|
#define REAPERAPI_WANT_TrackFX_GetCount
|
|
#define REAPERAPI_WANT_TrackFX_GetFXGUID
|
|
#define REAPERAPI_WANT_TrackFX_GetOffline
|
|
#define REAPERAPI_WANT_guidToString
|
|
#include "reaper_plugin_functions.h"
|
|
|
|
namespace reasampler {
|
|
|
|
namespace {
|
|
|
|
// 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
|
|
// — never the 0x1000000/0x2000000 input-FX or container forms — is the same one the
|
|
// reclaimer resolves against. `fxKeying` stays Identity: a live read always knows
|
|
// the chain.
|
|
TrackSnapshot snapshotTrack(MediaTrack* tr) {
|
|
TrackSnapshot snap;
|
|
auto read = [tr](Flag f) {
|
|
return static_cast<int>(GetMediaTrackInfo_Value(tr, trackFlagParm(f)));
|
|
};
|
|
snap.showInTcp = read(Flag::ShowInTcp);
|
|
snap.showInMixer = read(Flag::ShowInMixer);
|
|
snap.mainSend = read(Flag::MainSend);
|
|
snap.fxEnable = read(Flag::FxEnable);
|
|
|
|
const 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(
|
|
FxOfflineState{fxGuidString(tr, fx), TrackFX_GetOffline(tr, fx) ? 1 : 0});
|
|
return snap;
|
|
}
|
|
|
|
bool applyFlags(MediaTrack* tr, const std::vector<TrackFlagOp>& flags) {
|
|
bool wrote = false;
|
|
for (const TrackFlagOp& op : flags)
|
|
if (writeIfChanged(tr, trackFlagParm(op.flag), static_cast<double>(op.value)))
|
|
wrote = true;
|
|
return wrote;
|
|
}
|
|
|
|
// The visible/active mirror of a park write: same four ops, value 1 instead of
|
|
// whatever the plan says (always 0 for a park plan — see makeParkPlan). Reusing
|
|
// the incoming park ops rather than minting a separate "expose plan" keeps the
|
|
// two writes provably addressing the same four parameters.
|
|
bool exposeFlags(MediaTrack* tr, const std::vector<TrackFlagOp>& parkFlags) {
|
|
bool wrote = false;
|
|
for (const TrackFlagOp& op : parkFlags)
|
|
if (writeIfChanged(tr, trackFlagParm(op.flag), 1.0)) wrote = true;
|
|
return wrote;
|
|
}
|
|
|
|
// The last EXPOSE report, so an unchanged set is not reprinted on every reapply
|
|
// the track hasn't yet self-healed from.
|
|
ReaProject* g_reportedOwner = nullptr;
|
|
std::vector<std::string> g_reportedExposed;
|
|
|
|
} // namespace
|
|
|
|
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);
|
|
return true;
|
|
}
|
|
|
|
TrackParkResult parkTrack(ViewModeModel& model, const std::string& guid, MediaTrack* tr,
|
|
const std::vector<TrackFlagOp>& flags) {
|
|
TrackParkResult result;
|
|
|
|
if (!model.holdsSnapshotFlags(guid)) {
|
|
const TrackSnapshot* prior = model.snapshot(guid);
|
|
|
|
// No record of ANY kind (see parkTrack's header doc for why prior == nullptr
|
|
// is the right gate): the ambiguous case. A live chain already reading
|
|
// park-shaped is EXPOSEd — handed back visible, no snapshot stored — rather
|
|
// than adopted as the user's intent.
|
|
if (!prior) {
|
|
auto read = [tr](Flag f) {
|
|
return static_cast<int>(GetMediaTrackInfo_Value(tr, trackFlagParm(f)));
|
|
};
|
|
if (chainReadsParked(flags, read)) {
|
|
if (exposeFlags(tr, flags)) result.wrote = true;
|
|
result.exposed = true;
|
|
return result; // this cycle hands the track back; it does not park it
|
|
}
|
|
}
|
|
|
|
TrackSnapshot snap = snapshotTrack(tr);
|
|
|
|
// A restore hands the flags back immediately but leaves the chain offline
|
|
// until the reclaimer gets to it, so a re-park lands here with the FX half
|
|
// still held over a still-parked chain. Union the live read against the
|
|
// record rather than overwriting either way — see unionFxOfflineOnRepark.
|
|
//
|
|
// `prior->fxHeld` is provably redundant here today — eraseIfSpent
|
|
// (view_mode_model.cpp) guarantees a surviving record has fxHeld == true
|
|
// — so the false arm is unreached. Kept anyway: it is the direct
|
|
// statement of what this branch actually needs, and cheaper to keep true
|
|
// than to re-derive if that guarantee ever loosens.
|
|
if (prior && prior->fxHeld && prior->fxKeying == FxKeying::Identity) {
|
|
snap.fxOffline = unionFxOfflineOnRepark(prior->fxOffline, snap.fxOffline);
|
|
snap.fxKeying = FxKeying::Identity;
|
|
} else if (prior && prior->fxHeld) {
|
|
// Slot-keyed legacy record (lifted from a pre-identity view_state): no
|
|
// identities to union against, so fall back to the old behavior and
|
|
// trust the record verbatim over live slots that may have moved since.
|
|
snap.fxOffline = prior->fxOffline;
|
|
snap.fxKeying = prior->fxKeying;
|
|
}
|
|
model.storeSnapshot(guid, snap);
|
|
}
|
|
|
|
if (applyFlags(tr, flags)) result.wrote = true;
|
|
return result;
|
|
}
|
|
|
|
bool restoreTrack(ViewModeModel& model, const std::string& guid, MediaTrack* tr,
|
|
const std::vector<TrackFlagOp>& flags) {
|
|
const bool wrote = applyFlags(tr, flags);
|
|
model.releaseSnapshotFlags(guid);
|
|
return wrote;
|
|
}
|
|
|
|
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()));
|
|
}
|
|
|
|
void reportExposedTracks(ReaProject* proj, const std::vector<std::string>& trackNames) {
|
|
if (!shouldReport(proj == g_reportedOwner, g_reportedExposed, trackNames)) return;
|
|
g_reportedOwner = proj;
|
|
g_reportedExposed = trackNames;
|
|
|
|
const std::string msg = describeExposedTracks(trackNames);
|
|
// "!SHOW:" so an unattended project-load reapply cannot force the console open
|
|
// (reaper_plugin_functions.h:6536).
|
|
if (!msg.empty()) ShowConsoleMsg(("!SHOW:" + msg).c_str());
|
|
}
|
|
|
|
} // namespace reasampler
|