Ψ-W1-T2: disjoint per-mode solo surfaces and a playback-gated mode switch
Solo is cached, cleared and replayed per mode on a real switch only; the switch is refused visibly while the transport runs. The footer segment now routes through the activate actions, so a panel switch finally persists.
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
#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 parked in the incoming mode — a parked track is hidden and 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.
|
||||
// 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
|
||||
// come back unparked.
|
||||
std::vector<SoloOp> planSoloRestore(const std::map<std::string, int>& cached,
|
||||
const std::set<std::string>& liveGuids,
|
||||
const std::set<std::string>& parkedGuids);
|
||||
|
||||
} // namespace reasampler::view
|
||||
Reference in New Issue
Block a user