da14509ab5
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.
108 lines
4.3 KiB
C++
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;
|
|
}
|