Q-W1 pt2: core/shell/app relocation + sub-namespaces; one concrete ui::Rect (LTRB fork retired); slot_map split from bank_book; BankIndex→BankModel; 59/59 green
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
// Standalone tests for reasampler::vst::envelope_overlay — no VST3, no REAPER, no framework.
|
||||
// Standalone tests for reasampler::instrument::ui::envelope_overlay — no VST3, no REAPER, no framework.
|
||||
// Same fast assert loop as the sibling pure tests. Assert the S-VIEW-3/FA2 amp-envelope ->
|
||||
// polyline FORWARD map: the Gate BOUNDED-SCHEMATIC AHDSR shape (attack ramp / hold plateau /
|
||||
// decay-to-sustain / fixed-width sustain plateau / in-bounds release) and the Trigger
|
||||
@@ -14,12 +14,13 @@
|
||||
// the played span, overlap clamp, full-length/zero-fade-out nodes in-bounds at right-1);
|
||||
// degenerate flat baseline.
|
||||
|
||||
#include "../src/vst/envelope_overlay.h"
|
||||
#include "../src/core/instrument/ui/envelope_overlay.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <vector>
|
||||
|
||||
using namespace reasampler::vst;
|
||||
using namespace reasampler;
|
||||
using namespace reasampler::instrument::ui;
|
||||
|
||||
static int g_fail = 0;
|
||||
#define CHECK(cond) do { if(!(cond)) { \
|
||||
@@ -27,7 +28,7 @@ static int g_fail = 0;
|
||||
|
||||
// A comfortable overlay area: 1000px wide, 100px tall, offset so left/top != 0 (catches origin
|
||||
// bugs). Under levelToY the level span is height-1 = 99 rows.
|
||||
static Rect wideArea() { return Rect{20, 10, 1020, 110}; } // width 1000, height 100
|
||||
static Rect wideArea() { return Rect::ltrb(20, 10, 1020, 110); } // width 1000, height 100
|
||||
|
||||
// Find the first vertex with a given node in a polyline; asserts presence via the returned bool.
|
||||
static bool findNode(const std::vector<EnvVertex>& poly, EnvNode node, EnvVertex& out) {
|
||||
@@ -41,32 +42,32 @@ static bool findNode(const std::vector<EnvVertex>& poly, EnvNode node, EnvVertex
|
||||
|
||||
static void testTimeToXEndpoints() {
|
||||
const Rect a = wideArea();
|
||||
CHECK(timeToX(a, 2.0, 0.0) == a.left); // t=0 -> left
|
||||
CHECK(timeToX(a, 2.0, 2.0) == a.right - 1); // t=total -> last in-bounds column
|
||||
CHECK(timeToX(a, 2.0, 1.0) == a.left + 500); // midpoint
|
||||
CHECK(timeToX(a, 2.0, 0.0) == a.x); // t=0 -> left
|
||||
CHECK(timeToX(a, 2.0, 2.0) == a.right() - 1); // t=total -> last in-bounds column
|
||||
CHECK(timeToX(a, 2.0, 1.0) == a.x + 500); // midpoint
|
||||
}
|
||||
|
||||
static void testTimeToXNegativePinsLeft() {
|
||||
const Rect a = wideArea();
|
||||
CHECK(timeToX(a, 2.0, -0.5) == a.left); // t<0 pins left
|
||||
CHECK(timeToX(a, 2.0, -0.5) == a.x); // t<0 pins left
|
||||
}
|
||||
|
||||
static void testTimeToXPastEndClamps() {
|
||||
// FA2 bounds invariant: t past total pins to the last in-bounds column, never past right.
|
||||
const Rect a = wideArea();
|
||||
CHECK(timeToX(a, 2.0, 3.0) == a.right - 1);
|
||||
CHECK(timeToX(a, 2.0, 1000.0) == a.right - 1);
|
||||
CHECK(timeToX(a, 2.0, 3.0) == a.right() - 1);
|
||||
CHECK(timeToX(a, 2.0, 1000.0) == a.right() - 1);
|
||||
// A HUGE t must clamp in double space, not overflow the integer cast (32-bit long on
|
||||
// Windows would wrap to LONG_MIN and pin to the WRONG edge).
|
||||
CHECK(timeToX(a, 2.0, 1e15) == a.right - 1);
|
||||
CHECK(timeToX(a, 2.0, 1e15) == a.right() - 1);
|
||||
}
|
||||
|
||||
static void testGateTimedWidth() {
|
||||
// 15% of the 1000px canvas is reserved for the sustain plateau -> 850px timed region.
|
||||
CHECK(gateTimedWidth(wideArea()) == 850);
|
||||
// Zero-width area -> 0; a tiny area still yields >= 1 so the px<->s scale never degenerates.
|
||||
CHECK(gateTimedWidth(Rect{5, 5, 5, 45}) == 0);
|
||||
CHECK(gateTimedWidth(Rect{0, 0, 1, 10}) == 1);
|
||||
CHECK(gateTimedWidth(Rect::ltrb(5, 5, 5, 45)) == 0);
|
||||
CHECK(gateTimedWidth(Rect::ltrb(0, 0, 1, 10)) == 1);
|
||||
}
|
||||
|
||||
static void testGatePxPerSecond() {
|
||||
@@ -74,30 +75,30 @@ static void testGatePxPerSecond() {
|
||||
// 1000px canvas: (850 - 1 - 32) / 8.0s = 817/8 px/s. Independent of any sample duration.
|
||||
const double expected = 817.0 / (4.0 * kGateStageMaxSeconds);
|
||||
CHECK(gatePxPerSecond(wideArea()) == expected);
|
||||
CHECK(gatePxPerSecond(Rect{5, 5, 5, 45}) == 0.0); // zero-width area -> 0
|
||||
CHECK(gatePxPerSecond(Rect{0, 0, 10, 10}) > 0.0); // tiny area: usable floors at 1px, > 0
|
||||
CHECK(gatePxPerSecond(Rect::ltrb(5, 5, 5, 45)) == 0.0); // zero-width area -> 0
|
||||
CHECK(gatePxPerSecond(Rect::ltrb(0, 0, 10, 10)) > 0.0); // tiny area: usable floors at 1px, > 0
|
||||
}
|
||||
|
||||
static void testTimeToXDegenerate() {
|
||||
const Rect a = wideArea();
|
||||
CHECK(timeToX(a, 0.0, 1.0) == a.left); // no duration -> left
|
||||
const Rect z = Rect{5, 5, 5, 45}; // zero width
|
||||
CHECK(timeToX(z, 2.0, 1.0) == z.left);
|
||||
CHECK(timeToX(a, 0.0, 1.0) == a.x); // no duration -> left
|
||||
const Rect z = Rect::ltrb(5, 5, 5, 45); // zero width
|
||||
CHECK(timeToX(z, 2.0, 1.0) == z.x);
|
||||
}
|
||||
|
||||
static void testLevelToYEndpoints() {
|
||||
const Rect a = wideArea();
|
||||
CHECK(levelToY(a, 1.0) == a.top); // level 1 -> top row
|
||||
CHECK(levelToY(a, 0.0) == a.bottom - 1); // level 0 -> bottom row
|
||||
CHECK(levelToY(a, 0.5) == a.top + 50); // mid: round((1-0.5)*99)=round(49.5)=50
|
||||
CHECK(levelToY(a, 1.0) == a.y); // level 1 -> top row
|
||||
CHECK(levelToY(a, 0.0) == a.bottom() - 1); // level 0 -> bottom row
|
||||
CHECK(levelToY(a, 0.5) == a.y + 50); // mid: round((1-0.5)*99)=round(49.5)=50
|
||||
}
|
||||
|
||||
static void testLevelToYClamps() {
|
||||
const Rect a = wideArea();
|
||||
CHECK(levelToY(a, 2.0) == a.top); // >1 clamps to top
|
||||
CHECK(levelToY(a, -1.0) == a.bottom - 1); // <0 clamps to bottom
|
||||
const Rect z = Rect{5, 5, 45, 5}; // zero height
|
||||
CHECK(levelToY(z, 0.5) == z.top);
|
||||
CHECK(levelToY(a, 2.0) == a.y); // >1 clamps to top
|
||||
CHECK(levelToY(a, -1.0) == a.bottom() - 1); // <0 clamps to bottom
|
||||
const Rect z = Rect::ltrb(5, 5, 45, 5); // zero height
|
||||
CHECK(levelToY(z, 0.5) == z.y);
|
||||
}
|
||||
|
||||
// --- Gate polyline ------------------------------------------------------------
|
||||
@@ -149,11 +150,11 @@ static void testGateSchematicPlacement() {
|
||||
const std::vector<EnvVertex> poly = buildEnvelopePolyline(env, a, 2.0);
|
||||
|
||||
EnvVertex v;
|
||||
CHECK(findNode(poly, EnvNode::AttackEnd, v) && v.x == a.left + 28);
|
||||
CHECK(findNode(poly, EnvNode::HoldEnd, v) && v.x == a.left + 47);
|
||||
CHECK(findNode(poly, EnvNode::DecayEnd, v) && v.x == a.left + 85);
|
||||
CHECK(findNode(poly, EnvNode::ReleaseStart, v) && v.x == a.left + 235);
|
||||
CHECK(findNode(poly, EnvNode::ReleaseEnd, v) && v.x == a.left + 284);
|
||||
CHECK(findNode(poly, EnvNode::AttackEnd, v) && v.x == a.x + 28);
|
||||
CHECK(findNode(poly, EnvNode::HoldEnd, v) && v.x == a.x + 47);
|
||||
CHECK(findNode(poly, EnvNode::DecayEnd, v) && v.x == a.x + 85);
|
||||
CHECK(findNode(poly, EnvNode::ReleaseStart, v) && v.x == a.x + 235);
|
||||
CHECK(findNode(poly, EnvNode::ReleaseEnd, v) && v.x == a.x + 284);
|
||||
}
|
||||
|
||||
static void testGateLayoutIndependentOfSampleDuration() {
|
||||
@@ -195,7 +196,7 @@ static void testGateSustainPlateauFixedWidth() {
|
||||
env.sustainLevel = 0.6;
|
||||
env.releaseSeconds = 0.3;
|
||||
const Rect a = wideArea();
|
||||
const int plateauPx = a.width() - gateTimedWidth(a); // 150
|
||||
const int plateauPx = a.width - gateTimedWidth(a); // 150
|
||||
const std::vector<EnvVertex> poly = buildEnvelopePolyline(env, a, 2.0);
|
||||
|
||||
EnvVertex decay, plateauEnd;
|
||||
@@ -207,7 +208,7 @@ static void testGateSustainPlateauFixedWidth() {
|
||||
|
||||
static void testGateReleaseVisibleInBounds() {
|
||||
// The FA2 fix: Release is a VISIBLE, in-bounds segment — ReleaseEnd sits strictly right of
|
||||
// the plateau end and strictly inside the canvas (pre-FA2 it mapped past area.right and the
|
||||
// the plateau end and strictly inside the canvas (pre-FA2 it mapped past area.right() and the
|
||||
// shell clipped its handle away).
|
||||
AmpEnvelope env;
|
||||
env.mode = EnvMode::Gate;
|
||||
@@ -223,7 +224,7 @@ static void testGateReleaseVisibleInBounds() {
|
||||
CHECK(findNode(poly, EnvNode::ReleaseStart, plateauEnd));
|
||||
CHECK(findNode(poly, EnvNode::ReleaseEnd, rel));
|
||||
CHECK(rel.x > plateauEnd.x); // a visible ramp, not a collapsed point
|
||||
CHECK(rel.x < a.right); // strictly in-bounds
|
||||
CHECK(rel.x < a.right()); // strictly in-bounds
|
||||
CHECK(rel.level == 0.0);
|
||||
}
|
||||
|
||||
@@ -231,7 +232,7 @@ static void testGateOverrunCompressesFromRight() {
|
||||
// Stages BEYOND the schematic domain (4.0s each > kGateStageMaxSeconds): the layout
|
||||
// compresses from the right preserving the minimum gaps — ReleaseEnd pins to the last
|
||||
// in-bounds column, but the trailing nodes stay strictly increasing and individually
|
||||
// separated (>= kGateNodeSepPx), NOT piled on one pixel. NOTHING maps past area.right.
|
||||
// separated (>= kGateNodeSepPx), NOT piled on one pixel. NOTHING maps past area.right().
|
||||
AmpEnvelope env;
|
||||
env.mode = EnvMode::Gate;
|
||||
env.attackSeconds = 4.0;
|
||||
@@ -246,12 +247,12 @@ static void testGateOverrunCompressesFromRight() {
|
||||
EnvVertex plateauEnd, rel;
|
||||
CHECK(findNode(poly, EnvNode::ReleaseStart, plateauEnd));
|
||||
CHECK(findNode(poly, EnvNode::ReleaseEnd, rel));
|
||||
CHECK(rel.x == a.right - 1); // pinned to the last in-bounds column
|
||||
CHECK(rel.x == a.right() - 1); // pinned to the last in-bounds column
|
||||
CHECK(plateauEnd.level == 0.7); // still at sustain
|
||||
for (size_t i = 1; i < poly.size(); ++i) {
|
||||
CHECK(poly[i].x > poly[i - 1].x); // strictly monotonic
|
||||
CHECK(poly[i].x - poly[i - 1].x >= kGateNodeSepPx - 1); // min gaps survive compression
|
||||
CHECK(poly[i].x >= a.left && poly[i].x < a.right); // in-bounds
|
||||
CHECK(poly[i].x >= a.x && poly[i].x < a.right()); // in-bounds
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,8 +280,8 @@ static void testGateAllVerticesInBounds() {
|
||||
|
||||
for (const AmpEnvelope& env : {base, big, zero, trig, huge}) {
|
||||
for (const EnvVertex& v : buildEnvelopePolyline(env, a, 2.0)) {
|
||||
CHECK(v.x >= a.left && v.x < a.right);
|
||||
CHECK(v.y >= a.top && v.y < a.bottom);
|
||||
CHECK(v.x >= a.x && v.x < a.right());
|
||||
CHECK(v.y >= a.y && v.y < a.bottom());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -305,9 +306,9 @@ static void testTriggerShape() {
|
||||
CHECK(poly[3].node == EnvNode::LengthEnd);
|
||||
|
||||
EnvVertex v;
|
||||
CHECK(findNode(poly, EnvNode::FadeInEnd, v) && v.x == a.left + 100 && v.level == 1.0);
|
||||
CHECK(findNode(poly, EnvNode::FadeOutStart, v) && v.x == a.left + 350 && v.level == 1.0);
|
||||
CHECK(findNode(poly, EnvNode::LengthEnd, v) && v.x == a.left + 500 && v.level == 0.0);
|
||||
CHECK(findNode(poly, EnvNode::FadeInEnd, v) && v.x == a.x + 100 && v.level == 1.0);
|
||||
CHECK(findNode(poly, EnvNode::FadeOutStart, v) && v.x == a.x + 350 && v.level == 1.0);
|
||||
CHECK(findNode(poly, EnvNode::LengthEnd, v) && v.x == a.x + 500 && v.level == 0.0);
|
||||
}
|
||||
|
||||
static void testTriggerFadeOverlapClamp() {
|
||||
@@ -324,7 +325,7 @@ static void testTriggerFadeOverlapClamp() {
|
||||
CHECK(findNode(poly, EnvNode::FadeInEnd, fin));
|
||||
CHECK(findNode(poly, EnvNode::FadeOutStart, fout));
|
||||
CHECK(fin.x == fout.x); // fades meet exactly, never cross
|
||||
CHECK(fin.x == a.left + 800);
|
||||
CHECK(fin.x == a.x + 800);
|
||||
}
|
||||
|
||||
static void testTriggerFullLengthZeroFadeOutInBounds() {
|
||||
@@ -342,8 +343,8 @@ static void testTriggerFullLengthZeroFadeOutInBounds() {
|
||||
EnvVertex fout, lend;
|
||||
CHECK(findNode(poly, EnvNode::FadeOutStart, fout));
|
||||
CHECK(findNode(poly, EnvNode::LengthEnd, lend));
|
||||
CHECK(fout.x == a.right - 1); // present + in-bounds at zero fade-out
|
||||
CHECK(lend.x == a.right - 1);
|
||||
CHECK(fout.x == a.right() - 1); // present + in-bounds at zero fade-out
|
||||
CHECK(lend.x == a.right() - 1);
|
||||
CHECK(fout.level == 1.0 && lend.level == 0.0);
|
||||
}
|
||||
|
||||
@@ -351,7 +352,7 @@ static void testTriggerFullLengthZeroFadeOutInBounds() {
|
||||
|
||||
static void testDegenerateFlatBaseline() {
|
||||
AmpEnvelope env; // any params
|
||||
const Rect zeroW = Rect{0, 0, 0, 100};
|
||||
const Rect zeroW = Rect::ltrb(0, 0, 0, 100);
|
||||
const std::vector<EnvVertex> p1 = buildEnvelopePolyline(env, zeroW, 2.0);
|
||||
CHECK(p1.size() == 2); // always a drawable line
|
||||
CHECK(p1.front().level == 0.0 && p1.back().level == 0.0);
|
||||
@@ -360,7 +361,7 @@ static void testDegenerateFlatBaseline() {
|
||||
const std::vector<EnvVertex> p2 = buildEnvelopePolyline(env, ok, 0.0); // no duration
|
||||
CHECK(p2.size() == 2);
|
||||
CHECK(p2.front().level == 0.0 && p2.back().level == 0.0);
|
||||
CHECK(p2.front().x == ok.left && p2.back().x == ok.right - 1); // spans the area, in-bounds
|
||||
CHECK(p2.front().x == ok.x && p2.back().x == ok.right() - 1); // spans the area, in-bounds
|
||||
}
|
||||
|
||||
int main() {
|
||||
|
||||
Reference in New Issue
Block a user