Q-W2v: split VST god-modules — editor 8 face-axis TUs (+pure layout hoist), processor 3 TUs, component_state_io codec split (extension drops the voice engine), zone_params.h, core/wire putLE; formats frozen, 61/61 green

This commit is contained in:
2026-07-29 10:56:09 -04:00
parent 9d5783453c
commit ea86f540b8
37 changed files with 6202 additions and 5352 deletions
+21
View File
@@ -160,6 +160,26 @@ static void testFilterIndices() {
CHECK(filterNameIndices(names, "zzz").empty());
}
// --- The Browse-modal regions (Q-W2v hoist, T2-06) ---------------------------
// Title / search / content / footer partition the window top-to-bottom without gaps;
// Back right-anchors in the title band; Cancel/Load flank the footer.
static void testBrowseModalPartition() {
const BrowseModal m = computeBrowseModal(840, 620);
CHECK(m.title.y == 0 && m.title.width == 840 && m.title.height == kTitleHeight);
CHECK(m.back.right() == 840 - kPad && m.back.bottom() <= m.title.bottom());
CHECK(m.search.y == m.title.bottom());
CHECK(m.search.height == searchBoxRect(840).height);
CHECK(m.content.y == m.search.bottom());
CHECK(m.content.bottom() == 620 - 30); // the footer band (kBrowseFooterH)
CHECK(m.cancel.x == kPad);
CHECK(m.confirm.right() == 840 - kPad);
CHECK(m.cancel.y == m.content.bottom() + 3 && m.cancel.y == m.confirm.y);
// Degenerate short window: the footer clamps below the search box (no inversion).
const BrowseModal s = computeBrowseModal(840, 40);
CHECK(s.content.bottom() >= s.content.y);
}
int main() {
testContentHeight();
testMaxOffsetFitsAndOverflows();
@@ -174,6 +194,7 @@ int main() {
testSearchBoxRect();
testNameMatch();
testFilterIndices();
testBrowseModalPartition();
if (g_fail == 0) std::printf("browser_scroll: all tests passed\n");
return g_fail != 0;
+179
View File
@@ -0,0 +1,179 @@
// 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 <cstdio>
#include <string>
#include <vector>
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<std::uint8_t> 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<std::uint8_t> 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<std::uint8_t> 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<std::uint8_t> bytes = serializeComponentState(in);
for (std::size_t cut = 0; cut < bytes.size(); ++cut) {
const std::vector<std::uint8_t> part(bytes.begin(),
bytes.begin() + static_cast<long>(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;
}
+75
View File
@@ -311,6 +311,78 @@ static void testZoneHitTestMisses() {
CHECK(zoneHitTest(L, 3, L.sampleList.x + 2, midY).zoneIndex == -1);
}
// --- r11 Sample / Zone face layout (Q-W2v hoist, T2-06) ----------------------
// The band stack at the default 840x620 with a 120px deck: title / hero / cluster /
// deck in order, hero elastic (absorbs the slack), deck bottom-anchored at kPad.
static void testSampleBandsStackAndElasticHero() {
const SampleBands b = computeSampleBands(840, 620, 120);
CHECK(b.title.y == 0 && b.title.height == kTitleHeight && b.title.width == 840);
CHECK(b.hero.y == b.title.bottom());
CHECK(b.hero.height >= 150); // above the hero floor
CHECK(b.cluster.y > b.hero.bottom()); // cluster below the hero (+gap)
CHECK(b.deck.bottom() == 620 - kPad); // deck bottom-anchored
CHECK(b.deck.height == 120);
// Nav buttons right-anchored inside the title band, Browse left of Zone.
CHECK(b.navZone.right() == 840 - kPad);
CHECK(b.navBrowse.right() < b.navZone.x);
CHECK(b.navZone.bottom() <= b.title.bottom());
// A too-short window: the hero keeps its floor; the lower bands clip below.
const SampleBands s = computeSampleBands(840, 200, 120);
CHECK(s.hero.height == 150);
CHECK(s.deck.bottom() > 200); // clips past the window bottom (defensive case)
}
// The cluster's right-anchored run tiles left of the channel toggle without overlap:
// rootStrip | preview | velCell(velKnob+velLabel) | curveBtn | (toggle).
static void testClusterRectsRunAndKnobCentering() {
const Rect cluster = Rect::ltrb(0, 500, 840, 552);
const ChannelToggleRects chan = channelToggleRects(cluster);
CHECK(chan.stereo.right() == 840 - kPad);
CHECK(chan.mono.right() == chan.stereo.x);
const ClusterRects cr = clusterRects(cluster, chan.mono, 28);
CHECK(cr.curveBtn.right() == chan.mono.x - kPad);
CHECK(cr.velCell.right() == cr.curveBtn.x - kPad);
CHECK(cr.preview.right() == cr.velCell.x - kPad);
CHECK(cr.rootStrip.x == cluster.x + kPad);
CHECK(cr.rootStrip.right() == cr.preview.x - kPad);
// The knob square centers in the cell and the label band sits beneath it.
CHECK(cr.velKnob.width == 28);
CHECK(cr.velKnob.x - cr.velCell.x == cr.velCell.right() - cr.velKnob.right());
CHECK(cr.velLabel.y == cr.velKnob.bottom());
CHECK(cr.velLabel.bottom() == cr.velCell.bottom());
}
// The Zone surface: content below the title; strip below the add/delete row; the note
// entry fields tile in three ordered segments; deck + curve button split the panel.
static void testZoneSurfaceLayoutAnchors() {
const Rect content = zoneContentArea(840, 620);
CHECK(content.y == kTitleHeight && content.bottom() == 620);
const Rect back = zoneBackRect(840, 620);
CHECK(back.right() == 840 - kPad && back.bottom() <= kTitleHeight);
const Rect addR = zoneAddRect(content);
const Rect delR = zoneDeleteRect(addR);
CHECK(addR.y == content.y + 4);
CHECK(delR.x == addR.right() + 8 && delR.y == addR.y);
const Rect strip = zonesStripArea(content);
CHECK(strip.y == addR.bottom() + 12);
CHECK(strip.x == content.x + kPad && strip.right() == content.right() - kPad);
const Rect fields = noteEntryFieldsArea(content);
CHECK(fields.y == strip.bottom() + 8);
const Rect f0 = noteEntryFieldRect(fields, 0);
const Rect f1 = noteEntryFieldRect(fields, 1);
const Rect f2 = noteEntryFieldRect(fields, 2);
CHECK(f0.x < f1.x && f1.x < f2.x);
CHECK(f2.right() == fields.right());
CHECK(noteEntryFieldRect(fields, 3).width == 0); // out-of-range -> empty
const Rect panel = zonesControlPanel(content);
const Rect deck = zonesDeckArea(content);
const Rect curve = zonesCurveButton(content);
CHECK(panel.y == strip.bottom() + 8 + 18 + 8);
CHECK(deck.y == panel.y && deck.right() < curve.x); // curve column reserved
CHECK(curve.right() == panel.right() && curve.y == panel.y);
}
int main() {
testContainsHalfOpen();
testContainsDegenerate();
@@ -332,6 +404,9 @@ int main() {
testZoneRowStacksAndSelects();
testZoneRowControlsMapToFields();
testZoneHitTestMisses();
testSampleBandsStackAndElasticHero();
testClusterRectsRunAndKnobCentering();
testZoneSurfaceLayoutAnchors();
if (g_fail == 0) std::printf("editor_geometry: all tests passed\n");
return g_fail != 0;
+2 -1
View File
@@ -7,7 +7,7 @@
// cross-artifact contract guard — the same pattern assignment_request_tests uses.
#include "../src/core/wire/instrument_drop.h"
#include "../src/core/instrument/map/sample_map.h" // deserializeComponentState — the instrument's OWN reader
#include "../src/core/instrument/map/component_state_io.h" // deserializeComponentState — the instrument's OWN reader
#include "version_generated.h" // REASAMPLER_CHANNEL_IS_BETA — pins the per-channel class ID
@@ -18,6 +18,7 @@
using namespace reasampler;
using namespace reasampler::wire;
using namespace reasampler::instrument::map; // ComponentState + the codec (Q-W2v re-namespace)
static int g_fail = 0;
#define CHECK(cond) do { if(!(cond)) { \
+3 -1
View File
@@ -23,6 +23,7 @@
// channel-count stride downmixToMono divides by).
#include "../src/core/instrument/map/sample_map.h"
#include "../src/core/instrument/map/component_state_io.h" // the Q-W2v codec split (formats FROZEN; suite unchanged)
#include <cmath>
#include <cstdio>
@@ -36,7 +37,8 @@
using namespace reasampler;
using namespace reasampler::instrument::engine;
using namespace reasampler::capture; // wav_trim (WavLayout) — sample_map re-exports live in reasampler until Q-W2v
using namespace reasampler::instrument::map; // sample_map + component_state_io (Q-W2v re-namespace)
using namespace reasampler::capture; // wav_trim (WavLayout)
using namespace reasampler::model;
static int g_fail = 0;
+52
View File
@@ -9,9 +9,12 @@
// reference-bound std::string — the Cursor never outlives its buffer.
#include "../src/core/wire/wire.h"
#include "../src/core/wire/bytes.h" // putLE / ByteReader — the ONE LE byte codec (Q-W2v, T4-20)
#include <cstdint>
#include <cstdio>
#include <string>
#include <vector>
using namespace reasampler;
using namespace reasampler::wire;
@@ -173,7 +176,56 @@ static void testParseUnsignedDecimal() {
CHECK(!wire::parseUnsignedDecimal(std::string(40, '9'), v)); // long run cannot wrap
}
// --- core/wire/bytes.h — the LE byte codec (Q-W2v, T4-20) --------------------
// putLE emits exactly the bytes the retired hand-rolled putU32le/putU64le emitted
// (LSB first, fixed width) — the FROZEN wire byte order.
static void testPutLEExactBytes() {
std::vector<std::uint8_t> out;
wire::putLE(out, static_cast<std::uint32_t>(0x0403'0201u));
CHECK(out.size() == 4);
CHECK(out[0] == 0x01 && out[1] == 0x02 && out[2] == 0x03 && out[3] == 0x04);
out.clear();
wire::putLE(out, static_cast<std::uint64_t>(0x0807'0605'0403'0201ull));
CHECK(out.size() == 8);
CHECK(out[0] == 0x01 && out[7] == 0x08);
}
// ByteReader round-trips putLE output, and the double bit-cast is lossless.
static void testByteReaderRoundTrip() {
std::vector<std::uint8_t> out;
wire::putLE(out, static_cast<std::uint32_t>(7));
wire::putLE(out, static_cast<std::uint64_t>(wire::doubleToBits(-2.5)));
wire::putLE(out, static_cast<std::uint8_t>(0xAB));
wire::putLE(out,
static_cast<std::uint64_t>(static_cast<std::int64_t>(-42))); // i64 image
wire::ByteReader r(out);
CHECK(r.peekU32() == 7);
CHECK(r.u32() == 7);
CHECK(wire::bitsToDouble(r.u64()) == -2.5);
CHECK(r.u8() == 0xAB);
CHECK(r.i64() == -42);
CHECK(r.ok);
}
// Truncation latches ok=false and every subsequent read yields zero/empty —
// the partial-parse contract every codec consumer leans on.
static void testByteReaderLatchesOnTruncation() {
std::vector<std::uint8_t> out;
wire::putLE(out, static_cast<std::uint32_t>(9));
out.pop_back(); // truncate mid-u32
wire::ByteReader r(out);
CHECK(r.u32() == 0);
CHECK(!r.ok);
CHECK(r.u8() == 0); // latched: even an in-bounds width now fails
CHECK(r.str(1).empty());
CHECK(r.peekU32() == 0);
}
int main() {
testPutLEExactBytes();
testByteReaderRoundTrip();
testByteReaderLatchesOnTruncation();
testPutFieldExactBytes();
testFieldRoundTripIncludingSeparators();
testLiteralMismatchFails();