Decouple the instrument reload from VST3 activation, and make the master meter's accumulate exact
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
// 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.
|
||||
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_weak(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;
|
||||
}
|
||||
Reference in New Issue
Block a user