Merge pS-w1-t3-state: persist preview-trigger velocity as ComponentState v6 (S-VIEW-4 core)
This commit is contained in:
@@ -1026,6 +1026,85 @@ static void testComponentStateV5TruncatedMarker() {
|
||||
CHECK(back.selectionId.empty() && back.map.zones.empty());
|
||||
}
|
||||
|
||||
// --- v6 component state: the S-VIEW-4 preview-trigger velocity ------------------
|
||||
|
||||
static void testComponentStatePreviewVelocityRoundTrip() {
|
||||
// The preview velocity round-trips through the v6 envelope alongside selection + mode + marker
|
||||
// + zones. A non-default value (not 64) proves the byte is actually read back, not defaulted.
|
||||
ComponentState s;
|
||||
s.selectionId = "pick";
|
||||
s.channelMode = ChannelMode::Stereo;
|
||||
s.lastConsumedAssignGeneration = 1700000123456LL;
|
||||
s.previewVelocity = 111; // non-default
|
||||
s.map.zones.push_back(zone("z0", 0, 127, /*override=*/std::nullopt));
|
||||
const ComponentState back = deserializeComponentState(serializeComponentState(s), 44100.0);
|
||||
CHECK(back.previewVelocity == 111); // velocity survives
|
||||
CHECK(back.channelMode == ChannelMode::Stereo); // envelope neighbours intact
|
||||
CHECK(back.lastConsumedAssignGeneration == 1700000123456LL);
|
||||
CHECK(back.selectionId == "pick");
|
||||
CHECK(back.map.zones.size() == 1 && back.map.zones[0].sampleId == "z0");
|
||||
}
|
||||
|
||||
static void testComponentStateDefaultPreviewVelocityIsMid() {
|
||||
// A default-constructed state carries the mid velocity default and round-trips it.
|
||||
const ComponentState back = deserializeComponentState(serializeComponentState(ComponentState{}), 44100.0);
|
||||
CHECK(back.previewVelocity == kPreviewVelocityDefault);
|
||||
CHECK(kPreviewVelocityDefault == 64);
|
||||
}
|
||||
|
||||
static void testComponentStatePreviewVelocityExtremes() {
|
||||
// The full MIDI velocity range round-trips: 1 (softest audible) and 127 (max) both survive the
|
||||
// single-byte field without clamping or overflow.
|
||||
for (std::uint8_t v : {std::uint8_t{1}, std::uint8_t{127}}) {
|
||||
ComponentState s;
|
||||
s.previewVelocity = v;
|
||||
const ComponentState back = deserializeComponentState(serializeComponentState(s), 44100.0);
|
||||
CHECK(back.previewVelocity == v);
|
||||
}
|
||||
}
|
||||
|
||||
static void testComponentStateV5LiftsVelocityToMid() {
|
||||
// A GENUINE v5 blob (version tag 5: mode byte, 8-byte marker, then id + zones — NO velocity
|
||||
// byte) must lift previewVelocity to kPreviewVelocityDefault, its mode/marker/selection/zones
|
||||
// intact. Build it by hand (serializeComponentState now emits v6, so it cannot make a v5 blob).
|
||||
// This proves an already-saved pre-S-VIEW-4 instance restores at the mid default.
|
||||
std::vector<std::uint8_t> v5;
|
||||
v5.push_back(5); v5.push_back(0); v5.push_back(0); v5.push_back(0); // version 5
|
||||
v5.push_back(1); // channel mode = stereo
|
||||
for (int i = 0; i < 8; ++i) v5.push_back(0); // marker = 0
|
||||
const std::string id = "saved";
|
||||
v5.push_back(static_cast<std::uint8_t>(id.size())); v5.push_back(0); v5.push_back(0); v5.push_back(0);
|
||||
v5.insert(v5.end(), id.begin(), id.end());
|
||||
v5.push_back(0); v5.push_back(0); v5.push_back(0); v5.push_back(0); // zone count 0
|
||||
const ComponentState back = deserializeComponentState(v5, 44100.0);
|
||||
CHECK(back.previewVelocity == kPreviewVelocityDefault); // no velocity byte in v5 -> mid default
|
||||
CHECK(back.channelMode == ChannelMode::Stereo); // v5 mode byte honored
|
||||
CHECK(back.selectionId == "saved");
|
||||
CHECK(back.map.zones.empty());
|
||||
}
|
||||
|
||||
static void testComponentStateV4LiftsVelocityToMid() {
|
||||
// A pre-S8/S9 v4 blob (mode byte, then id + zones — no marker, no velocity) also lifts
|
||||
// previewVelocity to the mid default. Proves the older-than-v5 lift path defaults the field too.
|
||||
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(0); // channel mode = mono
|
||||
v4.push_back(0); v4.push_back(0); v4.push_back(0); v4.push_back(0); // id length 0
|
||||
v4.push_back(0); v4.push_back(0); v4.push_back(0); v4.push_back(0); // zone count 0
|
||||
const ComponentState back = deserializeComponentState(v4, 44100.0);
|
||||
CHECK(back.previewVelocity == kPreviewVelocityDefault);
|
||||
}
|
||||
|
||||
static void testComponentStateV6TruncatedVelocity() {
|
||||
// A v6 blob truncated inside the header before the velocity byte (mode + full marker present,
|
||||
// velocity byte cut) -> empty, with the mid velocity default holding (bounded read, never throws).
|
||||
std::vector<std::uint8_t> t{6, 0, 0, 0, 1}; // version 6, mode byte
|
||||
for (int i = 0; i < 8; ++i) t.push_back(0); // full marker, no velocity byte
|
||||
const ComponentState back = deserializeComponentState(t, 44100.0);
|
||||
CHECK(back.previewVelocity == kPreviewVelocityDefault);
|
||||
CHECK(back.selectionId.empty() && back.map.zones.empty());
|
||||
}
|
||||
|
||||
// --- MERGE COMPOSITION (S9 v5 marker envelope x S15/S16 v3 play-param payload) ----------------
|
||||
//
|
||||
// The merge of ps-w9-t1-sync (envelope v5, adds the consumed-assignment marker) and
|
||||
@@ -1574,6 +1653,12 @@ int main() {
|
||||
testComponentStateDefaultMarkerIsZero();
|
||||
testComponentStateV4LiftsMarkerToZero();
|
||||
testComponentStateV5TruncatedMarker();
|
||||
testComponentStatePreviewVelocityRoundTrip();
|
||||
testComponentStateDefaultPreviewVelocityIsMid();
|
||||
testComponentStatePreviewVelocityExtremes();
|
||||
testComponentStateV5LiftsVelocityToMid();
|
||||
testComponentStateV4LiftsVelocityToMid();
|
||||
testComponentStateV6TruncatedVelocity();
|
||||
testV5EnvelopeWithMarkerAndPlayParamsRoundTrip();
|
||||
testV4BlobWithPlayParamsLiftsMarkerZeroKeepsPlay();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user