// Standalone tests for reasampler::sample_map — no VST3, no REAPER, no test framework. // Same fast assert loop as the sibling pure tests. This module is the S4 mapping heart: // bank blob -> selected sample (through the SHARED bank_book JSON parse), interleaved -> // mono downmix (the Tier-0 channel policy), the Tier-0 chromatic keymap build, and the // selected-sample instance-state (de)serialization. // // Every assertion is written to FAIL if the mapping were wrong: the bank blobs are built // by serializing a real BankBook (so we exercise the shared parse, not a fixture string), // and the selection / downmix / keymap / state values are checked against independently // computed expectations. // // Covers: selectSample by-id hit (across pool + named banks), first-sample fallback for // an empty / unknown id, empty & malformed blob -> nullopt, zero-samples -> nullopt, // rootNote/loop intrinsic threading incl. the middle-C default; listSamples ordinal // order + empty/malformed; downmixToMono mono passthrough / stereo average / 3-ch // average / zero-stride / empty; buildTier0Keymap single full-keyboard zone with the // root + loop + rate threaded and rate defaulting; selection state round-trip + empty id // + wrong-version / truncated -> "". // wav_trim -> extractFloatFrames -> downmixToMono integration: locks the interleave- // stride contract across the seam (that the byte stride wav_trim reports matches the // channel-count stride downmixToMono divides by). #include "../src/vst/sample_map.h" #include #include #include #include #include #include "../src/bank_book.h" #include "../src/bank_model.h" using namespace reasampler; static int g_fail = 0; #define CHECK(cond) do { if(!(cond)) { \ std::printf("FAIL line %d: %s\n", __LINE__, #cond); ++g_fail; } } while(0) // Build a Sample with the fields sample_map reads. Relative path is required by // BankIndex::add (relative-only invariant); a content hash is set so dedup does not // collapse distinct entries. static Sample makeSample(const std::string& id, const std::string& name, const std::string& rel, std::optional root) { Sample s; s.id = id; s.displayName = name; s.relativePath = rel; s.contentHash = "hash-" + id; s.rootNote = root; return s; } // A serialized BankBook: the pool carries `poolSamples`, and one named bank "Drums" // carries `drumSamples`. Returns the JSON the instrument would read from ext-state. static std::string bookJson(const std::vector& poolSamples, const std::vector& drumSamples) { BankBook book; for (const Sample& s : poolSamples) book.pool().index.add(s); if (!drumSamples.empty()) { book.createBank("drums-id", "Drums"); BankIndex* di = book.index("drums-id"); for (const Sample& s : drumSamples) di->add(s); } return book.serialize(); } // --- selectSample ------------------------------------------------------------- static void testSelectByIdHit() { const std::string json = bookJson( {makeSample("a", "Kick", "reasampler_bank/a.wav", 36)}, {makeSample("b", "Snare", "reasampler_bank/b.wav", 38)}); // A sample in the NAMED bank resolves by id (search spans every bank). auto sel = selectSample(json, "b"); CHECK(sel.has_value()); CHECK(sel && sel->relativePath == "reasampler_bank/b.wav"); CHECK(sel && sel->rootNote == 38); } static void testSelectFirstSampleFallbackOnEmptyId() { const std::string json = bookJson( {makeSample("a", "Kick", "reasampler_bank/a.wav", 36)}, {makeSample("b", "Snare", "reasampler_bank/b.wav", 38)}); // No stored selection -> the FIRST sample in ordinal order (pool first). auto sel = selectSample(json, ""); CHECK(sel.has_value()); CHECK(sel && sel->relativePath == "reasampler_bank/a.wav"); CHECK(sel && sel->rootNote == 36); } static void testSelectFirstSampleFallbackOnUnknownId() { const std::string json = bookJson( {makeSample("a", "Kick", "reasampler_bank/a.wav", 36)}, {}); // A stored id that no longer resolves falls back to the first sample, not silence. auto sel = selectSample(json, "deleted-id"); CHECK(sel.has_value()); CHECK(sel && sel->relativePath == "reasampler_bank/a.wav"); } static void testSelectRootNoteDefault() { const std::string json = bookJson( {makeSample("a", "Loop", "reasampler_bank/a.wav", std::nullopt)}, {}); // A sample with no root-note intrinsic defaults to middle C (60). auto sel = selectSample(json, "a"); CHECK(sel.has_value()); CHECK(sel && sel->rootNote == 60); } static void testSelectLoopThreaded() { Sample s = makeSample("a", "Pad", "reasampler_bank/a.wav", 60); s.loop = LoopPoints{100, 500}; const std::string json = bookJson({s}, {}); auto sel = selectSample(json, "a"); CHECK(sel.has_value()); CHECK(sel && sel->loop.hasLoop); CHECK(sel && sel->loop.start == 100 && sel->loop.end == 500); } static void testSelectNoLoopIsAbsent() { const std::string json = bookJson( {makeSample("a", "OneShot", "reasampler_bank/a.wav", 60)}, {}); auto sel = selectSample(json, "a"); CHECK(sel.has_value()); CHECK(sel && !sel->loop.hasLoop); // absent loop -> hasLoop false (not a zero loop) } static void testSelectEmptyBlob() { CHECK(!selectSample("", "a").has_value()); } static void testSelectMalformedBlob() { CHECK(!selectSample("{not valid json", "a").has_value()); } static void testSelectZeroSamples() { // A valid book with NO samples anywhere -> nothing to play. const std::string json = bookJson({}, {}); CHECK(!selectSample(json, "").has_value()); CHECK(!selectSample(json, "anything").has_value()); } // --- listSamples -------------------------------------------------------------- static void testListSamplesOrdinalOrder() { const std::string json = bookJson( {makeSample("a", "Kick", "reasampler_bank/a.wav", 36), makeSample("c", "Hat", "reasampler_bank/c.wav", 42)}, {makeSample("b", "Snare", "reasampler_bank/b.wav", 38)}); const std::vector list = listSamples(json); // Pool samples (insertion order) come before the named bank's. CHECK(list.size() == 3); CHECK(list.size() == 3 && list[0].id == "a" && list[0].displayName == "Kick"); CHECK(list.size() == 3 && list[1].id == "c"); CHECK(list.size() == 3 && list[2].id == "b" && list[2].displayName == "Snare"); } static void testListSamplesEmptyAndMalformed() { CHECK(listSamples("").empty()); CHECK(listSamples("{garbage").empty()); CHECK(listSamples(bookJson({}, {})).empty()); } // --- downmixToMono ------------------------------------------------------------ static bool approx(double a, double b) { return std::fabs(a - b) < 1e-6; } static void testDownmixMonoPassthrough() { const std::vector in{0.1f, -0.2f, 0.3f}; const std::vector out = downmixToMono(in, 1); CHECK(out.size() == 3); CHECK(out.size() == 3 && approx(out[0], 0.1) && approx(out[1], -0.2) && approx(out[2], 0.3)); } static void testDownmixStereoAverages() { // Two frames, stereo interleaved: frame0 = (1.0, 0.0) -> 0.5; frame1 = (0.4, 0.6) -> 0.5. const std::vector in{1.0f, 0.0f, 0.4f, 0.6f}; const std::vector out = downmixToMono(in, 2); CHECK(out.size() == 2); CHECK(out.size() == 2 && approx(out[0], 0.5) && approx(out[1], 0.5)); } static void testDownmixThreeChannelAverages() { // One 3-channel frame (0.3, 0.3, 0.6) -> 0.4. const std::vector in{0.3f, 0.3f, 0.6f}; const std::vector out = downmixToMono(in, 3); CHECK(out.size() == 1); CHECK(out.size() == 1 && approx(out[0], 0.4)); } static void testDownmixDegenerate() { CHECK(downmixToMono({}, 2).empty()); // empty input CHECK(downmixToMono({0.1f, 0.2f}, 0).empty()); // zero stride CHECK(downmixToMono({0.1f, 0.2f}, -1).empty()); // negative stride } // --- buildTier0Keymap --------------------------------------------------------- static void testBuildKeymapSingleFullZone() { SampleLoop loop; loop.hasLoop = true; loop.start = 10; loop.end = 90; const Keymap km = buildTier0Keymap({0.1f, 0.2f, 0.3f}, 48000, 40, loop); // One sample, one zone spanning the whole keyboard, rooted at 40. CHECK(km.samples.size() == 1); CHECK(km.zones.size() == 1); CHECK(km.zones.size() == 1 && km.zones[0].lowNote == 0 && km.zones[0].highNote == 127); CHECK(km.zones.size() == 1 && km.zones[0].rootNote == 40); CHECK(km.samples.size() == 1 && km.samples[0].sampleRate == 48000); CHECK(km.samples.size() == 1 && km.samples[0].rootNote == 40); CHECK(km.samples.size() == 1 && km.samples[0].frames.size() == 3); CHECK(km.samples.size() == 1 && km.samples[0].loop.hasLoop && km.samples[0].loop.start == 10 && km.samples[0].loop.end == 90); // Resolution: any note lands in the single zone. CHECK(km.resolve(0, 100).matched); CHECK(km.resolve(127, 100).matched); } static void testBuildKeymapRateDefault() { // A zero/invalid rate defaults to 44100 rather than producing a divide-by-zero-shaped // sample rate downstream. const Keymap km = buildTier0Keymap({0.1f}, 0, 60, SampleLoop{}); CHECK(km.samples.size() == 1 && km.samples[0].sampleRate == 44100); } // --- selection state (setState/getState) -------------------------------------- static void testSelectionStateRoundTrip() { const std::string id = "sample-guid-123"; const std::vector bytes = serializeSelection(id); // Versioned: 4-byte tag + the id bytes. CHECK(bytes.size() == 4 + id.size()); CHECK(deserializeSelection(bytes) == id); } static void testSelectionStateEmptyId() { const std::vector bytes = serializeSelection(""); CHECK(bytes.size() == 4); // just the version tag CHECK(deserializeSelection(bytes) == ""); } static void testSelectionStateWrongVersion() { std::vector bytes = serializeSelection("id"); bytes[0] = 0xEE; // corrupt the version tag CHECK(deserializeSelection(bytes) == ""); // unknown version -> no selection } static void testSelectionStateTruncated() { CHECK(deserializeSelection({}) == ""); // empty CHECK(deserializeSelection({1, 0, 0}) == ""); // fewer than 4 bytes (no tag) } // --- wav_trim -> extractFloatFrames -> downmixToMono integration --------------- // // Locks the interleave-stride contract at the seam between wav_trim and sample_map: // wav_trim reports channelCount, extractFloatFrames yields interleaved samples with // that stride, and downmixToMono divides by that same stride. If either module // changed its understanding of the layout (e.g. extractFloatFrames started packing // differently, or downmixToMono changed its stride divisor), this test catches it. static void putU16sm(std::vector& b, std::uint16_t v) { b.push_back(static_cast(v & 0xFF)); b.push_back(static_cast((v >> 8) & 0xFF)); } static void putU32sm(std::vector& b, std::uint32_t v) { b.push_back(static_cast(v & 0xFF)); b.push_back(static_cast((v >> 8) & 0xFF)); b.push_back(static_cast((v >> 16) & 0xFF)); b.push_back(static_cast((v >> 24) & 0xFF)); } static void putTagsm(std::vector& b, const char* t) { for (int i = 0; i < 4; ++i) b.push_back(static_cast(t[i])); } static void putFloatsm(std::vector& b, float f) { std::uint8_t tmp[4]; std::memcpy(tmp, &f, 4); for (int i = 0; i < 4; ++i) b.push_back(tmp[i]); } // Build a 32-bit-float WAV byte buffer. Samples: frame f, channel c = value(f, c). template static std::vector buildWav(std::uint16_t channels, std::uint32_t sampleRate, std::size_t frames, Fn value) { const std::uint32_t dataBytes = static_cast(frames * channels * 4u); std::vector chunks; putTagsm(chunks, "fmt "); putU32sm(chunks, 16); putU16sm(chunks, 3); // IEEE float putU16sm(chunks, channels); putU32sm(chunks, sampleRate); putU32sm(chunks, sampleRate * channels * 4u); putU16sm(chunks, static_cast(channels * 4)); putU16sm(chunks, 32); putTagsm(chunks, "data"); putU32sm(chunks, dataBytes); for (std::size_t f = 0; f < frames; ++f) for (std::uint16_t c = 0; c < channels; ++c) putFloatsm(chunks, value(f, c)); std::vector wav; putTagsm(wav, "RIFF"); putU32sm(wav, static_cast(4 + chunks.size())); putTagsm(wav, "WAVE"); wav.insert(wav.end(), chunks.begin(), chunks.end()); return wav; } static void testWavTrimToDownmixPipelineStereo() { // Stereo WAV: frame f, L = f * 0.1f, R = f * 0.1f + 0.5f. Expected mono average: // (f * 0.1f + f * 0.1f + 0.5f) / 2 = f * 0.1f + 0.25f. const std::size_t kFrames = 4; auto wav = buildWav(2, 48000, kFrames, [](std::size_t f, std::uint16_t c) { return static_cast(f) * 0.1f + (c == 1 ? 0.5f : 0.0f); }); WavLayout layout = parseWavLayout(wav); CHECK(layout.valid); CHECK(layout.channelCount == 2); CHECK(layout.frameCount() == kFrames); const std::vector interleaved = extractFloatFrames(wav, layout, 0, layout.frameCount()); CHECK(interleaved.size() == kFrames * 2); const std::vector mono = downmixToMono(interleaved, layout.channelCount); CHECK(mono.size() == kFrames); for (std::size_t f = 0; f < kFrames; ++f) { const float expected = static_cast(f) * 0.1f + 0.25f; CHECK(approx(mono[f], expected)); } } static void testWavTrimToDownmixPipelineMono() { // Mono WAV: extractFloatFrames -> downmixToMono with channelCount==1 is a passthrough. const std::size_t kFrames = 3; auto wav = buildWav(1, 44100, kFrames, [](std::size_t f, std::uint16_t) { return static_cast(f) * 0.5f; }); WavLayout layout = parseWavLayout(wav); CHECK(layout.valid); CHECK(layout.channelCount == 1); const std::vector interleaved = extractFloatFrames(wav, layout, 0, layout.frameCount()); CHECK(interleaved.size() == kFrames); const std::vector mono = downmixToMono(interleaved, layout.channelCount); CHECK(mono.size() == kFrames); CHECK(approx(mono[0], 0.0) && approx(mono[1], 0.5) && approx(mono[2], 1.0)); } // --- performance map: resolvePerformance -------------------------------------- static PerformanceZone zone(const std::string& id, int lo, int hi, std::optional rootOverride = std::nullopt) { PerformanceZone z; z.sampleId = id; z.lowNote = lo; z.highNote = hi; z.rootOverride = rootOverride; return z; } static void testResolveEmptyMap() { // An empty performance map resolves to nothing (the shell falls back to Tier 0). const std::string json = bookJson({makeSample("a", "Kick", "b/a.wav", 36)}, {}); const ResolvedPerformance r = resolvePerformance(json, PerformanceMap{}); CHECK(r.zones.empty()); CHECK(r.droppedSampleIds.empty()); } static void testResolveEmptyBlob() { PerformanceMap m; m.zones.push_back(zone("a", 0, 127)); CHECK(resolvePerformance("", m).zones.empty()); // no bank CHECK(resolvePerformance("{garbage", m).zones.empty()); // malformed } static void testResolveMultiZoneAcrossBanks() { const std::string json = bookJson( {makeSample("a", "Kick", "b/a.wav", 36)}, {makeSample("b", "Snare", "b/b.wav", 38)}); PerformanceMap m; m.zones.push_back(zone("a", 36, 47)); m.zones.push_back(zone("b", 48, 59)); const ResolvedPerformance r = resolvePerformance(json, m); CHECK(r.zones.size() == 2); CHECK(r.droppedSampleIds.empty()); // Order preserved; paths + ranges threaded. CHECK(r.zones.size() == 2 && r.zones[0].relativePath == "b/a.wav"); CHECK(r.zones.size() == 2 && r.zones[0].lowNote == 36 && r.zones[0].highNote == 47); CHECK(r.zones.size() == 2 && r.zones[1].relativePath == "b/b.wav"); CHECK(r.zones.size() == 2 && r.zones[1].lowNote == 48 && r.zones[1].highNote == 59); } static void testResolveStaleIdDropsZone() { // STALE-ID POLICY: a zone naming a deleted sample is dropped, its id reported; the // surviving zone still resolves (the whole map is NOT abandoned). const std::string json = bookJson({makeSample("a", "Kick", "b/a.wav", 36)}, {}); PerformanceMap m; m.zones.push_back(zone("a", 0, 59)); m.zones.push_back(zone("ghost", 60, 127)); // no such sample const ResolvedPerformance r = resolvePerformance(json, m); CHECK(r.zones.size() == 1); CHECK(r.zones.size() == 1 && r.zones[0].relativePath == "b/a.wav"); CHECK(r.droppedSampleIds.size() == 1); CHECK(r.droppedSampleIds.size() == 1 && r.droppedSampleIds[0] == "ghost"); } static void testResolveRootPrecedence() { // Override beats bank intrinsic beats middle-C default. const std::string json = bookJson( {makeSample("rooted", "R", "b/r.wav", 40), // bank intrinsic 40 makeSample("unrooted", "U", "b/u.wav", std::nullopt)}, // no intrinsic {}); PerformanceMap m; m.zones.push_back(zone("rooted", 0, 42)); // no override -> 40 m.zones.push_back(zone("rooted", 43, 84, /*override=*/72)); // override -> 72 m.zones.push_back(zone("unrooted", 85, 127)); // no intrinsic -> 60 const ResolvedPerformance r = resolvePerformance(json, m); CHECK(r.zones.size() == 3); CHECK(r.zones.size() == 3 && r.zones[0].rootNote == 40); // bank intrinsic CHECK(r.zones.size() == 3 && r.zones[1].rootNote == 72); // override wins CHECK(r.zones.size() == 3 && r.zones[2].rootNote == 60); // middle-C default } static void testResolveLoopThreaded() { Sample s = makeSample("a", "Pad", "b/a.wav", 60); s.loop = LoopPoints{200, 800}; const std::string json = bookJson({s}, {}); PerformanceMap m; m.zones.push_back(zone("a", 0, 127)); const ResolvedPerformance r = resolvePerformance(json, m); CHECK(r.zones.size() == 1); CHECK(r.zones.size() == 1 && r.zones[0].loop.hasLoop); CHECK(r.zones.size() == 1 && r.zones[0].loop.start == 200 && r.zones[0].loop.end == 800); } // --- performance map: buildZonedKeymap ---------------------------------------- static void testBuildZonedKeymapMultiZone() { std::vector zones; ResolvedZone z0; z0.lowNote = 36; z0.highNote = 47; z0.rootNote = 36; zones.push_back(z0); ResolvedZone z1; z1.lowNote = 48; z1.highNote = 59; z1.rootNote = 48; zones.push_back(z1); std::vector decoded; decoded.push_back(DecodedZonePcm{{0.1f, 0.2f}, 44100}); decoded.push_back(DecodedZonePcm{{0.3f, 0.4f, 0.5f}, 48000}); const Keymap km = buildZonedKeymap(zones, decoded); CHECK(km.samples.size() == 2); CHECK(km.zones.size() == 2); // Zone 0 -> sample 0, rooted 36, range 36..47; zone 1 -> sample 1, rooted 48. CHECK(km.zones.size() == 2 && km.zones[0].sampleIndex == 0 && km.zones[0].rootNote == 36); CHECK(km.zones.size() == 2 && km.zones[0].lowNote == 36 && km.zones[0].highNote == 47); CHECK(km.zones.size() == 2 && km.zones[1].sampleIndex == 1 && km.zones[1].rootNote == 48); CHECK(km.samples.size() == 2 && km.samples[1].sampleRate == 48000); CHECK(km.samples.size() == 2 && km.samples[1].frames.size() == 3); // Resolution: a note in each range lands in the right zone. CHECK(km.resolve(40, 100).matched && km.resolve(40, 100).zoneIndex == 0); CHECK(km.resolve(52, 100).matched && km.resolve(52, 100).zoneIndex == 1); // A note outside every zone does not match (no-play, not zone 0). CHECK(!km.resolve(24, 100).matched); } static void testBuildZonedKeymapDropsEmptyPcm() { // A zone whose decoded WAV is empty is dropped; the other zone survives, and the // survivor's sampleIndex points at ITS sample (not the dropped one's slot). std::vector zones; ResolvedZone z0; z0.lowNote = 0; z0.highNote = 63; z0.rootNote = 60; zones.push_back(z0); ResolvedZone z1; z1.lowNote = 64; z1.highNote = 127; z1.rootNote = 72; zones.push_back(z1); std::vector decoded; decoded.push_back(DecodedZonePcm{{}, 44100}); // empty -> dropped decoded.push_back(DecodedZonePcm{{0.9f}, 44100}); // survives const Keymap km = buildZonedKeymap(zones, decoded); CHECK(km.samples.size() == 1); CHECK(km.zones.size() == 1); CHECK(km.zones.size() == 1 && km.zones[0].sampleIndex == 0); // remapped to slot 0 CHECK(km.zones.size() == 1 && km.zones[0].lowNote == 64 && km.zones[0].rootNote == 72); } static void testBuildZonedKeymapOverlapFirstWins() { // OVERLAP POLICY: two zones share keys; the FIRST in order wins the contested note // (mirrors the S3 core's first-match resolve). std::vector zones; ResolvedZone z0; z0.lowNote = 0; z0.highNote = 127; z0.rootNote = 60; zones.push_back(z0); ResolvedZone z1; z1.lowNote = 60; z1.highNote = 72; z1.rootNote = 48; zones.push_back(z1); std::vector decoded; decoded.push_back(DecodedZonePcm{{0.1f}, 44100}); decoded.push_back(DecodedZonePcm{{0.2f}, 44100}); const Keymap km = buildZonedKeymap(zones, decoded); CHECK(km.zones.size() == 2); // Note 64 is in both zones; first-match resolves to zone 0. CHECK(km.resolve(64, 100).matched && km.resolve(64, 100).zoneIndex == 0); } static void testBuildZonedKeymapEmpty() { // No zones -> empty keymap (silence). const Keymap km = buildZonedKeymap({}, {}); CHECK(km.samples.empty() && km.zones.empty()); CHECK(!km.resolve(60, 100).matched); } // --- performance-map state: serialize / deserialize --------------------------- static void testPerformanceStateRoundTrip() { PerformanceMap m; m.zones.push_back(zone("kick", 36, 47)); // no override m.zones.push_back(zone("snare", 48, 59, /*override=*/50)); // with override const std::vector bytes = serializePerformance(m); const PerformanceMap back = deserializePerformance(bytes); CHECK(back.zones.size() == 2); CHECK(back.zones.size() == 2 && back.zones[0].sampleId == "kick"); CHECK(back.zones.size() == 2 && back.zones[0].lowNote == 36 && back.zones[0].highNote == 47); CHECK(back.zones.size() == 2 && !back.zones[0].rootOverride.has_value()); CHECK(back.zones.size() == 2 && back.zones[1].sampleId == "snare"); CHECK(back.zones.size() == 2 && back.zones[1].rootOverride.has_value() && *back.zones[1].rootOverride == 50); } static void testPerformanceStateEmpty() { const std::vector bytes = serializePerformance(PerformanceMap{}); // Just the version + zero-count header. CHECK(bytes.size() == 8); CHECK(deserializePerformance(bytes).zones.empty()); } static void testPerformanceStateV1BackCompat() { // A v1 blob (the S4 single-selection format) lifts to a single full-keyboard zone. const std::vector v1 = serializeSelection("legacy-sample-id"); const PerformanceMap back = deserializePerformance(v1); CHECK(back.zones.size() == 1); CHECK(back.zones.size() == 1 && back.zones[0].sampleId == "legacy-sample-id"); CHECK(back.zones.size() == 1 && back.zones[0].lowNote == 0 && back.zones[0].highNote == 127); CHECK(back.zones.size() == 1 && !back.zones[0].rootOverride.has_value()); // A v1 blob with an EMPTY id lifts to an empty map (no zone for "no selection"). CHECK(deserializePerformance(serializeSelection("")).zones.empty()); } static void testPerformanceStateGarbage() { // Unknown version / truncated / empty -> empty map (never throws). CHECK(deserializePerformance({}).zones.empty()); CHECK(deserializePerformance({0xAA, 0xBB, 0xCC, 0xDD}).zones.empty()); // unknown version // Truncated mid-zone: valid v2 header claiming 1 zone but no zone bytes -> empty. std::vector t; t.push_back(2); t.push_back(0); t.push_back(0); t.push_back(0); // version 2 t.push_back(1); t.push_back(0); t.push_back(0); t.push_back(0); // count 1 // (no zone payload) CHECK(deserializePerformance(t).zones.empty()); } static void testPerformanceStateNegativeNotesRoundTrip() { // Notes are clamped in the UI, but the wire format must survive the full int range so // a hand-set/legacy value round-trips without corruption (two's-complement on the wire). PerformanceMap m; m.zones.push_back(zone("s", 0, 127, /*override=*/0)); const PerformanceMap back = deserializePerformance(serializePerformance(m)); CHECK(back.zones.size() == 1 && back.zones[0].rootOverride.has_value() && *back.zones[0].rootOverride == 0); } int main() { testSelectByIdHit(); testSelectFirstSampleFallbackOnEmptyId(); testSelectFirstSampleFallbackOnUnknownId(); testSelectRootNoteDefault(); testSelectLoopThreaded(); testSelectNoLoopIsAbsent(); testSelectEmptyBlob(); testSelectMalformedBlob(); testSelectZeroSamples(); testListSamplesOrdinalOrder(); testListSamplesEmptyAndMalformed(); testDownmixMonoPassthrough(); testDownmixStereoAverages(); testDownmixThreeChannelAverages(); testDownmixDegenerate(); testBuildKeymapSingleFullZone(); testBuildKeymapRateDefault(); testSelectionStateRoundTrip(); testSelectionStateEmptyId(); testSelectionStateWrongVersion(); testSelectionStateTruncated(); testWavTrimToDownmixPipelineStereo(); testWavTrimToDownmixPipelineMono(); testResolveEmptyMap(); testResolveEmptyBlob(); testResolveMultiZoneAcrossBanks(); testResolveStaleIdDropsZone(); testResolveRootPrecedence(); testResolveLoopThreaded(); testBuildZonedKeymapMultiZone(); testBuildZonedKeymapDropsEmptyPcm(); testBuildZonedKeymapOverlapFirstWins(); testBuildZonedKeymapEmpty(); testPerformanceStateRoundTrip(); testPerformanceStateEmpty(); testPerformanceStateV1BackCompat(); testPerformanceStateGarbage(); testPerformanceStateNegativeNotesRoundTrip(); if (g_fail == 0) std::printf("sample_map: all tests passed\n"); return g_fail != 0; }