// component_state_io unit tests (Q-W2v). The HISTORICAL codec suite — the full // envelope/payload version ladder, every legacy lift, the golden byte fixtures — // lives in test_sample_map.cpp and runs unmodified against the split module; this // target exists as the module's OWN executable (house rule: every pure module has // one) and as the STRUCTURAL PROOF the codec links WITHOUT the voice engine // (T2-07): it links component_state_io + velocity_curve + master_gain only — a // sampler_core/pitch_shift symbol reaching this link is a regression. #include "../src/core/instrument/map/component_state_io.h" #include #include #include using namespace reasampler; using namespace reasampler::instrument::map; static int failures = 0; #define CHECK(cond) \ do { \ if (!(cond)) { \ std::printf("FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \ ++failures; \ } \ } while (0) // A full round-trip through the CURRENT envelope (v11): every field survives. static void testComponentStateRoundTrip() { ComponentState in; in.selectionId = "smp-1"; in.channelMode = ChannelMode::Stereo; in.channelModeExplicit = true; in.lastConsumedAssignGeneration = 42; in.previewVelocity = 99; in.voiceCount = 7; in.voiceMode = VoiceMode::Mono; in.monoTrigger = MonoTrigger::Legato; in.masterGainLinear = 0.5; in.instanceGuid = "0123456789abcdef0123456789abcdef"; SampleRefEntry e; e.sampleId = "smp-1"; e.ref.relativePath = "bank/smp-1.wav"; e.ref.rootNote = 64; e.ref.loop.hasLoop = true; e.ref.loop.start = 100; e.ref.loop.end = 2000; e.ref.channelCount = 2; e.displayName = "My Capture"; in.sampleRefs.push_back(e); PerformanceZone z; z.sampleId = "smp-1"; z.lowNote = 30; z.highNote = 90; z.rootOverride = 61; z.startPoint = 5; z.keyTrack = 1.5; z.play.playMode = PlayMode::Trigger; z.play.trigger.lengthFraction = 0.75; z.play.trigger.fadeInFrames = 441; z.play.trigger.fadeOutFrames = 882; in.map.zones.push_back(z); const std::vector bytes = serializeComponentState(in); const ComponentState out = deserializeComponentState(bytes, 48000.0); CHECK(out.selectionId == "smp-1"); CHECK(out.channelMode == ChannelMode::Stereo); CHECK(out.channelModeExplicit); CHECK(out.lastConsumedAssignGeneration == 42); CHECK(out.previewVelocity == 99); CHECK(out.voiceCount == 7); CHECK(out.voiceMode == VoiceMode::Mono); CHECK(out.monoTrigger == MonoTrigger::Legato); CHECK(out.masterGainLinear == 0.5); CHECK(out.instanceGuid == "0123456789abcdef0123456789abcdef"); CHECK(out.sampleRefs.size() == 1); if (out.sampleRefs.size() == 1) { CHECK(out.sampleRefs[0].sampleId == "smp-1"); CHECK(out.sampleRefs[0].ref.relativePath == "bank/smp-1.wav"); CHECK(out.sampleRefs[0].ref.rootNote == 64); CHECK(out.sampleRefs[0].ref.loop.hasLoop); CHECK(out.sampleRefs[0].ref.loop.start == 100); CHECK(out.sampleRefs[0].ref.loop.end == 2000); CHECK(out.sampleRefs[0].ref.channelCount == 2); CHECK(out.sampleRefs[0].displayName == "My Capture"); } CHECK(out.map.zones.size() == 1); if (out.map.zones.size() == 1) { const PerformanceZone& oz = out.map.zones[0]; CHECK(oz.sampleId == "smp-1"); CHECK(oz.lowNote == 30); CHECK(oz.highNote == 90); CHECK(oz.rootOverride && *oz.rootOverride == 61); CHECK(oz.startPoint && *oz.startPoint == 5); CHECK(oz.keyTrack == 1.5); CHECK(oz.play.playMode == PlayMode::Trigger); CHECK(oz.play.trigger.lengthFraction == 0.75); CHECK(oz.play.trigger.fadeInFrames == 441); CHECK(oz.play.trigger.fadeOutFrames == 882); } } // The FROZEN envelope prefix: version tag v11 LE, then the mode byte — a drift in // either is a byte-format break the round-trip alone can't prove (both sides could // drift together). Pins the writer's absolute bytes. static void testEnvelopePrefixBytesFrozen() { ComponentState in; // defaults: mono, implicit, no refs, no selection, no zones const std::vector bytes = serializeComponentState(in); CHECK(bytes.size() > 5); if (bytes.size() > 5) { CHECK(bytes[0] == 11 && bytes[1] == 0 && bytes[2] == 0 && bytes[3] == 0); CHECK(bytes[4] == 0); // ChannelMode::Mono } CHECK(kComponentStateVersion == 11); CHECK(kZonesPayloadVersion == 7); CHECK(kZonesFormatMarker == 0xFFFFFF00u); } // A v1 selection blob lifts to {id, one full-keyboard zone} — the oldest live lift. static void testV1SelectionLift() { const std::vector v1 = serializeSelection("old-pick"); const ComponentState out = deserializeComponentState(v1, 48000.0); CHECK(out.selectionId == "old-pick"); CHECK(out.map.zones.size() == 1); if (out.map.zones.size() == 1) { CHECK(out.map.zones[0].sampleId == "old-pick"); CHECK(out.map.zones[0].lowNote == 0); CHECK(out.map.zones[0].highNote == 127); } } // Truncation degrades to a partial/empty parse — never out-of-bounds, never throws. static void testTruncationDegradesCleanly() { ComponentState in; in.selectionId = "smp-2"; PerformanceZone z; z.sampleId = "smp-2"; in.map.zones.push_back(z); const std::vector bytes = serializeComponentState(in); for (std::size_t cut = 0; cut < bytes.size(); ++cut) { const std::vector part(bytes.begin(), bytes.begin() + static_cast(cut)); const ComponentState out = deserializeComponentState(part, 48000.0); (void)out; // reaching here without UB/throw is the contract under test } CHECK(true); } // serializePerformance/deserializePerformance round-trip through the v2 envelope. static void testPerformanceRoundTrip() { PerformanceMap in; PerformanceZone z; z.sampleId = "zone-a"; z.lowNote = 10; z.highNote = 20; in.zones.push_back(z); const PerformanceMap out = deserializePerformance(serializePerformance(in), 48000.0); CHECK(out.zones.size() == 1); if (out.zones.size() == 1) { CHECK(out.zones[0].sampleId == "zone-a"); CHECK(out.zones[0].lowNote == 10); CHECK(out.zones[0].highNote == 20); } } int main() { testComponentStateRoundTrip(); testEnvelopePrefixBytesFrozen(); testV1SelectionLift(); testTruncationDegradesCleanly(); testPerformanceRoundTrip(); if (failures == 0) { std::printf("component_state_io_tests: all tests passed\n"); return 0; } std::printf("component_state_io_tests: %d FAILURE(S)\n", failures); return 1; }