Files
reasampler/src/core/view/solo_cache.h
T
daniel f2cdf676f3 Ψ-W1-T2 review remediation: solo restore drops on visible-in-target, not parked; N-mode segments read dead when unroutable
Fixes a hidden-parent solo replay that could silence the mix. Also closes the N-mode segment silent no-op, amends the invariant comment, trims view.cpp under 600 lines, hedges two SDK inferences, drops a dead null-check.
2026-08-01 20:13:10 -04:00

91 lines
4.1 KiB
C++

#pragma once
// Per-mode solo surface: the cache of raw I_SOLO values a real mode switch banks on
// the way out and replays on the way back, plus the two decisions over it. Pure —
// the GetMediaTrackInfo_Value/SetMediaTrackInfo_Value pair is shell/view/view_solo.
// Values are the RAW I_SOLO int, never collapsed to a bool: the SDK's domain is
// 0=off, 1=solo, 2=solo-in-place, 5=safe solo, 6=safe solo-in-place, and all four
// non-zero variants must survive the round trip.
#include <cstddef>
#include <map>
#include <set>
#include <string>
#include <vector>
namespace reasampler::view {
// One live (track GUID, I_SOLO) reading from the shell's enumeration.
struct TrackSolo {
std::string guid;
int solo = 0;
};
// One I_SOLO write the shell must apply.
struct SoloOp {
std::string guid;
int value = 0;
bool operator==(const SoloOp& o) const { return guid == o.guid && value == o.value; }
};
// The soloed subset of a live enumeration. Zero is the resting value — a track at
// zero is neither cached (nothing to replay) nor cleared (nothing to undo), so a
// project with no solo anywhere produces no cache entry and no project write at
// all. Empty GUIDs are dropped: they can never resolve back to a track.
std::map<std::string, int> soloedTracks(const std::vector<TrackSolo>& live);
// mode id -> (track GUID -> raw I_SOLO). Lifecycle mirrors the park/restore
// snapshots: stored on the way out of a mode, consumed on the way back in, pruned
// when a GUID stops existing.
class SoloCache {
public:
// Replaces `modeId`'s entry. An EMPTY set removes it rather than storing an
// empty record — otherwise a serialized cache would parse back to a model that
// differs from its source, breaking the model's round-trip contract.
bool store(const std::string& modeId, const std::map<std::string, int>& soloed);
const std::map<std::string, int>* query(const std::string& modeId) const;
bool clear(const std::string& modeId);
const std::map<std::string, std::map<std::string, int>>& all() const { return byMode_; }
bool empty() const { return byMode_.empty(); }
// Drops every cached GUID absent from `liveGuids`, and any mode left empty.
// Returns the number of GUID entries removed.
//
// Pruned like the snapshots and unlike membership: a cached solo is a captured
// prior value awaiting replay onto one specific track, so a stale entry
// surviving a delete would replay onto whatever track later reuses that GUID —
// soloing a track the user never soloed and silencing the rest of the mix.
// The cost is the mirror case: undoing a track delete restores the GUID but not
// its cached solo. One lost solo the user can see and re-click beats an
// inexplicable mix-wide mute.
std::size_t reconcile(const std::set<std::string>& liveGuids);
bool operator==(const SoloCache& o) const { return byMode_ == o.byMode_; }
private:
std::map<std::string, std::map<std::string, int>> byMode_;
};
// The incoming mode's restore writes, in GUID order. Two kinds of entry are dropped
// rather than written:
// * a GUID absent from `liveGuids` — the track is gone (same prune rule as above);
// * a GUID absent from `visibleGuids` — not visible in the incoming mode, whether
// because the leaf is parked or because it is a folder parent that is itself
// derived-invisible there. Either way the track is hidden and (for a parked
// leaf) carries B_MAINSEND=0, so soloing it would silence the whole mix while
// contributing nothing audible, and the user would have no visible control to
// undo it. A show-both leaf is a member of every mode's visible set, so it is
// never dropped by this rule.
// The caller consumes the whole mode entry regardless (clear-on-restore), so a
// dropped entry does not linger as zombie state waiting on a track that may never
// become visible again.
std::vector<SoloOp> planSoloRestore(const std::map<std::string, int>& cached,
const std::set<std::string>& liveGuids,
const std::set<std::string>& visibleGuids);
} // namespace reasampler::view