Widen the deck row block to 1028 so the filter tie-line is exact, and accumulate the meter's block peaks instead of sampling one in 47
This commit is contained in:
+35
-25
@@ -12,6 +12,7 @@
|
||||
// and which selections are inert).
|
||||
|
||||
#include "../src/core/instrument/ui/deck_groups.h"
|
||||
#include "../src/core/instrument/ui/master_meter.h" // kMeterColumnW: MASTER's reserve IS this
|
||||
#include "../src/core/instrument/ui/sample_bands.h"
|
||||
|
||||
#include <cmath>
|
||||
@@ -310,12 +311,15 @@ static void testTheEditorFloorIsDerivedFromTheDeckWidthBudget() {
|
||||
CHECK(kDeckRowBlockW + kDeckGroupGap + kDeckSpanningW + 2 * kPad == kEditorMinWidth);
|
||||
// The budget: what is left between the derived floor and the hard ceiling, and it is spent
|
||||
// once. A cell costs 60 of it.
|
||||
CHECK(kEditorCeilingWidth - kEditorMinWidth == 90);
|
||||
CHECK(kEditorCeilingWidth - kEditorMinWidth == 82);
|
||||
// 82 still buys one more deck cell (60), which is the only purchase the ledger promises —
|
||||
// the widen below spent 8 px of slack, not the layout's purchasing power.
|
||||
CHECK(kEditorCeilingWidth - kEditorMinWidth >= kDeckCellW);
|
||||
// The reflow's 112 px goes entirely to the waveform, so the height does not move.
|
||||
CHECK(kEditorMinHeight == 680);
|
||||
// The floor did not move to make the reflow fit — the reflow was fitted to the floor. This
|
||||
// wave spends the budget it was handed; it does not widen it.
|
||||
CHECK(kEditorMinWidth == 1190);
|
||||
// 1190 + 8: the row block was widened 1020 -> 1028 to put the two rows' filter edges on
|
||||
// one pixel, which is the only reason the floor moved off Γ-W1-T4's number.
|
||||
CHECK(kEditorMinWidth == 1198);
|
||||
CHECK(kEditorMinWidth <= kEditorCeilingWidth);
|
||||
CHECK(kEditorMinHeight <= 720);
|
||||
// And the row block really is what the two rows justify inside — derived from the floor
|
||||
@@ -382,11 +386,11 @@ static void testBothRowsAndTheSpanningDeckFitTheBudget() {
|
||||
}
|
||||
|
||||
// The gutters the justification law produces at the floor, and the alignment they buy. The
|
||||
// SPEC (instrument-control-surface.md §1.2/§1.3) states row 1 as 12/14/14 with both filter
|
||||
// edges at x = 636; equal division of 40 px over three gutters cannot produce that, so what is
|
||||
// pinned here is what the LAW produces — 14/13/13, filter edge 638 — with row 2 exact at
|
||||
// 72/72 and 636. The 2 px is flagged for review; a row block of 1028 (floor 1198, still under
|
||||
// the 1280 ceiling) is the width at which the law puts both edges on 640.
|
||||
// At the 1028 block the justification law makes the tie-line exact by arithmetic rather than
|
||||
// by a special rule: row 1's slack is 48 over three gutters (16 each, no residue) and row 2's
|
||||
// is 152 over two (76 each), which lands both filter edges on 640. Only two of the three
|
||||
// properties §1.3 once claimed can hold at once — a smallest gutter of exactly kDeckGroupGap
|
||||
// needs a 1016 block — and 12 is a floor, not a target, so 16 satisfies the real rule.
|
||||
static void testGutterArithmeticAndTheFilterTieLineAtTheFloor() {
|
||||
const std::vector<DeckGroupDesc> g = sampleDeckGroups(PlayMode::Gate);
|
||||
const DeckLayout dl = layoutDeck(g, kPad, 0, kAvailAtMinWidth);
|
||||
@@ -394,24 +398,25 @@ static void testGutterArithmeticAndTheFilterTieLineAtTheFloor() {
|
||||
const auto box = [&](int id) {
|
||||
return dl.groups[static_cast<std::size_t>(indexOfGroup(g, id))].box;
|
||||
};
|
||||
// Row 1: flush left, flush right on the block, gutters 14/13/13.
|
||||
// Row 1: flush left, flush right on the block, and three EQUAL gutters — 48 divides by 3
|
||||
// with no residue, so no gutter carries a leftover pixel.
|
||||
CHECK(box(kGroupPitch).x == kPad);
|
||||
CHECK(box(kGroupFilter).x - box(kGroupPitch).right() == 14);
|
||||
CHECK(box(kGroupVelocity).x - box(kGroupFilter).right() == 13);
|
||||
CHECK(box(kGroupVoice).x - box(kGroupVelocity).right() == 13);
|
||||
CHECK(box(kGroupFilter).x - box(kGroupPitch).right() == 16);
|
||||
CHECK(box(kGroupVelocity).x - box(kGroupFilter).right() == 16);
|
||||
CHECK(box(kGroupVoice).x - box(kGroupVelocity).right() == 16);
|
||||
CHECK(box(kGroupVoice).right() == kPad + kDeckRowBlockW);
|
||||
|
||||
// Row 2: flush left, flush right, and its two gutters exactly equal — the property the
|
||||
// 1020 block was chosen for, and the one it does deliver.
|
||||
// Row 2: flush left, flush right, two gutters exactly equal.
|
||||
CHECK(box(kGroupPitchEnv).x == kPad);
|
||||
CHECK(box(kGroupFilterEnv).x - box(kGroupPitchEnv).right() == 72);
|
||||
CHECK(box(kGroupAmpEnv).x - box(kGroupFilterEnv).right() == 72);
|
||||
CHECK(box(kGroupFilterEnv).x - box(kGroupPitchEnv).right() == 76);
|
||||
CHECK(box(kGroupAmpEnv).x - box(kGroupFilterEnv).right() == 76);
|
||||
CHECK(box(kGroupAmpEnv).right() == kPad + kDeckRowBlockW);
|
||||
|
||||
// The filter tie-line, block-relative. Row 2 lands on the specified 636; row 1 lands 2 px
|
||||
// past it. See this test's header.
|
||||
CHECK(box(kGroupFilterEnv).right() - kPad == 636);
|
||||
CHECK(box(kGroupFilter).right() - kPad == 638);
|
||||
// The tie-line, block-relative: both filter edges on ONE pixel, which is what the widen
|
||||
// bought. Pinned as an identity too, so a group-width change cannot pass by moving both.
|
||||
CHECK(box(kGroupFilterEnv).right() - kPad == 640);
|
||||
CHECK(box(kGroupFilter).right() - kPad == 640);
|
||||
CHECK(box(kGroupFilter).right() == box(kGroupFilterEnv).right());
|
||||
|
||||
// MASTER is right-anchored outside the block, one kDeckGroupGap clear of it.
|
||||
CHECK(box(kGroupMaster).x - box(kGroupVoice).right() == kDeckGroupGap);
|
||||
@@ -450,8 +455,8 @@ static void testGuttersHoldTheirMinimumAndTheTieLineDriftsAboveTheFloor() {
|
||||
CHECK(drift <= lastDrift);
|
||||
lastDrift = drift;
|
||||
}
|
||||
// It really does open up, and by far more than the 2 px it starts at — separation
|
||||
// above the floor is the accepted outcome, not a near-miss to be pinned back.
|
||||
// It really does open up: the tie-line is exact AT the floor and separates above it,
|
||||
// which is the accepted outcome rather than a near-miss to be pinned back.
|
||||
CHECK(lastDrift < -50);
|
||||
}
|
||||
}
|
||||
@@ -467,8 +472,13 @@ static void testTheMasterDeckInteriorLandsOnBothRowBaselines() {
|
||||
|
||||
CHECK(m.box.width == 142);
|
||||
CHECK(m.box.height == 216);
|
||||
// 6 + 60 + 8 + 62 + 6 — the decomposition, not just the total.
|
||||
CHECK(kDeckGroupPadX + kDeckCellW + kDeckColumnGap + 62 + kDeckGroupPadX == 142);
|
||||
// 6 + 60 + 8 + 62 + 6 — the decomposition, not just the total, and the 62 is the meter
|
||||
// module's own kMeterColumnW rather than a copy of it. That link is the whole point: the
|
||||
// column is banked to GROW (§1.2), and a reserve that did not track it would leave the
|
||||
// interior underfilling or overrunning with every test still green.
|
||||
CHECK(kDeckGroupPadX + kDeckCellW + kDeckColumnGap + kMeterColumnW + kDeckGroupPadX == 142);
|
||||
CHECK(m.column.id == cell(DeckParam::kMasterMeter));
|
||||
CHECK(m.column.box.width == kMeterColumnW);
|
||||
|
||||
// One cell drawn (gain) and one slot RESERVED below it: the reserve is height at a fixed
|
||||
// position and draws nothing.
|
||||
|
||||
@@ -306,7 +306,7 @@ static void testGutterAtTheSawtoothMaximumResidueWidth() {
|
||||
// nor the floor is covered by another test firing if either ever changes. Derived from
|
||||
// kEditorMinWidth/kEditorMinHeight (editor_session.cpp's ViewRect default IS the floor) rather
|
||||
// than a hardcoded window size, so a floor change fails HERE instead of silently moving the
|
||||
// shipped gutter out from under it. The numbers below are today's floor (1190x680); re-derive
|
||||
// shipped gutter out from under it. The numbers below are today's floor (1198x680); re-derive
|
||||
// them by hand if the floor ever moves.
|
||||
static void testGutterAtTheShippedDefaultWindowSize() {
|
||||
// Derive rootStrip's width the same way the shell does, through the real allocator +
|
||||
@@ -315,14 +315,14 @@ static void testGutterAtTheShippedDefaultWindowSize() {
|
||||
const ChromeRects chrome = chromeRects(bands.chrome, /*knobSize=*/24);
|
||||
const int stripW = chrome.rootStrip.width;
|
||||
CHECK(stripW == kEditorMinWidth - 2 * kPad);
|
||||
CHECK(stripW == 1174);
|
||||
CHECK(stripW == 1182);
|
||||
|
||||
const StripLayout L = layoutStrip(stripW, 30);
|
||||
CHECK(L.whiteWidth == 15);
|
||||
const int margins = L.band.width - L.keys.width;
|
||||
CHECK(margins == 49);
|
||||
CHECK(margins == 57);
|
||||
const int leftMargin = L.keys.x - L.band.x;
|
||||
CHECK(leftMargin == 24);
|
||||
CHECK(leftMargin == 28);
|
||||
}
|
||||
|
||||
int main() {
|
||||
|
||||
+124
-22
@@ -1,14 +1,15 @@
|
||||
// Standalone tests for reasampler::instrument::ui::master_meter — no VST3, no REAPER, no
|
||||
// framework. Assert:
|
||||
//
|
||||
// * column interior — the 22/4/36 decomposition, the mono bar taking the whole field, the
|
||||
// two stereo bars at 17 px and kMeterBarGap apart, all inside the column.
|
||||
// * bar count — the SAME LaneSplit waveformSurface folds, over channel mode x source
|
||||
// * column interior — the 22/4/36 decomposition, the exported column width the deck reserves,
|
||||
// the mono bar taking the whole field, the two stereo bars, all inside the column.
|
||||
// * bar count — the SAME LaneSplit resolveLaneSplit folds, over channel mode x source
|
||||
// channel count, so it can never become a second rule.
|
||||
// * the dB axis — top/floor land on the field's edges, it is monotone, and it clamps.
|
||||
// * ballistics — instantaneous rise, 20 dB/s fall, the 1.5 s hold and its release; the
|
||||
// audio thread's clip latch surviving a UI frame that never sampled the loud block.
|
||||
// * the GR lamp — lit only while the limiter actually reduces, and decaying afterwards.
|
||||
// * the dB axis — top/floor on the field's edges, an interior value, and the clamps.
|
||||
// * ballistics — instantaneous rise, 20 dB/s fall, the 1.5 s hold and its release AT RATE;
|
||||
// the audio thread's clip latch surviving a UI frame; the per-field single-lane fold.
|
||||
// * the GR lamp — lit only while the limiter reduces, held, and surviving the 500 ms tick the
|
||||
// editor actually runs it at.
|
||||
|
||||
#include "../src/core/instrument/ui/master_meter.h"
|
||||
#include "../src/core/instrument/ui/waveform_view.h"
|
||||
@@ -37,8 +38,11 @@ static void testColumnDividesIntoGutterAndBarField() {
|
||||
CHECK(m.field.x == m.labels.right() + kMeterLabelGap);
|
||||
CHECK(m.field.width == kMeterFieldW);
|
||||
// The three parts account for the column exactly — a residue would leave dead pixels the
|
||||
// scale's numerals would then be centred against.
|
||||
CHECK(kMeterLabelW + kMeterLabelGap + kMeterFieldW == kColumn.width);
|
||||
// scale's numerals would then be centred against. Asserted against the EXPORTED width the
|
||||
// deck reserves, not against this fixture's literal rect: the deck reading the same
|
||||
// constant is what keeps the reserve and the interior from drifting apart.
|
||||
CHECK(kMeterLabelW + kMeterLabelGap + kMeterFieldW == kMeterColumnW);
|
||||
CHECK(kMeterColumnW == kColumn.width);
|
||||
CHECK(m.field.right() == kColumn.right());
|
||||
// Full height in both rects: the column spans both row baselines as ONE readout.
|
||||
CHECK(m.labels.y == kColumn.y && m.labels.bottom() == kColumn.bottom());
|
||||
@@ -62,18 +66,19 @@ static void testMonoDrawsOneWideBarAndStereoDrawsTwo() {
|
||||
CHECK(st.barA.y == st.field.y && st.barB.bottom() == st.field.bottom());
|
||||
}
|
||||
|
||||
// The bar count is NOT a second rule: it is whatever waveformSurface resolved for the same
|
||||
// (mode, source) pair. A mono source under stereo mode is dual-mono — one source, two views.
|
||||
// The bar count is NOT a second rule: it is resolveLaneSplit's answer for the same (mode,
|
||||
// source) pair the waveform asks about. A mono source under stereo mode is dual-mono — one
|
||||
// source, two views.
|
||||
static void testBarCountFollowsTheWaveformsOwnLaneSplit() {
|
||||
const Rect band = Rect::ltrb(8, 100, 1182, 458);
|
||||
for (bool stereoMode : {false, true}) {
|
||||
for (int sourceChannels : {1, 2}) {
|
||||
const WaveformSurface s = waveformSurface(band, stereoMode, sourceChannels);
|
||||
const LaneSplit split =
|
||||
s.laneCount == 2 ? LaneSplit::Stereo : LaneSplit::Single;
|
||||
const LaneSplit split = resolveLaneSplit(stereoMode, sourceChannels);
|
||||
const MeterRects m = meterRects(kColumn, split);
|
||||
const int bars = m.barB.empty() ? 1 : 2;
|
||||
CHECK(bars == s.laneCount);
|
||||
// The waveform's own surface folds the SAME call, so on a band tall enough to
|
||||
// divide the two answers agree by construction rather than by coincidence.
|
||||
CHECK(bars == waveformSurface(band, stereoMode, sourceChannels).laneCount);
|
||||
// Spelled out per combination so a regression names which one broke.
|
||||
const bool expectTwo = stereoMode && sourceChannels >= 2;
|
||||
CHECK(bars == (expectTwo ? 2 : 1));
|
||||
@@ -95,6 +100,49 @@ static void testDbAxisSpansTheFieldAndClamps() {
|
||||
// Clamped outside the scale rather than drawn off the field.
|
||||
CHECK(meterDbToY(m.field, kMeterTopDb + 40.0) == m.field.y);
|
||||
CHECK(meterDbToY(m.field, kMeterFloorDb - 40.0) == m.field.bottom());
|
||||
|
||||
// One INTERIOR point, because endpoints plus monotonicity are satisfied by any log or
|
||||
// piecewise map through them, and the scale is specified LINEAR in dB. −27 is the
|
||||
// midpoint of −60…+6, so it must land on the field's own midpoint: 186 x 0.5 = 93.
|
||||
CHECK(meterDbToY(m.field, -27.0) == m.field.bottom() - 93);
|
||||
// And a quarter of the way up, which fixes the slope rather than just the centre.
|
||||
CHECK(meterDbToY(m.field, -43.5) == m.field.bottom() - 47); // round(0.25 x 186) = 47
|
||||
}
|
||||
|
||||
// The numeral SET is spec-pinned (0, −12, −24, −36, −48, −60) as a property of the scale, so it
|
||||
// is asserted here rather than left as a modulo inside the painter.
|
||||
static void testEveryOtherTickCarriesANumeral() {
|
||||
const int expected[] = {6, -6, -18, -30, -42, -54};
|
||||
for (int db : expected) CHECK(!meterTickNumeralled(db));
|
||||
const int numeralled[] = {0, -12, -24, -36, -48, -60};
|
||||
for (int db : numeralled) CHECK(meterTickNumeralled(db));
|
||||
}
|
||||
|
||||
// The floor tick sits ON the field's bottom edge, so an unclamped y±5 numeral box hangs below
|
||||
// the column and into the deck's bottom padding.
|
||||
static void testTheFloorNumeralStaysInsideTheGutter() {
|
||||
const MeterRects m = meterRects(kColumn, LaneSplit::Single);
|
||||
const Rect floorLabel = meterNumeralRect(m.labels, meterDbToY(m.field, kMeterFloorDb));
|
||||
CHECK(floorLabel.bottom() <= m.labels.bottom());
|
||||
CHECK(floorLabel.y >= m.labels.y);
|
||||
CHECK(floorLabel.height == 10); // clamped, not squashed — the numeral still has its band
|
||||
const Rect topLabel = meterNumeralRect(m.labels, meterDbToY(m.field, kMeterTopDb));
|
||||
CHECK(topLabel.y >= m.labels.y);
|
||||
CHECK(topLabel.height == 10);
|
||||
// An interior tick is centred on its rule, which is the case the clamp must not disturb.
|
||||
const int midY = meterDbToY(m.field, -24.0);
|
||||
CHECK(meterNumeralRect(m.labels, midY).y == midY - 5);
|
||||
}
|
||||
|
||||
// A column narrower than the interior needs yields NOTHING rather than a field overrunning it.
|
||||
// Reachable only if the deck's reserve and this module's interior ever disagree — which is
|
||||
// exactly what kMeterColumnW exists to prevent.
|
||||
static void testAColumnTooNarrowForTheInteriorDrawsNothing() {
|
||||
const Rect narrow = Rect::ltrb(0, 0, kMeterColumnW - 1, 186);
|
||||
const MeterRects m = meterRects(narrow, LaneSplit::Stereo);
|
||||
CHECK(m.field.empty() && m.barA.empty() && m.barB.empty());
|
||||
// Exactly the needed width still lays out.
|
||||
CHECK(!meterRects(Rect::ltrb(0, 0, kMeterColumnW, 186), LaneSplit::Stereo).field.empty());
|
||||
}
|
||||
|
||||
static void testPeakRisesAtOnceAndFallsAtTwentyDbPerSecond() {
|
||||
@@ -120,9 +168,12 @@ static void testPeakHoldSitsForItsFullWindowThenReleases() {
|
||||
CHECK(s.left.levelDb < held - 20.0);
|
||||
CHECK(std::fabs(s.left.holdDb - held) < 1e-9);
|
||||
|
||||
// Past it, the tick releases at the same 20 dB/s the bar uses.
|
||||
// Past it, the tick releases at the SAME 20 dB/s the bar uses — pinned by value, not as an
|
||||
// inequality: a slower release would satisfy "it fell" and still be the wrong meter. The
|
||||
// frame spends the 0.01 s of hold it had left and releases for the remaining 0.49 s, which
|
||||
// is also what proves the release does not quantize to whole UI frames.
|
||||
s = advanceMasterMeter(s, {0.0, 0.0, 1.0, false}, 0.5);
|
||||
CHECK(s.left.holdDb < held);
|
||||
CHECK(std::fabs(s.left.holdDb - (held - kMeterFallDbPerSecond * 0.49)) < 1e-9);
|
||||
CHECK(s.left.holdDb >= s.left.levelDb);
|
||||
}
|
||||
|
||||
@@ -151,21 +202,67 @@ static void testGrLampLitOnlyWhileTheLimiterReduces() {
|
||||
CHECK(s.reductionDb == 0.0);
|
||||
CHECK(!grLampLit(s));
|
||||
|
||||
// ~6 dB of reduction lights it.
|
||||
// ~6 dB of reduction lights it, and arms the hold.
|
||||
s = advanceMasterMeter(s, {0.5, 0.5, 0.5, false}, 0.1);
|
||||
CHECK(std::fabs(s.reductionDb - 6.0206) < 1e-3);
|
||||
CHECK(grLampLit(s));
|
||||
CHECK(s.reductionHoldSeconds == kMeterPeakHoldSeconds);
|
||||
|
||||
// It decays at the meter's own rate rather than snapping dark, so a transient catch is
|
||||
// visible for more than the single frame it happened on.
|
||||
s = advanceMasterMeter(s, {0.5, 0.5, 1.0, false}, 0.1);
|
||||
// Held flat, not decaying, for its whole window — the peak tick's own contract.
|
||||
s = advanceMasterMeter(s, {0.5, 0.5, 1.0, false}, kMeterPeakHoldSeconds - 0.01);
|
||||
CHECK(std::fabs(s.reductionDb - 6.0206) < 1e-3);
|
||||
CHECK(grLampLit(s));
|
||||
CHECK(s.reductionDb < 6.0206);
|
||||
|
||||
// Past the window it releases at the meter's 20 dB/s, and 6 dB of catch is gone inside a
|
||||
// third of a second of release.
|
||||
s = advanceMasterMeter(s, {0.5, 0.5, 1.0, false}, 1.0);
|
||||
CHECK(!grLampLit(s));
|
||||
CHECK(s.reductionDb == 0.0);
|
||||
}
|
||||
|
||||
// The cadence the lamp ACTUALLY runs at is editor_platform's 500 ms sync tick, and the whole
|
||||
// point of the hold is that the lamp survives it. Without one, a 6 dB catch decays 20 x 0.5 =
|
||||
// 10 dB on the very next frame and clamps to 0 — lit for exactly one repaint. Pinned in frames,
|
||||
// because "how many times does this draw lit" is arithmetic, not a look.
|
||||
static void testGrLampSurvivesTheFiveHundredMillisecondTick() {
|
||||
constexpr double kTick = 0.5; // editor_platform.cpp's kSyncTimerIntervalMs
|
||||
MasterMeterUi s = advanceMasterMeter(MasterMeterUi{}, {0.5, 0.5, 0.5, false}, kTick);
|
||||
CHECK(grLampLit(s));
|
||||
|
||||
int litFrames = 1;
|
||||
for (int i = 0; i < 20 && grLampLit(s); ++i) {
|
||||
s = advanceMasterMeter(s, {0.5, 0.5, 1.0, false}, kTick);
|
||||
if (grLampLit(s)) ++litFrames;
|
||||
}
|
||||
// 1.5 s of hold spans the tick that armed it plus three more, and the release then takes
|
||||
// 6.02 dB below the 0.5 dB floor within one further 10 dB step.
|
||||
CHECK(litFrames == 4);
|
||||
CHECK(!grLampLit(s));
|
||||
|
||||
// A catch the previous frame does not shorten: a SECOND catch re-arms the full window.
|
||||
MasterMeterUi t = advanceMasterMeter(MasterMeterUi{}, {0.5, 0.5, 0.5, false}, kTick);
|
||||
t = advanceMasterMeter(t, {0.5, 0.5, 1.0, false}, kTick);
|
||||
t = advanceMasterMeter(t, {0.5, 0.5, 0.5, false}, kTick);
|
||||
CHECK(t.reductionHoldSeconds == kMeterPeakHoldSeconds);
|
||||
}
|
||||
|
||||
// The one bar a single-lane column draws folds the two channels per FIELD. Picking whichever
|
||||
// channel won on level would draw the OTHER channel's hold tick and clip nowhere.
|
||||
static void testSingleLaneStateFoldsBothChannelsPerField() {
|
||||
MasterMeterUi m;
|
||||
m.left.levelDb = -30.0;
|
||||
m.left.holdDb = -2.0; // left is quieter now but held the loudest peak
|
||||
m.right.levelDb = -10.0;
|
||||
m.right.holdDb = -8.0;
|
||||
m.left.clip = true; // and only left ever clipped
|
||||
m.right.clip = false;
|
||||
|
||||
const instrument::engine::MeterState s = meterSingleLaneState(m);
|
||||
CHECK(s.levelDb == -10.0); // the louder channel's bar
|
||||
CHECK(s.holdDb == -2.0); // but the higher hold tick, which is the other channel's
|
||||
CHECK(s.clip); // and the clip, which a level pick would have dropped
|
||||
}
|
||||
|
||||
// The tick repaints only on a change, so what counts as a change has to cover every drawn
|
||||
// quantity — and only those.
|
||||
static void testDrawEqualityCoversTheDrawnQuantities() {
|
||||
@@ -200,10 +297,15 @@ int main() {
|
||||
testMonoDrawsOneWideBarAndStereoDrawsTwo();
|
||||
testBarCountFollowsTheWaveformsOwnLaneSplit();
|
||||
testDbAxisSpansTheFieldAndClamps();
|
||||
testEveryOtherTickCarriesANumeral();
|
||||
testTheFloorNumeralStaysInsideTheGutter();
|
||||
testAColumnTooNarrowForTheInteriorDrawsNothing();
|
||||
testPeakRisesAtOnceAndFallsAtTwentyDbPerSecond();
|
||||
testPeakHoldSitsForItsFullWindowThenReleases();
|
||||
testClipLatchesFromThePublishedFlagAndClearsOnDemand();
|
||||
testSingleLaneStateFoldsBothChannelsPerField();
|
||||
testGrLampLitOnlyWhileTheLimiterReduces();
|
||||
testGrLampSurvivesTheFiveHundredMillisecondTick();
|
||||
testDrawEqualityCoversTheDrawnQuantities();
|
||||
testDegenerateColumnYieldsNothing();
|
||||
if (g_fail) {
|
||||
|
||||
Reference in New Issue
Block a user