Files
reasampler/src/shell/view/view_park.h
T

179 lines
9.8 KiB
C++

#pragma once
// Design View's park surface: one track's flag park or restore, plus the park
// record that makes the restore non-destructive. FLAG DOMAIN ONLY — a park record's
// FX half is captured here (a read) but never written here; the reclaimer owns
// every TrackFX_SetOffline, off the switch's synchronous path.
#include <set>
#include <string>
#include <vector>
#include "core/view/view_mode_model.h" // Flag / TrackFlagOp — what a park writes
// Forward-declared to keep this header SDK-free; the .cpp includes the real SDK header.
class MediaTrack;
class ReaProject;
namespace reasampler {
// The REAPER track parameter each driven flag addresses. One home for the mapping —
// the park's pre-park read, the restore's write and the reconcile's repair must
// never disagree about which parameter a Flag names.
inline const char* trackFlagParm(Flag f) {
switch (f) {
case Flag::ShowInTcp: return "B_SHOWINTCP";
case Flag::ShowInMixer: return "B_SHOWINMIXER";
case Flag::MainSend: return "B_MAINSEND";
case Flag::FxEnable: return "I_FXEN";
}
return "B_SHOWINTCP"; // unreachable; keeps the compiler quiet
}
// The FX's own durable identity, braced exactly like the track GUID keys, empty when
// REAPER reports none. The ONE such read: the park's snapshot and the reclaimer's
// resolve must address the same chain by the same names, or a restore lands one
// plugin's state on another. See its definition for the stability caveat.
std::string fxGuidString(MediaTrack* tr, int fx);
// A re-park's FX half, unioned rather than overwritten: keeps every FX identity
// `prior` already names (the user's real pre-park state, still offline while the
// reclaimer hasn't run) and appends whatever identity `live` sees that `prior`
// does not — a plugin added during the restored interval, which a verbatim keep
// of `prior` would otherwise never capture and leave stuck offline for good once
// the reclaimer lands. Only meaningful when `prior` is Identity-keyed; a
// Slot-keyed legacy record has no identities to union against, so parkTrack falls
// back to trusting it verbatim instead of calling this. An empty fxGuid (REAPER
// reported no identity) is never deduped against another empty one — it is
// unrestorable either way, so appending it costs nothing and dedup would be a
// coin flip.
inline std::vector<FxOfflineState> unionFxOfflineOnRepark(
const std::vector<FxOfflineState>& prior, const std::vector<FxOfflineState>& live) {
std::vector<FxOfflineState> merged = prior;
std::set<std::string> named;
for (const FxOfflineState& fx : merged)
if (!fx.fxGuid.empty()) named.insert(fx.fxGuid);
for (const FxOfflineState& fx : live)
if (fx.fxGuid.empty() || named.insert(fx.fxGuid).second) merged.push_back(fx);
return merged;
}
// Does the track already sit at every value `parkFlags` would write? Templated on
// the read so live code compares as it reads (one call per Flag, no live-value
// vector) while a test drives the same fold from a table. FX-offline is
// deliberately NOT folded in: Wave 3 moved every per-FX write off this path onto
// the reclaimer, so a chain with these four flags fine but an FX hand-offlined is
// not evidence THIS path ever touched the track — only the flags it would itself
// have driven are. An empty plan is evidence of nothing and answers false.
template <class ReadFlag>
bool chainReadsParked(const std::vector<TrackFlagOp>& parkFlags, ReadFlag readFlag) {
if (parkFlags.empty()) return false;
for (const TrackFlagOp& op : parkFlags)
if (readFlag(op.flag) != op.value) return false;
return true;
}
// The undo-state domains both the mode-switch apply (view.cpp) and lane minting
// (view_lanes.cpp) write: track config/routing, item lane assignment, and the
// ext-state block (UNDO_STATE_MISCCFG covers extension state). NOT UNDO_STATE_ALL,
// and no UNDO_STATE_FX at all — neither writer moves FX state, so carrying the
// project's whole FX chunk on every point would cost with the plugin count for
// nothing. One symbol, not two identically-valued constants free to diverge.
// [verify — DAW] INFERRED, not documented: UNDO_STATE_TRACKCFG reads "track/master
// vol/pan/routing" (reaper_plugin.h:1541) and names neither the fixed-lane
// properties (assumed to ride UNDO_STATE_ITEMS, :1543) nor
// B_SHOWINTCP/B_SHOWINMIXER — and for the visibility pair that inference is now
// DISPROVEN in the DAW: REAPER's undo does not restore them, which is what
// view_reconcile exists to repair.
extern const int kViewUndoMask;
// A track parameter written only when it is not already there. A reapply re-plans
// every flag it already applied, so most of what it writes is a value the track
// already holds. The read is ASSUMED cheaper than the write it elides (unmeasured);
// what it certainly does is keep those no-op writes out of the undo record's mask.
// Returns whether it wrote.
bool writeIfChanged(MediaTrack* tr, const char* parm, double value);
// What one parkTrack call did. `wrote` is any project write at all — a flag write
// or a record write — and feeds the apply's undo-point fold. `exposed` is true iff
// this call handed the track back visible instead of parking or adopting it; the
// caller (view.cpp) collects exposed tracks across the whole apply for the one
// batched console report.
struct TrackParkResult {
bool wrote = false;
bool exposed = false;
};
// Parks one inactive leaf: capture the pre-park truth when the model does not
// already hold ANY record for it (holdsSnapshotFlags false AND model.snapshot ==
// nullptr), then drive the flags.
//
// A record with a held FX half but no held flag half (a re-park landing right
// after a restore, before the reclaimer catches up) is NOT the no-record case —
// model.snapshot(guid) is non-null there, so it takes the existing union path
// below, never the branch this comment is about. That is how EXPOSE is known not
// to fire on a track the tool legitimately still holds a record for: EXPOSE is
// gated on snapshot(guid) == nullptr, and a legitimately-held record is by
// definition non-null (eraseIfSpent only drops a record once BOTH halves are
// spent).
//
// The true no-record case splits on whether the live chain already reads
// park-shaped (chainReadsParked, flags only — see its own comment for why FX is
// excluded). Not park-shaped: the chain is the track's own honest state, captured
// and parked exactly as ever. Park-shaped: parking now would adopt a park with no
// account behind it — an undo/redo or reload that dropped the snapshot, a hand or
// script edit, or simply a track the user keeps hidden — as the user's intent,
// which either wipes a hand repair on the very next restore or hides an untouched
// track for good. EXPOSE hands it back instead: no snapshot stored, the four
// flags driven to visible/active, nothing reported here (the caller batches it).
// The next apply sees an honest chain again and captures/parks it normally —
// self-healing, no user action required.
TrackParkResult parkTrack(ViewModeModel& model, const std::string& guid, MediaTrack* tr,
const std::vector<TrackFlagOp>& flags);
// GetTrackName, matching capture::trackName's reason for the same choice: "Track
// N" for an unnamed track, matching REAPER's own convention. Exported so a caller
// holding the handle only briefly (view.cpp's park loop, before
// Undo_EndBlock2/TrackList_AdjustWindows can invalidate it) captures the name at
// resolve time rather than the handle.
std::string trackDisplayName(MediaTrack* tr);
// One console message for the tracks EXPOSEd this apply, empty when none. NAMES
// them: an exposed track was hidden from both the TCP and the mixer moments ago,
// so a bare count points at nothing the user can go and find. Says the tool lost
// its own record and handed the track back — never "refused".
inline std::string describeExposedTracks(const std::vector<std::string>& trackNames) {
if (trackNames.empty()) return {};
const bool one = trackNames.size() == 1;
std::string msg = "ReaSampler: " + std::to_string(trackNames.size()) + " track" +
(one ? "" : "s") + " handed back visible -- the tool's record of " +
(one ? "its" : "their") +
" pre-park state is gone (an undo/redo or a project reload dropped "
"it), and parking now would have recorded the parked state as "
"yours instead.\n";
for (const std::string& name : trackNames) msg += " " + name + "\n";
msg += "The next mode switch will park it correctly from what you see now.\n";
return msg;
}
// Pure gate behind reportExposedTracks' one-per-changed-set memo: true on any
// change of owner or of the named set, false on an exact repeat.
inline bool shouldReport(bool sameOwnerAsLast, const std::vector<std::string>& last,
const std::vector<std::string>& now) {
return !(sameOwnerAsLast && last == now);
}
// Prints describeExposedTracks for `trackNames` (captured by the caller while its
// track handles are still fresh — see view.cpp's park loop), ONCE per changed set
// per project: the same exposure recurs on every reapply until the track
// self-heals, and repeating it verbatim is noise the user cannot act on any
// better the second time. `proj` is used only as a memo key, never dereferenced.
void reportExposedTracks(ReaProject* proj, const std::vector<std::string>& trackNames);
// Restores one returning leaf from its record and spends the record's flag half.
// Always true: the record is consumed here, so this apply changed state and is not
// repeatable, which is exactly what its one reader (the undo-point decision) asks.
bool restoreTrack(ViewModeModel& model, const std::string& guid, MediaTrack* tr,
const std::vector<TrackFlagOp>& flags);
} // namespace reasampler