// Standalone tests for reasampler::instrument::engine::Limiter — no VST3, no REAPER, no // framework. The properties the master bus depends on, asserted rather than judged by ear: // // * bypassed and settled, process() does not touch one byte of the buffers (the byte-identical // at-rest path) and reports no reduction; // * engaged below the ceiling, the output is the input DELAYED and bit-exact — nothing is // louder, quieter or altered at rest, and there is no makeup gain to find; // * engaged on program +12 dB over, no output sample passes the ceiling; bypassed, the same // program still passes 0 dBFS, so the toggle is doing the work; // * the detection is TRUE-peak: a signal whose SAMPLES all clear the ceiling but whose // inter-sample peak does not still engages; // * the gain is stereo-linked, so a dual-mono signal stays centered across a full toggle; // * the engage/disengage crossfade leaves no step larger than the signal's own. #include "../src/core/instrument/engine/limiter.h" #include #include #include #include #include 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 constexpr double kRate = 48000.0; // A deterministic non-repeating pattern, so an untouched-buffer check cannot pass by accident. static std::vector pattern(int n, float scale = 1.f) { std::vector v(static_cast(n)); std::uint32_t s = 0x1234567u; for (int i = 0; i < n; ++i) { s = s * 1664525u + 1013904223u; v[static_cast(i)] = scale * (static_cast(static_cast(s >> 8) % 20001 - 10000) / 10000.f); } return v; } // Runs `in` through `lim` in blocks of `block`, returning the output and the smallest gain // reported across the whole run. static std::vector runMono(Limiter& lim, const std::vector& in, int block, float* minGainOut = nullptr) { std::vector out = in; float lowest = 1.f; for (std::size_t i = 0; i < out.size(); i += static_cast(block)) { const int n = static_cast( std::min(static_cast(block), out.size() - i)); const float g = lim.process(out.data() + i, nullptr, n); if (g < lowest) lowest = g; } if (minGainOut) *minGainOut = lowest; return out; } static void testBypassedLeavesEveryByteUntouched() { Limiter lim; lim.prepare(kRate); CHECK(!lim.enabled()); const std::vector in = pattern(2048, 1.8f); // well over full scale float minGain = 0.f; const std::vector out = runMono(lim, in, 512, &minGain); bool identical = true; for (std::size_t i = 0; i < in.size(); ++i) { if (out[i] != in[i]) { identical = false; break; } } CHECK(identical); CHECK(minGain == 1.f); // And that untouched signal still passes 0 dBFS — the toggle, not the meter, is what // stops it. float peak = 0.f; for (float v : out) peak = std::max(peak, std::fabs(v)); CHECK(peak > 1.f); } static void testEngagedBelowThresholdIsTheInputDelayedBitExactly() { Limiter lim; lim.setEnabled(true); lim.prepare(kRate); // prepare snaps to the target: no crossfade, no priming const int latency = limiterLookaheadSamples(kRate); // Comfortably under the ceiling at every sample AND between samples. const std::vector in = pattern(4096, 0.4f); float minGain = 0.f; const std::vector out = runMono(lim, in, 256, &minGain); CHECK(minGain == 1.f); // exactly unity: there is no makeup gain and no residual trim bool exact = true; for (std::size_t i = static_cast(latency); i < in.size(); ++i) { if (out[i] != in[i - static_cast(latency)]) { exact = false; break; } } CHECK(exact); } static void testEngagedHoldsTheCeilingOnProgramTwelveDbOver() { Limiter lim; lim.setEnabled(true); lim.prepare(kRate); const int latency = limiterLookaheadSamples(kRate); const float ceiling = static_cast(limiterCeilingLinear()); // +12 dB over the ceiling, sustained, with the transient content the pattern gives. std::vector in = pattern(24000, ceiling * 3.98f); float minGain = 0.f; const std::vector out = runMono(lim, in, 128, &minGain); CHECK(minGain < 0.4f); // it really did pull the gain down float worst = 0.f; for (std::size_t i = static_cast(latency); i < out.size(); ++i) { worst = std::max(worst, std::fabs(out[i])); } // Sample peak, so the true-peak ceiling is the bound with room to spare for float rounding // (ceiling/peak then x*gain admits at most ~2.4e-7 relative overshoot; 1e-6 stays a hard // bound without hiding a systematic error the way a much wider tolerance would). CHECK(worst <= ceiling * (1.f + 1e-6f)); } static void testTruePeakDetectionEngagesWhereSamplePeakWouldNot() { // fs/4 at 45 degrees: every SAMPLE sits at A/sqrt(2) while the waveform reaches A between // them. A sample-peak detector would pass this through untouched. const double amp = 1.2; const float ceiling = static_cast(limiterCeilingLinear()); std::vector in(8000); for (std::size_t i = 0; i < in.size(); ++i) { in[i] = static_cast( amp * std::cos(3.14159265358979323846 * (0.5 * static_cast(i) + 0.25))); } float samplePeak = 0.f; for (float v : in) samplePeak = std::max(samplePeak, std::fabs(v)); CHECK(samplePeak < ceiling); // the premise: no SAMPLE is over Limiter lim; lim.setEnabled(true); lim.prepare(kRate); float minGain = 0.f; runMono(lim, in, 256, &minGain); CHECK(minGain < 1.f); } static void testStereoLinkedGainKeepsDualMonoCenteredAcrossAToggle() { Limiter lim; lim.prepare(kRate); const float ceiling = static_cast(limiterCeilingLinear()); const std::vector src = pattern(48000, ceiling * 2.5f); std::vector l = src, r = src; // dual mono: L and R are the same signal const int block = 64; bool centered = true; for (std::size_t i = 0; i < l.size(); i += static_cast(block)) { // Toggle on a quarter in and off three quarters in, so the run covers bypassed, // the engage crossfade, fully engaged, the disengage crossfade, and bypassed again. if (i >= l.size() / 4 && !lim.enabled()) lim.setEnabled(true); if (i >= (l.size() * 3) / 4 && lim.enabled()) lim.setEnabled(false); const int n = static_cast( std::min(static_cast(block), l.size() - i)); lim.process(l.data() + i, r.data() + i, n); } for (std::size_t i = 0; i < l.size(); ++i) { if (l[i] != r[i]) { centered = false; break; } } CHECK(centered); // And the engaged stretch really was limited, so the equality above is not equality on an // untouched buffer. float worstEngaged = 0.f; for (std::size_t i = l.size() / 2; i < (l.size() * 3) / 4; ++i) { worstEngaged = std::max(worstEngaged, std::fabs(l[i])); } CHECK(worstEngaged <= ceiling * (1.f + 1e-6f)); CHECK(worstEngaged > 0.f); } static void testToggleEmitsNoStepLargerThanTheSignalsOwn() { // A steady sine: the crossfade blends it with a copy of itself delayed by the lookahead, // which at 440 Hz is nearly half a cycle out — switching hard instead of fading would step // by up to twice the amplitude, so this assertion has real teeth. const double freq = 440.0; const double amp = 0.5; // under the ceiling: this measures the TRANSITION, not limiting std::vector x(48000); for (std::size_t i = 0; i < x.size(); ++i) { x[i] = static_cast( amp * std::sin(2.0 * 3.14159265358979323846 * freq * static_cast(i) / kRate)); } const float naturalStep = static_cast(amp * 2.0 * 3.14159265358979323846 * freq / kRate); Limiter lim; lim.prepare(kRate); const int block = 32; for (std::size_t i = 0; i < x.size(); i += static_cast(block)) { if (i >= x.size() / 4 && !lim.enabled()) lim.setEnabled(true); if (i >= (x.size() * 3) / 4 && lim.enabled()) lim.setEnabled(false); const int n = static_cast( std::min(static_cast(block), x.size() - i)); lim.process(x.data() + i, nullptr, n); } float worstStep = 0.f; for (std::size_t i = 1; i < x.size(); ++i) { worstStep = std::max(worstStep, std::fabs(x[i] - x[i - 1])); } CHECK(worstStep <= naturalStep * 1.2f); } static void testCrossfadeSettlesToTheExactEngagedAndBypassedPaths() { Limiter lim; lim.prepare(kRate); const int latency = limiterLookaheadSamples(kRate); const int settle = static_cast(kLimiterCrossfadeSeconds * kRate) + latency + 64; const std::vector src = pattern(4 * settle, 0.3f); // under the ceiling throughout std::vector y = src; lim.setEnabled(true); lim.process(y.data(), nullptr, static_cast(y.size())); // Past the crossfade the engaged path is exactly the delayed input again. bool exact = true; for (std::size_t i = static_cast(settle); i < y.size(); ++i) { if (y[i] != src[i - static_cast(latency)]) { exact = false; break; } } CHECK(exact); std::vector z = src; lim.setEnabled(false); lim.process(z.data(), nullptr, static_cast(z.size())); bool passthrough = true; for (std::size_t i = static_cast(settle); i < z.size(); ++i) { if (z[i] != src[i]) { passthrough = false; break; } } CHECK(passthrough); // And once settled bypassed, the next block is untouched again. std::vector w = pattern(512, 1.5f); const std::vector before = w; CHECK(lim.process(w.data(), nullptr, static_cast(w.size())) == 1.f); bool untouched = true; for (std::size_t i = 0; i < w.size(); ++i) { if (w[i] != before[i]) { untouched = false; break; } } CHECK(untouched); } static void testGainNeverRisesAboveUnity() { // "No makeup gain, ever, of any kind" as a property rather than an absence: across quiet, // loud and silent material the applied gain is never above 1 and the output magnitude is // never above the input's own. Limiter lim; lim.setEnabled(true); lim.prepare(kRate); std::vector in = pattern(16000, 2.0f); for (std::size_t i = 4000; i < 8000; ++i) in[i] = 0.f; // a silent stretch for (std::size_t i = 8000; i < 12000; ++i) in[i] *= 0.001f; // and a very quiet one float minGain = 0.f; const std::vector out = runMono(lim, in, 200, &minGain); CHECK(minGain <= 1.f); float inPeak = 0.f, outPeak = 0.f; for (std::size_t i = 0; i < in.size(); ++i) { inPeak = std::max(inPeak, std::fabs(in[i])); outPeak = std::max(outPeak, std::fabs(out[i])); } CHECK(outPeak <= inPeak); } static void testAlignmentIdentityHoldsAtTheExactWindowEdge() { // Pins the alignment identity window_ = latency_ - kLimiterOsDelay + 1 (limiter.h's // comment on window_, otherwise asserted nowhere): a single isolated over-ceiling impulse // is reduced to EXACTLY the ceiling at the one output sample the identity predicts // (impulseAt + latency), because that is the unique push index where the sliding // min-then-average has folded in nothing but this impulse's own detected peak. Shifting // the identity by +-1 either lets the impulse's own excess slip just outside the window // (undershoots the reduction, sample overshoots the ceiling) or applies the full reduction // one sample late (same overshoot at this index) — confirmed by hand-mutating window_'s // formula in both directions and observing this assertion fail before restoring it. Limiter lim; lim.setEnabled(true); lim.prepare(kRate); const int latency = limiterLookaheadSamples(kRate); const float ceiling = static_cast(limiterCeilingLinear()); const int impulseAt = 500; std::vector in(static_cast(impulseAt + latency + 200), 0.f); in[static_cast(impulseAt)] = ceiling * 4.f; // isolated, well over float minGain = 0.f; const std::vector out = runMono(lim, in, 37, &minGain); // odd block: crosses the edge CHECK(minGain > 0.24f && minGain < 0.26f); // ceiling/peak == 0.25 for this impulse const float atEdge = out[static_cast(impulseAt + latency)]; CHECK(std::fabs(atEdge - ceiling) <= ceiling * 1e-6f); // Every neighbor stays exactly silent — the reduction lands on this one sample, not smeared. CHECK(out[static_cast(impulseAt + latency - 1)] == 0.f); CHECK(out[static_cast(impulseAt + latency + 1)] == 0.f); } static void testBakedConstants() { CHECK(kLimiterCeilingDbTp == -0.3); CHECK(std::fabs(limiterCeilingLinear() - std::pow(10.0, -0.3 / 20.0)) < 1e-12); CHECK(limiterCeilingLinear() < 1.0); // 2 ms at the common rates, and never below the detector's own group delay. CHECK(limiterLookaheadSamples(48000.0) == 96); CHECK(limiterLookaheadSamples(44100.0) == 88); CHECK(limiterLookaheadSamples(96000.0) == 192); CHECK(limiterLookaheadSamples(0.0) == 0); CHECK(limiterLookaheadSamples(-1.0) == 0); CHECK(limiterLookaheadSamples(100.0) > kLimiterOsDelay); } int main() { testBypassedLeavesEveryByteUntouched(); testEngagedBelowThresholdIsTheInputDelayedBitExactly(); testEngagedHoldsTheCeilingOnProgramTwelveDbOver(); testTruePeakDetectionEngagesWhereSamplePeakWouldNot(); testStereoLinkedGainKeepsDualMonoCenteredAcrossAToggle(); testToggleEmitsNoStepLargerThanTheSignalsOwn(); testCrossfadeSettlesToTheExactEngagedAndBypassedPaths(); testGainNeverRisesAboveUnity(); testAlignmentIdentityHoldsAtTheExactWindowEdge(); testBakedConstants(); if (g_fail) { std::printf("%d FAILURE(S)\n", g_fail); return 1; } std::printf("limiter tests passed\n"); return 0; }