feat(S9/S8): bank-generation change-detection + instrument-side assignment reader
Extension stamps a monotonic bank_generation counter, bumped at content mutations; the VST3 instrument polls it off-thread on an editor timer and consumes assignment requests, refreshing playback hands-free.
This commit is contained in:
+23
-3
@@ -406,6 +406,10 @@ std::vector<std::uint8_t> serializeComponentState(const ComponentState& state) {
|
||||
putU32le(out, kComponentStateVersion);
|
||||
// v4 envelope addition: the channel mode (0 = mono, 1 = stereo) precedes the v3 body.
|
||||
out.push_back(state.channelMode == ChannelMode::Stereo ? 1 : 0);
|
||||
// v5 envelope addition (S8/S9 reader): the last-consumed assignment generation, 8-byte LE
|
||||
// two's-complement, precedes the selection id. Follows the mode byte so a v4 reader that
|
||||
// stops at the mode byte is a strict prefix (see the v4 lift below).
|
||||
putU64le(out, asU64(state.lastConsumedAssignGeneration));
|
||||
// Length-prefixed selection id (it precedes the zones payload, so it MUST be framed —
|
||||
// unlike the v1 selection blob where the id ran to end-of-stream).
|
||||
putU32le(out, static_cast<std::uint32_t>(state.selectionId.size()));
|
||||
@@ -447,15 +451,31 @@ ComponentState deserializeComponentState(const std::vector<std::uint8_t>& bytes)
|
||||
out.selectionId = r.str(idLen);
|
||||
if (!r.ok) { out.selectionId.clear(); return out; } // truncated id -> empty
|
||||
readZonesPayload(r, out.map);
|
||||
return out; // channelMode stays Mono (pre-S7)
|
||||
return out; // channelMode stays Mono, marker stays 0 (pre-S7/S8/S9)
|
||||
}
|
||||
// BACK-COMPAT: a v4 blob (pre-S8/S9 reader {mode, selection, zones}, no consumed marker):
|
||||
// mode byte, then the id + zones body — no 8-byte marker. lastConsumedAssignGeneration
|
||||
// defaults to 0, so a first assign still applies for a pre-marker instance.
|
||||
if (version == kSelectionZonesModeV4Version) {
|
||||
const std::uint8_t modeByte = r.u8();
|
||||
if (!r.ok) return out; // truncated before the mode byte -> empty (mono default holds)
|
||||
out.channelMode = (modeByte == 1) ? ChannelMode::Stereo : ChannelMode::Mono;
|
||||
const std::uint32_t idLen = r.u32();
|
||||
out.selectionId = r.str(idLen);
|
||||
if (!r.ok) { out.selectionId.clear(); return out; } // truncated id -> empty
|
||||
readZonesPayload(r, out.map);
|
||||
return out; // marker stays 0 (pre-S8/S9 reader)
|
||||
}
|
||||
if (version != kComponentStateVersion) return out; // unknown -> empty
|
||||
|
||||
// v4: the channel-mode byte precedes the v3 body. A non-{0,1} byte is treated as mono
|
||||
// (conservative default) rather than rejected — a corrupt mode never silences the instance.
|
||||
// v5: the channel-mode byte, then the 8-byte consumed-assignment marker, precede the v3
|
||||
// body. A non-{0,1} mode byte is treated as mono (conservative default) rather than
|
||||
// rejected — a corrupt mode never silences the instance.
|
||||
const std::uint8_t modeByte = r.u8();
|
||||
if (!r.ok) return out; // truncated before the mode byte -> empty (mono default holds)
|
||||
out.channelMode = (modeByte == 1) ? ChannelMode::Stereo : ChannelMode::Mono;
|
||||
out.lastConsumedAssignGeneration = r.i64();
|
||||
if (!r.ok) return out; // truncated before/inside the marker -> empty (marker 0 holds)
|
||||
const std::uint32_t idLen = r.u32();
|
||||
out.selectionId = r.str(idLen);
|
||||
if (!r.ok) { out.selectionId.clear(); return out; } // truncated id -> empty
|
||||
|
||||
Reference in New Issue
Block a user