// Standalone tests for reasampler::instrument::engine::meter_accumulate — no VST3, no REAPER, // no framework. Assert: // // * the window semantics — max for a peak, min for the limiter gain, and a consume that both // reports the window and reinstalls the identity element that starts the next one. // * the INTERLEAVE the CAS exists for: a consume landing inside a fold must not swallow that // block. Driven by an accumulator that performs the consume from inside its first CAS, so // the ordering is pinned rather than raced for. #include "../src/core/instrument/engine/meter_accumulate.h" #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) // Stands in for std::atomic with ONE scripted interference: the first compare-exchange // runs the UI's consume (identity reinstalled, the window taken) and reports failure exactly as // the real CAS does — expected updated to what the consume left. Everything after is ordinary, // which is what makes the retry count assertable: a strong CAS fails only under interference. struct ConsumingAccumulator { float value; float identity; float consumed = -1.f; // what the injected consume took int casCount = 0; float load(std::memory_order) const { return value; } bool compare_exchange_strong(float& expected, float desired, std::memory_order, std::memory_order) { if (casCount++ == 0) { consumed = value; value = identity; expected = value; return false; } value = desired; return true; } }; static void testPeakWindowKeepsTheLoudestBlock() { std::atomic acc{kMeterPeakIdentity}; foldPeak(acc, 0.25f); foldPeak(acc, 0.90f); foldPeak(acc, 0.40f); // quieter than the window's max: must not lower it CHECK(acc.load() == 0.90f); CHECK(consumePeak(acc) == 0.90f); // Consumed means a NEW window, not a carried-over one. CHECK(acc.load() == kMeterPeakIdentity); foldPeak(acc, 0.10f); CHECK(consumePeak(acc) == 0.10f); } static void testGainWindowKeepsTheDeepestReduction() { std::atomic acc{kMeterGainIdentity}; foldMinGain(acc, 0.80f); foldMinGain(acc, 0.55f); foldMinGain(acc, 0.95f); // shallower: must not raise the window CHECK(acc.load() == 0.55f); CHECK(consumeMinGain(acc) == 0.55f); // 1.0, not 0.0 — an untouched gain window means "no reduction", and a 0 identity would // report a total mute on every idle frame. CHECK(acc.load() == kMeterGainIdentity); } static void testBlocksAtOrBelowTheWindowLeaveItAlone() { std::atomic acc{kMeterPeakIdentity}; foldPeak(acc, 0.50f); foldPeak(acc, 0.50f); CHECK(acc.load() == 0.50f); // The post-condition every fold owes, whichever way the comparison went. foldPeak(acc, 0.20f); CHECK(acc.load() >= 0.20f); } static void testConsumeInsideAFoldStillLandsTheBlockInTheNewWindow() { // The window holds a LOUDER peak than the block being folded — the exact case a // load-compare-store fold skips, so the block would be lost when the consume lands // between that load and the store it decided not to make. ConsumingAccumulator acc{0.90f, kMeterPeakIdentity}; foldPeak(acc, 0.40f); CHECK(acc.consumed == 0.90f); // the UI got the window it was owed CHECK(acc.value == 0.40f); // and the block reached the NEW window rather than vanishing CHECK(acc.casCount == 2); // one interfering consume, exactly one retry // Same for the gain window: a block reducing LESS than the window's minimum is the one a // skipping fold drops, and losing it reports "no reduction" over a block that had some. ConsumingAccumulator gain{0.60f, kMeterGainIdentity}; foldMinGain(gain, 0.90f); CHECK(gain.consumed == 0.60f); CHECK(gain.value == 0.90f); CHECK(gain.casCount == 2); } int main() { testPeakWindowKeepsTheLoudestBlock(); testGainWindowKeepsTheDeepestReduction(); testBlocksAtOrBelowTheWindowLeaveItAlone(); testConsumeInsideAFoldStillLandsTheBlockInTheNewWindow(); if (g_fail == 0) std::printf("meter_accumulate tests passed\n"); return g_fail == 0 ? 0 : 1; }