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:
@@ -979,6 +979,58 @@ static void testComponentStateV4StereoWithZoneOverridesRoundTrip() {
|
||||
!back.map.zones[1].startPoint.has_value());
|
||||
}
|
||||
|
||||
// --- v5 component state: the S8/S9 last-consumed-assignment marker -------------
|
||||
|
||||
static void testComponentStateV5MarkerRoundTrip() {
|
||||
// The consumed-assignment generation (S8 reader marker) round-trips through the v5 envelope
|
||||
// alongside selection + mode + zones. A non-zero, > 32-bit value proves the 8-byte LE field.
|
||||
ComponentState s;
|
||||
s.selectionId = "pick";
|
||||
s.channelMode = ChannelMode::Stereo;
|
||||
s.lastConsumedAssignGeneration = 1700000123456LL; // > INT32_MAX
|
||||
s.map.zones.push_back(zone("z0", 0, 127, /*override=*/std::nullopt));
|
||||
const ComponentState back = deserializeComponentState(serializeComponentState(s));
|
||||
CHECK(back.lastConsumedAssignGeneration == 1700000123456LL); // marker survives
|
||||
CHECK(back.channelMode == ChannelMode::Stereo);
|
||||
CHECK(back.selectionId == "pick");
|
||||
CHECK(back.map.zones.size() == 1 && back.map.zones[0].sampleId == "z0");
|
||||
}
|
||||
|
||||
static void testComponentStateDefaultMarkerIsZero() {
|
||||
// A default-constructed state has marker 0 and round-trips 0 — a fresh instance's first
|
||||
// assign (generation >= 1) must not be swallowed by a non-zero default.
|
||||
const ComponentState back = deserializeComponentState(serializeComponentState(ComponentState{}));
|
||||
CHECK(back.lastConsumedAssignGeneration == 0);
|
||||
}
|
||||
|
||||
static void testComponentStateV4LiftsMarkerToZero() {
|
||||
// A GENUINE v4 blob (version tag 4: mode byte, then id + zones — NO 8-byte marker) must lift
|
||||
// with lastConsumedAssignGeneration = 0 and its mode/selection/zones intact. Build it by hand
|
||||
// (serializeComponentState now emits v5, so we cannot use it to make a v4 blob). This proves
|
||||
// an already-saved pre-S8/S9 instance restores cleanly and its first assign still applies.
|
||||
std::vector<std::uint8_t> v4;
|
||||
v4.push_back(4); v4.push_back(0); v4.push_back(0); v4.push_back(0); // version 4
|
||||
v4.push_back(1); // channel mode = stereo
|
||||
const std::string id = "saved";
|
||||
v4.push_back(static_cast<std::uint8_t>(id.size())); v4.push_back(0); v4.push_back(0); v4.push_back(0);
|
||||
v4.insert(v4.end(), id.begin(), id.end());
|
||||
v4.push_back(0); v4.push_back(0); v4.push_back(0); v4.push_back(0); // zone count 0
|
||||
const ComponentState back = deserializeComponentState(v4);
|
||||
CHECK(back.lastConsumedAssignGeneration == 0); // no marker in v4 -> default 0
|
||||
CHECK(back.channelMode == ChannelMode::Stereo); // v4 mode byte still honored
|
||||
CHECK(back.selectionId == "saved");
|
||||
CHECK(back.map.zones.empty());
|
||||
}
|
||||
|
||||
static void testComponentStateV5TruncatedMarker() {
|
||||
// A v5 blob truncated inside the 8-byte marker (mode byte present, marker cut short) -> empty,
|
||||
// mono + marker 0 default holds (bounded read, never throws across the host).
|
||||
std::vector<std::uint8_t> t{5, 0, 0, 0, 1, 0xAA, 0xBB}; // version 5, mode byte, 2 marker bytes
|
||||
const ComponentState back = deserializeComponentState(t);
|
||||
CHECK(back.lastConsumedAssignGeneration == 0);
|
||||
CHECK(back.selectionId.empty() && back.map.zones.empty());
|
||||
}
|
||||
|
||||
int main() {
|
||||
testSelectByIdHit();
|
||||
testSelectEmptyIdIsSilence();
|
||||
@@ -1049,6 +1101,10 @@ int main() {
|
||||
testComponentStateV1V2LiftToMono();
|
||||
testComponentStateV4TruncatedModeByte();
|
||||
testComponentStateV4StereoWithZoneOverridesRoundTrip();
|
||||
testComponentStateV5MarkerRoundTrip();
|
||||
testComponentStateDefaultMarkerIsZero();
|
||||
testComponentStateV4LiftsMarkerToZero();
|
||||
testComponentStateV5TruncatedMarker();
|
||||
|
||||
if (g_fail == 0) std::printf("sample_map: all tests passed\n");
|
||||
return g_fail != 0;
|
||||
|
||||
Reference in New Issue
Block a user