Files
reasampler/tests/test_meter_accumulate.cpp
daniel da14509ab5 Restore the bank fold and usage publish to the resume path, guard setActive against repeats, and make the meter fold's bound literal
The resume also hands back to a full reload when the fold moves the loaded capture's decode source, so the refs table and the audio cannot skew.
2026-08-02 14:06:37 -04:00

108 lines
4.3 KiB
C++

// 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 <atomic>
#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)
// Stands in for std::atomic<float> 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<float> 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<float> 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<float> 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;
}