f2cdf676f3
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.
91 lines
4.1 KiB
C++
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
|