Γ-W1-T2: the master bus — a true-peak limiter whose ceiling is a theorem, the meter's published half, and the plugin's first PDC report
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
// Standalone tests for reasampler::instrument::engine::meter_ballistics — no VST3, no REAPER,
|
||||
// no framework. The meter's whole behaviour is asserted here without a host: instantaneous
|
||||
// rise, the 20 dB/s fall, the 1.5 s peak hold and its release at the same rate, the clip
|
||||
// latch and its clear, and the dB -> normalized map the bar is drawn against.
|
||||
|
||||
#include "../src/core/instrument/engine/meter_ballistics.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
|
||||
using namespace reasampler::instrument::engine;
|
||||
|
||||
static int g_fail = 0;
|
||||
#define CHECK(cond) do { if(!(cond)) { \
|
||||
std::printf("FAIL line %d: %s\n", __LINE__, #cond); ++g_fail; } } while(0)
|
||||
|
||||
static bool near(double a, double b, double eps = 1e-9) { return std::fabs(a - b) <= eps; }
|
||||
|
||||
static double linearFromDb(double db) { return std::pow(10.0, db / 20.0); }
|
||||
|
||||
static void testDbFromLinear() {
|
||||
CHECK(near(meterDbFromLinear(1.0), 0.0));
|
||||
CHECK(near(meterDbFromLinear(0.5), -6.0205999132796239, 1e-9));
|
||||
CHECK(near(meterDbFromLinear(2.0), 6.0205999132796239, 1e-9));
|
||||
// Silence and anything under the floor read the floor, not -inf: the ballistics subtract
|
||||
// from this value, so it has to stay finite.
|
||||
CHECK(meterDbFromLinear(0.0) == kMeterFloorDb);
|
||||
CHECK(meterDbFromLinear(-1.0) == kMeterFloorDb);
|
||||
CHECK(meterDbFromLinear(1e-9) == kMeterFloorDb);
|
||||
}
|
||||
|
||||
static void testNormFromDbIsLinearInDbAndClamped() {
|
||||
CHECK(meterNormFromDb(kMeterFloorDb) == 0.0);
|
||||
CHECK(meterNormFromDb(kMeterTopDb) == 1.0);
|
||||
CHECK(meterNormFromDb(-1000.0) == 0.0);
|
||||
CHECK(meterNormFromDb(1000.0) == 1.0);
|
||||
// Linear in dB: equal dB steps are equal normalized steps anywhere in the span.
|
||||
const double a = meterNormFromDb(-48.0) - meterNormFromDb(-54.0);
|
||||
const double b = meterNormFromDb(-6.0) - meterNormFromDb(-12.0);
|
||||
CHECK(near(a, b, 1e-12));
|
||||
CHECK(near(a, 6.0 / (kMeterTopDb - kMeterFloorDb), 1e-12));
|
||||
// The 0 dB tick, which the scale's heavier rule is drawn on.
|
||||
CHECK(near(meterNormFromDb(0.0), 60.0 / 66.0, 1e-12));
|
||||
}
|
||||
|
||||
static void testRiseIsInstantaneous() {
|
||||
MeterState s;
|
||||
s = advanceMeter(s, linearFromDb(-12.0), 1.0 / 30.0);
|
||||
CHECK(near(s.levelDb, -12.0, 1e-9));
|
||||
// A louder block on the very next frame displays at once, however short the frame.
|
||||
s = advanceMeter(s, linearFromDb(-3.0), 1e-6);
|
||||
CHECK(near(s.levelDb, -3.0, 1e-9));
|
||||
}
|
||||
|
||||
static void testFallIsTwentyDbPerSecond() {
|
||||
MeterState s;
|
||||
s = advanceMeter(s, 1.0, 0.0); // 0 dBFS
|
||||
CHECK(near(s.levelDb, 0.0, 1e-9));
|
||||
s = advanceMeter(s, 0.0, 0.5);
|
||||
CHECK(near(s.levelDb, -10.0, 1e-9));
|
||||
s = advanceMeter(s, 0.0, 0.25);
|
||||
CHECK(near(s.levelDb, -15.0, 1e-9));
|
||||
// The fall never takes the bar under the peak the block itself carried.
|
||||
s = advanceMeter(s, linearFromDb(-14.0), 1.0);
|
||||
CHECK(near(s.levelDb, -14.0, 1e-9));
|
||||
// And it stops at the floor.
|
||||
for (int i = 0; i < 20; ++i) s = advanceMeter(s, 0.0, 0.5);
|
||||
CHECK(near(s.levelDb, kMeterFloorDb, 1e-9));
|
||||
}
|
||||
|
||||
static void testPeakHoldLatchesForOnePointFiveSecondsThenFallsAtTheSameRate() {
|
||||
MeterState s;
|
||||
s = advanceMeter(s, 1.0, 0.0);
|
||||
CHECK(near(s.holdDb, 0.0, 1e-9));
|
||||
CHECK(near(s.holdRemainingSeconds, kMeterPeakHoldSeconds, 1e-12));
|
||||
|
||||
// 1.4 s of silence: the bar has long fallen away, the tick has not moved.
|
||||
for (int i = 0; i < 14; ++i) s = advanceMeter(s, 0.0, 0.1);
|
||||
CHECK(near(s.holdDb, 0.0, 1e-9));
|
||||
CHECK(s.levelDb < -20.0);
|
||||
|
||||
// Past 1.5 s it releases at the bar's own rate — 0.2 s past the latch is 4 dB down.
|
||||
s = advanceMeter(s, 0.0, 0.3);
|
||||
CHECK(near(s.holdDb, -4.0, 1e-9));
|
||||
s = advanceMeter(s, 0.0, 0.1);
|
||||
CHECK(near(s.holdDb, -6.0, 1e-9));
|
||||
|
||||
// A new peak re-latches it and restarts the hold.
|
||||
s = advanceMeter(s, linearFromDb(-2.0), 0.1);
|
||||
CHECK(near(s.holdDb, -2.0, 1e-9));
|
||||
CHECK(near(s.holdRemainingSeconds, kMeterPeakHoldSeconds, 1e-12));
|
||||
}
|
||||
|
||||
static void testHoldNeverFallsBelowTheBar() {
|
||||
MeterState s;
|
||||
s = advanceMeter(s, 1.0, 0.0);
|
||||
for (int i = 0; i < 40; ++i) s = advanceMeter(s, linearFromDb(-20.0), 0.1);
|
||||
CHECK(near(s.levelDb, -20.0, 1e-9));
|
||||
CHECK(near(s.holdDb, -20.0, 1e-9));
|
||||
}
|
||||
|
||||
static void testClipLatchesAtFullScaleAndOnlyClearsOnRequest() {
|
||||
MeterState s;
|
||||
s = advanceMeter(s, linearFromDb(-0.01), 0.1);
|
||||
CHECK(!s.clip); // under 0 dBFS does not latch
|
||||
s = advanceMeter(s, 1.0, 0.1);
|
||||
CHECK(s.clip); // exactly 0 dBFS does
|
||||
for (int i = 0; i < 100; ++i) s = advanceMeter(s, 0.0, 0.1);
|
||||
CHECK(s.clip); // and silence does not unlatch it
|
||||
s = clearMeterClip(s);
|
||||
CHECK(!s.clip);
|
||||
s = advanceMeter(s, linearFromDb(-6.0), 0.1);
|
||||
CHECK(!s.clip); // cleared stays cleared while nothing reaches full scale
|
||||
CHECK(near(s.levelDb, -6.0, 1e-9)); // and clearing left the ballistics alone
|
||||
}
|
||||
|
||||
static void testNonPositiveElapsedFreezesTheBallistics() {
|
||||
MeterState s;
|
||||
s = advanceMeter(s, 1.0, 0.0);
|
||||
const MeterState frozen = advanceMeter(s, 0.0, -1.0);
|
||||
CHECK(near(frozen.levelDb, s.levelDb, 1e-12));
|
||||
CHECK(near(frozen.holdDb, s.holdDb, 1e-12));
|
||||
}
|
||||
|
||||
int main() {
|
||||
testDbFromLinear();
|
||||
testNormFromDbIsLinearInDbAndClamped();
|
||||
testRiseIsInstantaneous();
|
||||
testFallIsTwentyDbPerSecond();
|
||||
testPeakHoldLatchesForOnePointFiveSecondsThenFallsAtTheSameRate();
|
||||
testHoldNeverFallsBelowTheBar();
|
||||
testClipLatchesAtFullScaleAndOnlyClearsOnRequest();
|
||||
testNonPositiveElapsedFreezesTheBallistics();
|
||||
if (g_fail) {
|
||||
std::printf("%d FAILURE(S)\n", g_fail);
|
||||
return 1;
|
||||
}
|
||||
std::printf("meter_ballistics tests passed\n");
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user