Merge Design View FX-GUID keying: parked FX-offline state follows the plugin, not the slot
This commit is contained in:
@@ -42,7 +42,9 @@ settled 2026-07-23):
|
||||
reapply touches solo not at all.
|
||||
- **GUID-keyed, reorder-safe.** Membership keys on track GUID (`GetTrackGUID`),
|
||||
never track index; tolerates unknown/stale GUIDs (pruned on reconcile via
|
||||
`ViewModeModel::reconcile(liveGuids)`).
|
||||
`ViewModeModel::reconcile(liveGuids)`). The same rule binds one level down: a
|
||||
parked track's per-FX offline state keys on the FX's own identity
|
||||
(`TrackFX_GetFXGUID`), never its slot — see `fx_offline`.
|
||||
- **Relative/portable state only** in the persisted view section (GUID strings,
|
||||
mode ids — no absolute paths, no index positions).
|
||||
- **Show-both semantics.** A per-track "pin visible across modes" flag that
|
||||
@@ -95,6 +97,7 @@ settled 2026-07-23):
|
||||
## Modules
|
||||
|
||||
- `view_mode_model` — Design View mode system: mode registry, GUID-keyed membership, folder-tree-aware visibility derivation, snapshot-based park/restore planner, the per-mode `SoloCache` it owns, JSON round-trip.
|
||||
- `fx_offline` — the per-FX offline snapshot's KEY and its restore resolution: `FxKeying` (Identity / Slot), `FxOfflineState`, the planned `FxOfflineOp`, and `resolveFxRestore`, which matches each captured state to the FX it came from against the chain as it stands at restore time. An identity that is no longer live is DROPPED and counted (`FxRestoreDrops`, reported through `describeFxRestoreDrops`), never re-pointed at a slot — see the `FxKeying` and `resolveFxRestore` comments in `fx_offline.h` for why. Slot keying survives only for snapshots lifted from a pre-identity `view_state` and for park plans, where every live slot is the target by construction.
|
||||
- `solo_cache` — the per-mode solo surface: `SoloCache` (mode id → GUID → raw `I_SOLO`), the soloed-subset filter, and `planSoloRestore`, whose two drop rules (dead GUID, not visible in the incoming mode) and their reasoning live in its header.
|
||||
- `view_tree` — pure `I_FOLDERDEPTH`→FolderTree helper for the Design View shell.
|
||||
- `mode_switch` — REAPER-free segment layout + hit-test for the bank_panel's Design View mode switch.
|
||||
@@ -110,6 +113,11 @@ settled 2026-07-23):
|
||||
- `kManagedLanePrefix` ("reasampler:") is stable-forever like an action-id
|
||||
string — changing it strands the ownership of every already-minted lane in
|
||||
every already-saved project.
|
||||
- The `view_state` blob's version ladder lives beside `kViewStateVersion` in
|
||||
`view_mode_model.cpp`. v2 writes the v1 slot array BESIDE the identity array so
|
||||
a downgrade keeps the behavior it had; the version field is written but
|
||||
deliberately not validated on read, because an unreadable `view_state` falls
|
||||
back to a default model and loses every membership tag.
|
||||
- `guid_diff::GuidBaseline` must have `reset()` called on every detected
|
||||
project switch, or the next `observe()` will diff across two unrelated
|
||||
projects and mass-tag (or miss) content.
|
||||
|
||||
@@ -4,12 +4,16 @@ reasampler_test(lane_keys LINK lane_keys)
|
||||
reasampler_pure_library(solo_cache SOURCES solo_cache.cpp)
|
||||
reasampler_test(solo_cache LINK solo_cache)
|
||||
|
||||
# lane_keys and solo_cache are PUBLIC: the lane-minting plan names managed lanes through the
|
||||
# one durable-key convention, and ViewModeModel exposes the SoloCache by reference, so every
|
||||
# consumer has to resolve those symbols too.
|
||||
reasampler_pure_library(fx_offline SOURCES fx_offline.cpp)
|
||||
reasampler_test(fx_offline LINK fx_offline)
|
||||
|
||||
# lane_keys, solo_cache and fx_offline are PUBLIC: the lane-minting plan names managed lanes
|
||||
# through the one durable-key convention, ViewModeModel exposes the SoloCache by reference, and
|
||||
# TrackSnapshot/TrackPlan carry the FX-offline types by value — so every consumer has to
|
||||
# resolve those symbols too.
|
||||
reasampler_pure_library(view_mode_model
|
||||
SOURCES view_mode_model.cpp
|
||||
LINK PRIVATE json PUBLIC lane_keys solo_cache)
|
||||
LINK PRIVATE json PUBLIC lane_keys solo_cache fx_offline)
|
||||
reasampler_test(view_mode_model LINK view_mode_model)
|
||||
|
||||
reasampler_pure_library(view_tree SOURCES view_tree.cpp LINK PUBLIC view_mode_model)
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
#include "core/view/fx_offline.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <map>
|
||||
|
||||
namespace reasampler {
|
||||
|
||||
namespace {
|
||||
|
||||
// Identity -> current slot. First occurrence wins: REAPER mints one GUID per FX
|
||||
// instance, so a repeat can only come from a corrupt/hand-edited chain, and
|
||||
// picking one deterministically beats writing twice.
|
||||
std::map<std::string, int> slotByIdentity(const std::vector<std::string>& liveFxGuids) {
|
||||
std::map<std::string, int> byGuid;
|
||||
for (std::size_t i = 0; i < liveFxGuids.size(); ++i) {
|
||||
if (liveFxGuids[i].empty()) continue; // unidentifiable FX is not a restore target
|
||||
byGuid.emplace(liveFxGuids[i], static_cast<int>(i));
|
||||
}
|
||||
return byGuid;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
FxRestoreResolution resolveFxRestore(const std::vector<FxOfflineOp>& planned,
|
||||
const std::vector<std::string>& liveFxGuids) {
|
||||
FxRestoreResolution res;
|
||||
const std::map<std::string, int> byGuid = slotByIdentity(liveFxGuids);
|
||||
const int liveCount = static_cast<int>(liveFxGuids.size());
|
||||
|
||||
for (const FxOfflineOp& op : planned) {
|
||||
if (op.keying == FxKeying::Identity) {
|
||||
if (op.fxGuid.empty()) {
|
||||
// REAPER reported no GUID at capture time — distinct from a real
|
||||
// captured identity going missing: the FX may still be live, we
|
||||
// just never had a name for it. Counted separately so the report
|
||||
// never claims it was deleted (see describeFxRestoreDrops).
|
||||
++res.drops.unidentified;
|
||||
continue;
|
||||
}
|
||||
auto it = byGuid.find(op.fxGuid);
|
||||
if (it == byGuid.end()) {
|
||||
++res.drops.missingIdentity;
|
||||
continue;
|
||||
}
|
||||
res.writes.push_back(FxOfflineWrite{it->second, op.offline});
|
||||
} else {
|
||||
if (op.slot < 0 || op.slot >= liveCount) {
|
||||
++res.drops.slotOutOfRange;
|
||||
continue;
|
||||
}
|
||||
res.writes.push_back(FxOfflineWrite{op.slot, op.offline});
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
std::string describeFxRestoreDrops(const FxRestoreDrops& drops, int trackCount) {
|
||||
if (drops.total() <= 0) return {};
|
||||
|
||||
std::string msg = "ReaSampler: Design View restore dropped " +
|
||||
std::to_string(drops.total()) + " captured FX offline state(s) on " +
|
||||
std::to_string(trackCount) + " track(s) -- ";
|
||||
|
||||
std::vector<std::string> clauses;
|
||||
if (drops.missingIdentity > 0) {
|
||||
clauses.push_back(std::to_string(drops.missingIdentity) +
|
||||
" FX no longer in the chain (deleted or replaced while parked)");
|
||||
}
|
||||
if (drops.unidentified > 0) {
|
||||
clauses.push_back(std::to_string(drops.unidentified) +
|
||||
" FX REAPER could not identify at capture time (no GUID reported), "
|
||||
"so it could not be matched now");
|
||||
}
|
||||
if (drops.slotOutOfRange > 0) {
|
||||
clauses.push_back(std::to_string(drops.slotOutOfRange) +
|
||||
" from a project saved before FX identity was recorded, whose slot no longer exists");
|
||||
}
|
||||
for (std::size_t i = 0; i < clauses.size(); ++i) {
|
||||
if (i) msg += ", ";
|
||||
msg += clauses[i];
|
||||
}
|
||||
|
||||
// Every dropped entry is still sitting exactly where park left it — offline
|
||||
// — because the restore that would have flipped it back never ran. Say
|
||||
// that, not the reassuring-but-wrong "left as it was" (park itself was the
|
||||
// change; restore is what didn't happen for these).
|
||||
msg += ". Each was left offline, as park left it, with no snapshot left to "
|
||||
"restore it -- switch it back on by hand.\n";
|
||||
return msg;
|
||||
}
|
||||
|
||||
} // namespace reasampler
|
||||
@@ -0,0 +1,93 @@
|
||||
#pragma once
|
||||
// The per-FX offline snapshot's key and its restore resolution: what a captured
|
||||
// FX state is keyed BY, and how that key resolves against the chain as it stands
|
||||
// at restore time. Pure — the FX identity is an opaque string the shell reads
|
||||
// from REAPER (TrackFX_GetFXGUID) and hands in.
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace reasampler {
|
||||
|
||||
// How a set of per-FX entries is keyed. `Slot` is the position-addressed shape:
|
||||
// a park plan (every live slot, by construction) or a snapshot lifted from a
|
||||
// project saved before identity was recorded. A live snapshot is always
|
||||
// `Identity` — a chain reordered while the track is parked makes a slot a lie.
|
||||
enum class FxKeying { Identity, Slot };
|
||||
|
||||
// One FX's captured offline state. Under Identity keying `fxGuid` is that FX's
|
||||
// own durable identity; under Slot keying it is empty and the entry's POSITION
|
||||
// in the snapshot is the slot it was captured from.
|
||||
struct FxOfflineState {
|
||||
std::string fxGuid;
|
||||
int offline = 0; // int, not bool — mirrors TrackSnapshot's defensive contract
|
||||
|
||||
bool operator==(const FxOfflineState& o) const {
|
||||
return fxGuid == o.fxGuid && offline == o.offline;
|
||||
}
|
||||
};
|
||||
|
||||
// One per-FX offline write as PLANNED. The keying travels with the op; see
|
||||
// FxKeying above and resolveFxRestore below for why a missing identity is
|
||||
// never re-pointed at a slot.
|
||||
struct FxOfflineOp {
|
||||
std::string guid; // track GUID
|
||||
FxKeying keying = FxKeying::Identity;
|
||||
std::string fxGuid; // FX identity, under Identity keying
|
||||
int slot = 0; // write target under Slot keying only
|
||||
bool offline = false;
|
||||
|
||||
bool operator==(const FxOfflineOp& o) const {
|
||||
return guid == o.guid && keying == o.keying && fxGuid == o.fxGuid &&
|
||||
slot == o.slot && offline == o.offline;
|
||||
}
|
||||
};
|
||||
|
||||
// One resolved write: TrackFX_SetOffline(track, fxIndex, offline).
|
||||
struct FxOfflineWrite {
|
||||
int fxIndex = 0;
|
||||
bool offline = false;
|
||||
|
||||
bool operator==(const FxOfflineWrite& o) const {
|
||||
return fxIndex == o.fxIndex && offline == o.offline;
|
||||
}
|
||||
};
|
||||
|
||||
// Captured state a restore could not apply. All three counts mean the same
|
||||
// act: the entry was dropped and no FX was written in its place.
|
||||
struct FxRestoreDrops {
|
||||
int missingIdentity = 0; // identity-keyed entry with no live FX carrying that GUID
|
||||
int unidentified = 0; // identity-keyed entry whose captured fxGuid was itself empty
|
||||
int slotOutOfRange = 0; // slot-keyed entry whose capture-time slot no longer exists
|
||||
|
||||
int total() const { return missingIdentity + unidentified + slotOutOfRange; }
|
||||
|
||||
void add(const FxRestoreDrops& o) {
|
||||
missingIdentity += o.missingIdentity;
|
||||
unidentified += o.unidentified;
|
||||
slotOutOfRange += o.slotOutOfRange;
|
||||
}
|
||||
|
||||
bool operator==(const FxRestoreDrops& o) const {
|
||||
return missingIdentity == o.missingIdentity && unidentified == o.unidentified &&
|
||||
slotOutOfRange == o.slotOutOfRange;
|
||||
}
|
||||
};
|
||||
|
||||
struct FxRestoreResolution {
|
||||
std::vector<FxOfflineWrite> writes; // in planned order
|
||||
FxRestoreDrops drops;
|
||||
};
|
||||
|
||||
// Resolves each planned op against the live chain, where `liveFxGuids[i]` is the
|
||||
// identity of the FX at slot `i` right now (empty when REAPER reported none).
|
||||
// An identity that is not live is DROPPED, never re-pointed at a slot; a live FX
|
||||
// no op names is left entirely alone.
|
||||
FxRestoreResolution resolveFxRestore(const std::vector<FxOfflineOp>& planned,
|
||||
const std::vector<std::string>& liveFxGuids);
|
||||
|
||||
// One console line describing a whole apply's drops, or "" when nothing was
|
||||
// dropped — the degrade is reported rather than swallowed.
|
||||
std::string describeFxRestoreDrops(const FxRestoreDrops& drops, int trackCount);
|
||||
|
||||
} // namespace reasampler
|
||||
@@ -196,8 +196,13 @@ TrackPlan makeParkPlan(const std::string& guid, int fxCount) {
|
||||
{guid, Flag::MainSend, 0},
|
||||
{guid, Flag::FxEnable, 0},
|
||||
};
|
||||
// Park has no identity question to answer: it offlines every slot that is
|
||||
// live right now, so slot keying IS the addressing. In production fxCount
|
||||
// is always 0 here — planToggle calls this with fxCount=0 and the D2 shell
|
||||
// expands the real writes itself via TrackFX_GetCount (parkFxOffline in
|
||||
// shell/view/view.cpp); a nonzero fxCount only exercises this loop in tests.
|
||||
for (int i = 0; i < fxCount; ++i)
|
||||
p.fxOffline.push_back({guid, i, true});
|
||||
p.fxOffline.push_back(FxOfflineOp{guid, FxKeying::Slot, {}, i, true});
|
||||
return p;
|
||||
}
|
||||
|
||||
@@ -209,8 +214,11 @@ TrackPlan makeRestorePlan(const std::string& guid, const TrackSnapshot& snap) {
|
||||
{guid, Flag::MainSend, snap.mainSend},
|
||||
{guid, Flag::FxEnable, snap.fxEnable},
|
||||
};
|
||||
for (std::size_t i = 0; i < snap.fxOffline.size(); ++i)
|
||||
p.fxOffline.push_back({guid, static_cast<int>(i), snap.fxOffline[i] != 0});
|
||||
for (std::size_t i = 0; i < snap.fxOffline.size(); ++i) {
|
||||
const FxOfflineState& fx = snap.fxOffline[i];
|
||||
p.fxOffline.push_back(FxOfflineOp{guid, snap.fxKeying, fx.fxGuid,
|
||||
static_cast<int>(i), fx.offline != 0});
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
@@ -361,13 +369,33 @@ using json::writeIntArray;
|
||||
std::string intToStr(int v) { return json::numToStr(v); }
|
||||
using ObjWriter = json::Writer;
|
||||
|
||||
// Version ladder for the stored blob, under the FOREVER-STABLE "view_state" key.
|
||||
// Only the per-FX snapshot shape has ever moved:
|
||||
//
|
||||
// v1 "snapshots":[{...,"fxOffline":[0,1,0]}] — offline state by SLOT
|
||||
// v2 "snapshots":[{...,"fxOffline":[0,1,0], — the v1 array, still written
|
||||
// "fx":[{"guid":"{..}","offline":0},...]}] — by FX IDENTITY
|
||||
//
|
||||
// v2 writes BOTH: "fx" is what this build reads, and the v1 array is what a build
|
||||
// that predates identity keying reads — a downgrade keeps exactly the behavior it
|
||||
// had rather than losing every captured FX state to an unknown key. Reading, "fx"
|
||||
// wins outright; a blob carrying only "fxOffline" lifts to a Slot-keyed snapshot
|
||||
// and restores by slot ONCE, which is the only thing its bytes can support (the
|
||||
// restore then clears it, so the next park captures identities).
|
||||
//
|
||||
// "version" is WRITTEN but deliberately not validated on read: an unreadable
|
||||
// view_state falls back to a default model, which loses every membership tag, so
|
||||
// leniency is the safe direction here — the opposite call from origin_ledger,
|
||||
// where a misread blob would put prune's deletion authority on bad data.
|
||||
constexpr int kViewStateVersion = 2;
|
||||
|
||||
} // namespace
|
||||
|
||||
std::string ViewModeModel::serialize() const {
|
||||
std::string out;
|
||||
{
|
||||
ObjWriter root(out);
|
||||
root.keyRaw("version", intToStr(1));
|
||||
root.keyRaw("version", intToStr(kViewStateVersion));
|
||||
root.keyStr("activeMode", activeModeId_);
|
||||
|
||||
root.keyBegin("modes");
|
||||
@@ -410,7 +438,8 @@ std::string ViewModeModel::serialize() const {
|
||||
}
|
||||
out += ']';
|
||||
|
||||
// snapshots: array of { guid, showInTcp, showInMixer, mainSend, fxEnable, fxOffline[] }
|
||||
// snapshots: array of { guid, showInTcp, showInMixer, mainSend, fxEnable,
|
||||
// fxOffline[], fx[] } — see the version ladder above.
|
||||
root.keyBegin("snapshots");
|
||||
out += '[';
|
||||
{
|
||||
@@ -424,8 +453,28 @@ std::string ViewModeModel::serialize() const {
|
||||
e.keyRaw("showInMixer", intToStr(snap.showInMixer));
|
||||
e.keyRaw("mainSend", intToStr(snap.mainSend));
|
||||
e.keyRaw("fxEnable", intToStr(snap.fxEnable));
|
||||
|
||||
std::vector<int> bySlot;
|
||||
bySlot.reserve(snap.fxOffline.size());
|
||||
for (const FxOfflineState& fx : snap.fxOffline) bySlot.push_back(fx.offline);
|
||||
e.keyBegin("fxOffline");
|
||||
writeIntArray(out, snap.fxOffline);
|
||||
writeIntArray(out, bySlot);
|
||||
|
||||
// A Slot-keyed snapshot has no identities to write — emitting an
|
||||
// "fx" array for it would invent the very keys it lacks.
|
||||
if (snap.fxKeying == FxKeying::Identity) {
|
||||
e.keyBegin("fx");
|
||||
out += '[';
|
||||
bool firstFx = true;
|
||||
for (const FxOfflineState& fx : snap.fxOffline) {
|
||||
if (!firstFx) out += ',';
|
||||
firstFx = false;
|
||||
ObjWriter f(out);
|
||||
f.keyStr("guid", fx.fxGuid);
|
||||
f.keyRaw("offline", intToStr(fx.offline));
|
||||
}
|
||||
out += ']';
|
||||
}
|
||||
}
|
||||
}
|
||||
out += ']';
|
||||
@@ -537,6 +586,32 @@ bool parseMembership(json::Reader& r, MembershipIndex& idx) {
|
||||
return r.consume(']');
|
||||
}
|
||||
|
||||
// The identity-keyed "fx" array. Both keys are mandatory (strict like parseLanes);
|
||||
// an EMPTY guid is accepted, because a live capture records one when REAPER
|
||||
// reported no identity for that FX — the entry is honest about being unresolvable
|
||||
// rather than being silently dropped at write time.
|
||||
bool parseFxStates(json::Reader& r, std::vector<FxOfflineState>& out) {
|
||||
if (!r.consume('[')) return false;
|
||||
r.skipWs();
|
||||
if (r.consume(']')) return true;
|
||||
do {
|
||||
if (!r.consume('{')) return false;
|
||||
FxOfflineState fx;
|
||||
bool haveGuid = false, haveOffline = false;
|
||||
do {
|
||||
std::string k;
|
||||
if (!r.parseKey(k)) return false;
|
||||
if (k == "guid") { if (!r.parseString(fx.fxGuid)) return false; haveGuid = true; }
|
||||
else if (k == "offline") { if (!r.parseInt(fx.offline)) return false; haveOffline = true; }
|
||||
else if (!r.skipValue()) return false;
|
||||
} while (r.consume(','));
|
||||
if (!r.consume('}')) return false;
|
||||
if (!haveGuid || !haveOffline) return false;
|
||||
out.push_back(fx);
|
||||
} while (r.consume(','));
|
||||
return r.consume(']');
|
||||
}
|
||||
|
||||
bool parseSnapshots(json::Reader& r, std::map<std::string, TrackSnapshot>& snaps) {
|
||||
if (!r.consume('[')) return false;
|
||||
r.skipWs();
|
||||
@@ -546,6 +621,10 @@ bool parseSnapshots(json::Reader& r, std::map<std::string, TrackSnapshot>& snaps
|
||||
std::string guid;
|
||||
TrackSnapshot snap;
|
||||
bool haveGuid = false;
|
||||
std::vector<int> bySlot;
|
||||
bool haveSlot = false;
|
||||
std::vector<FxOfflineState> byIdentity;
|
||||
bool haveIdentity = false;
|
||||
do {
|
||||
std::string k;
|
||||
if (!r.parseKey(k)) return false;
|
||||
@@ -554,12 +633,33 @@ bool parseSnapshots(json::Reader& r, std::map<std::string, TrackSnapshot>& snaps
|
||||
else if (k == "showInMixer") { if (!r.parseInt(snap.showInMixer)) return false; }
|
||||
else if (k == "mainSend") { if (!r.parseInt(snap.mainSend)) return false; }
|
||||
else if (k == "fxEnable") { if (!r.parseInt(snap.fxEnable)) return false; }
|
||||
else if (k == "fxOffline") { if (!r.parseIntArray(snap.fxOffline)) return false; }
|
||||
else if (k == "fxOffline") { if (!r.parseIntArray(bySlot)) return false; haveSlot = true; }
|
||||
else if (k == "fx") {
|
||||
if (!parseFxStates(r, byIdentity)) return false;
|
||||
haveIdentity = true;
|
||||
}
|
||||
else if (!r.skipValue()) return false;
|
||||
} while (r.consume(','));
|
||||
if (!r.consume('}')) return false;
|
||||
if (!haveGuid || guid.empty()) return false;
|
||||
snaps[guid] = snap;
|
||||
|
||||
// A blob carrying both arrays at different lengths is not something this
|
||||
// writer (or any prior version) produces — reject rather than silently
|
||||
// trusting "fx" over a slot array that disagrees with it; an unreadable
|
||||
// view_state falls back to a default model per the version-ladder note
|
||||
// above, which is the same leniency-direction call already made there.
|
||||
if (haveIdentity && haveSlot && byIdentity.size() != bySlot.size()) return false;
|
||||
|
||||
// "fx" wins outright — v2 writes the slot array beside it purely so an
|
||||
// older build can still read something (see the version ladder above).
|
||||
if (haveIdentity) {
|
||||
snap.fxOffline = std::move(byIdentity);
|
||||
snap.fxKeying = FxKeying::Identity;
|
||||
} else {
|
||||
for (int offline : bySlot) snap.fxOffline.push_back(FxOfflineState{{}, offline});
|
||||
snap.fxKeying = FxKeying::Slot;
|
||||
}
|
||||
snaps[guid] = std::move(snap);
|
||||
} while (r.consume(','));
|
||||
return r.consume(']');
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "core/view/fx_offline.h"
|
||||
#include "core/view/solo_cache.h"
|
||||
|
||||
namespace reasampler {
|
||||
@@ -201,13 +202,16 @@ struct TrackSnapshot {
|
||||
int mainSend = 0; // B_MAINSEND prior value
|
||||
int fxEnable = 0; // I_FXEN prior value
|
||||
|
||||
// Prior per-FX offline state, index = fx slot.
|
||||
std::vector<int> fxOffline;
|
||||
// Prior per-FX offline state in capture-time slot order, keyed per fxKeying:
|
||||
// by the FX's own identity (live capture), or by position (a snapshot lifted
|
||||
// from a project saved before identity was recorded).
|
||||
std::vector<FxOfflineState> fxOffline;
|
||||
FxKeying fxKeying = FxKeying::Identity;
|
||||
|
||||
bool operator==(const TrackSnapshot& o) const {
|
||||
return showInTcp == o.showInTcp && showInMixer == o.showInMixer &&
|
||||
mainSend == o.mainSend && fxEnable == o.fxEnable &&
|
||||
fxOffline == o.fxOffline;
|
||||
fxOffline == o.fxOffline && fxKeying == o.fxKeying;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -231,17 +235,6 @@ struct TrackFlagOp {
|
||||
}
|
||||
};
|
||||
|
||||
// One per-FX offline write: TrackFX_SetOffline(guid, fxIndex, offline).
|
||||
struct FxOfflineOp {
|
||||
std::string guid;
|
||||
int fxIndex = 0;
|
||||
bool offline = false;
|
||||
|
||||
bool operator==(const FxOfflineOp& o) const {
|
||||
return guid == o.guid && fxIndex == o.fxIndex && offline == o.offline;
|
||||
}
|
||||
};
|
||||
|
||||
// One managed-lane play/show write the shell must apply (translated into
|
||||
// C_LANEPLAYS / I_FIXEDLANE / B_FIXEDLANE_HIDDEN). Emitted for MANAGED lanes
|
||||
// only — never a manual lane; enforced in planToggle and mirrored by
|
||||
@@ -257,9 +250,10 @@ struct LanePlayOp {
|
||||
};
|
||||
|
||||
// The complete set of operations to park one inactive leaf, or restore one
|
||||
// leaf. Park uses fixed zeros; restore uses a snapshot's values. fxOffline is
|
||||
// per known FX slot: on park all slots go offline (from the snapshot's slot
|
||||
// count); on restore each slot returns to its captured value.
|
||||
// leaf. Park uses fixed zeros; restore uses a snapshot's values. Park's
|
||||
// fxOffline ops are slot-keyed (every live slot goes offline); restore's carry
|
||||
// the snapshot's keying and are resolved against the live chain by
|
||||
// resolveFxRestore before any write.
|
||||
struct TrackPlan {
|
||||
std::vector<TrackFlagOp> flags;
|
||||
std::vector<FxOfflineOp> fxOffline;
|
||||
|
||||
Reference in New Issue
Block a user