Service both VST3 parameter channels, and promote pitch key-track and Trigger length so all 44 ids issue
The SDK's own single-component sample drains inputParameterChanges in process() and implements setParamNormalized; automation was reading the GUI channel alone. The audio thread now patches a block it solely owns.
This commit is contained in:
+85
-21
@@ -1,17 +1,21 @@
|
||||
// Standalone tests for the ONE formatter per unit category: the digit shapes each category
|
||||
// prints, and the property that makes the editor's knob label and the host's parameter string
|
||||
// identical — both call THIS function, over a plain value derived the way each surface derives
|
||||
// it. No VST3, no REAPER, no framework.
|
||||
// identical — both call THIS function, each over the plain value ITS OWN surface derives (the
|
||||
// editor's exponent read goes to the stored field, not through the knob law). No VST3, no
|
||||
// REAPER, no framework.
|
||||
|
||||
#include "../src/core/instrument/param/param_format.h"
|
||||
|
||||
#include "../src/core/instrument/engine/master_gain.h"
|
||||
#include "../src/core/instrument/map/sample_map.h"
|
||||
#include "../src/core/instrument/param/param_id.h"
|
||||
#include "../src/core/instrument/param/param_units.h"
|
||||
#include "../src/core/instrument/ui/deck_values.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
|
||||
using namespace reasampler;
|
||||
@@ -61,34 +65,40 @@ static void testEachCategoryPrintsItsSpecifiedShape() {
|
||||
CHECK(digits(UnitKind::Dimensionless, 0.1) == "0.10");
|
||||
}
|
||||
|
||||
// The EDITOR's derivation, spelled the way ReaSamplerEditor::deckPlainValue spells it — a curve
|
||||
// exponent is read off its stored field, never round-tripped through the knob law. Driving the
|
||||
// sweep through this rather than through toPlain(deckParamNorm(...)) is what makes the exponent
|
||||
// half of it a real assertion instead of a round trip on both sides.
|
||||
static double editorPlainValue(DeckParam deck, PlaySeconds& play) {
|
||||
using reasampler::instrument::ui::UnitCategory;
|
||||
if (reasampler::instrument::ui::deckParamUnit(deck) == UnitCategory::Exponent) {
|
||||
const double* stored = reasampler::instrument::ui::deckDoubleField(deck, play);
|
||||
return stored ? *stored : 0.0;
|
||||
}
|
||||
return toPlain(deck, reasampler::instrument::ui::deckParamNorm(deck, play));
|
||||
}
|
||||
|
||||
static void testTheEditorAndTheHostPrintTheSameDigitsAtTheSameStoredValue() {
|
||||
// The host derives its plain value from the normalized one it holds; the editor derives its
|
||||
// from the STORED field, through the deck's own read. If those two derivations disagreed at
|
||||
// any reachable value the two surfaces would print different numbers for one control — this
|
||||
// is that property, swept over the whole travel of every exposed control.
|
||||
// from the STORED field. If those two derivations disagreed at any reachable value the two
|
||||
// surfaces would print different numbers for one control — this is that property, swept over
|
||||
// the whole travel of every exposed control the parameter set carries.
|
||||
for (const ParamRow& row : exposedParams()) {
|
||||
if (row.deck == DeckParam::kMasterGain) continue; // not stored in PlaySeconds
|
||||
if (valueHomeFor(row.deck) == ValueHome::InstanceScalar) continue; // own tests below
|
||||
for (int step = 0; step <= 40; ++step) {
|
||||
const double norm = step / 40.0;
|
||||
PlaySeconds play;
|
||||
reasampler::instrument::ui::setDeckParam(row.deck, play, norm, /*segment=*/0);
|
||||
const double storedNorm = reasampler::instrument::ui::deckParamNorm(row.deck, play);
|
||||
|
||||
char hostBuf[24];
|
||||
formatPlainFor(row.deck, toPlain(row.deck, norm), hostBuf, sizeof(hostBuf));
|
||||
|
||||
const double editorNorm =
|
||||
reasampler::instrument::ui::deckParamNorm(row.deck, play);
|
||||
char editorBuf[24];
|
||||
formatPlainFor(row.deck, toPlain(row.deck, editorNorm), editorBuf, sizeof(editorBuf));
|
||||
|
||||
if (storesNormalized(row.deck)) {
|
||||
// The filter's four store their position as a FLOAT, so a norm the host has sent
|
||||
// but we have not yet stored differs from the stored one by up to a float ulp.
|
||||
// At a value landing exactly on a display rounding boundary that is worth one
|
||||
// digit, so these four are held to the PLAIN value rather than to the string —
|
||||
// the derivation is still asserted to be one derivation.
|
||||
if (row.deck == DeckParam::kFilterMorph) {
|
||||
// MORPH ALONE: its toPlain is full-double (`clamp01(n) * 100`), so the float the
|
||||
// model stores and the double the host holds are genuinely different inputs, and
|
||||
// at a value landing on a display rounding boundary that is worth one integer
|
||||
// percent. Cutoff/Q/drive cast to float INSIDE toPlain, so they are bit-identical
|
||||
// either way and are held to the string below like everything else.
|
||||
const double hostPlain = toPlain(row.deck, norm);
|
||||
const double editorPlain = toPlain(row.deck, editorNorm);
|
||||
const double editorPlain = toPlain(row.deck, storedNorm);
|
||||
const double tolerance = std::fabs(hostPlain) * 1e-6 + 1e-9;
|
||||
if (std::fabs(hostPlain - editorPlain) > tolerance) {
|
||||
std::printf("FAIL param %u at norm %.4f: host %.9g vs editor %.9g\n",
|
||||
@@ -97,6 +107,16 @@ static void testTheEditorAndTheHostPrintTheSameDigitsAtTheSameStoredValue() {
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// A write the model CLAMPED (Trigger length's (0,1] floor) is not a settled state:
|
||||
// setParamNormalized caches what the model TOOK, so the host never holds the rejected
|
||||
// value. Only the four float-stored positions differ by a ulp rather than a clamp.
|
||||
if (!storesNormalized(row.deck) && storedNorm != norm) continue;
|
||||
|
||||
char hostBuf[24];
|
||||
char editorBuf[24];
|
||||
formatPlainFor(row.deck, toPlain(row.deck, norm), hostBuf, sizeof(hostBuf));
|
||||
formatPlainFor(row.deck, editorPlainValue(row.deck, play), editorBuf,
|
||||
sizeof(editorBuf));
|
||||
if (std::strcmp(hostBuf, editorBuf) != 0) {
|
||||
std::printf("FAIL param %u at norm %.4f: host \"%s\" vs editor \"%s\"\n",
|
||||
row.id, norm, hostBuf, editorBuf);
|
||||
@@ -106,6 +126,48 @@ static void testTheEditorAndTheHostPrintTheSameDigitsAtTheSameStoredValue() {
|
||||
}
|
||||
}
|
||||
|
||||
// The one place the two surfaces GENUINELY diverge, asserted so it stays a known property rather
|
||||
// than a surprise: an exponent inside curve_law's centre detent but not exactly neutral is
|
||||
// reachable only through an overlay knot drag, and the host — which holds the norm and nothing
|
||||
// else — reads it back as the neutral the knob law snaps to.
|
||||
static void testAnOffDetentExponentReadsNeutralToTheHostAndTrueToTheEditor() {
|
||||
PlaySeconds play;
|
||||
// The detent is +/-0.01 in NORM, which is a ~+/-0.047 band in the exponent — so 1.04 is
|
||||
// inside it and still prints as a distinct number.
|
||||
play.adsr.attackCurve = 1.04;
|
||||
char editorBuf[24];
|
||||
formatPlainFor(DeckParam::kAttackCurve, editorPlainValue(DeckParam::kAttackCurve, play),
|
||||
editorBuf, sizeof(editorBuf));
|
||||
CHECK(std::string(editorBuf) == "1.04");
|
||||
const double hostNorm =
|
||||
reasampler::instrument::ui::deckParamNorm(DeckParam::kAttackCurve, play);
|
||||
char hostBuf[24];
|
||||
formatPlainFor(DeckParam::kAttackCurve, toPlain(DeckParam::kAttackCurve, hostNorm), hostBuf,
|
||||
sizeof(hostBuf));
|
||||
CHECK(std::string(hostBuf) == "1.00");
|
||||
}
|
||||
|
||||
static void testKeyTrackPrintsTheSameDigitsFromEitherSurface() {
|
||||
using reasampler::instrument::map::InstrumentParams;
|
||||
for (int step = 0; step <= 40; ++step) {
|
||||
const double norm = step / 40.0;
|
||||
InstrumentParams params;
|
||||
params.keyTrack = reasampler::instrument::ui::keyTrackFromNorm(norm);
|
||||
const double editorNorm = reasampler::instrument::ui::keyTrackNormFrom(params.keyTrack);
|
||||
char hostBuf[24];
|
||||
char editorBuf[24];
|
||||
formatPlainFor(DeckParam::kKeyTrack, toPlain(DeckParam::kKeyTrack, norm), hostBuf,
|
||||
sizeof(hostBuf));
|
||||
formatPlainFor(DeckParam::kKeyTrack, toPlain(DeckParam::kKeyTrack, editorNorm), editorBuf,
|
||||
sizeof(editorBuf));
|
||||
if (std::strcmp(hostBuf, editorBuf) != 0) {
|
||||
std::printf("FAIL key-track at norm %.4f: host \"%s\" vs editor \"%s\"\n",
|
||||
norm, hostBuf, editorBuf);
|
||||
++g_fail;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void testMasterGainPrintsTheSameDigitsFromEitherSurface() {
|
||||
using reasampler::instrument::engine::masterGainLinearFromNorm;
|
||||
using reasampler::instrument::engine::masterGainNormFromLinear;
|
||||
@@ -165,7 +227,9 @@ static void testEveryExposedParameterHasAFormatterThatWritesSomething() {
|
||||
int main() {
|
||||
testEachCategoryPrintsItsSpecifiedShape();
|
||||
testTheEditorAndTheHostPrintTheSameDigitsAtTheSameStoredValue();
|
||||
testAnOffDetentExponentReadsNeutralToTheHostAndTrueToTheEditor();
|
||||
testMasterGainPrintsTheSameDigitsFromEitherSurface();
|
||||
testKeyTrackPrintsTheSameDigitsFromEitherSurface();
|
||||
testTypingBackADisplayedValueLandsOnIt();
|
||||
testAShortBufferIsNeverOverrunAndAlwaysTerminates();
|
||||
testEveryExposedParameterHasAFormatterThatWritesSomething();
|
||||
|
||||
Reference in New Issue
Block a user