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:
2026-08-02 16:22:17 -04:00
parent bfaa0f2614
commit de5654fb6f
44 changed files with 1205 additions and 302 deletions
+37
View File
@@ -6,7 +6,9 @@
#include "../src/core/instrument/param/param_id.h"
#include "../src/core/instrument/engine/filter/filter_params.h"
#include "../src/core/instrument/engine/master_gain.h"
#include "../src/core/instrument/map/play_seconds.h"
#include "../src/core/instrument/map/sample_map.h"
#include "../src/core/instrument/ui/deck_values.h"
#include <cmath>
@@ -29,6 +31,7 @@ static void testEveryUnitStringAndRangeMatchesTheSpecifiedTable() {
// docs/product/parameter-automation.md 6.7.1, per parameter rather than per category, so a
// control silently reclassified into the wrong category fails here.
const Expect kExpected[] = {
{kParamKeyTrackPitch, "%", 0.0, 200.0},
{kParamRate, "%", 50.0, 200.0},
{kParamPitchOffset, "st", -24.0, 24.0},
{kParamPitchEnvAttack, "ms", 0.0, 10000.0},
@@ -65,6 +68,7 @@ static void testEveryUnitStringAndRangeMatchesTheSpecifiedTable() {
{kParamAmpSustain, "%", 0.0, 100.0},
{kParamAmpRelease, "ms", 0.0, 10000.0},
{kParamAmpReleaseCurve, "", 0.1, 10.0},
{kParamTriggerLength, "%", 0.0, 100.0},
{kParamAmpTrigAttack, "ms", 0.0, 10000.0},
{kParamAmpTrigAttackCurve, "", 0.1, 10.0},
{kParamAmpTrigHold, "%", 0.0, 100.0},
@@ -100,6 +104,38 @@ static void testEveryDefaultHasAnExactNormalizedPreimage() {
}
}
static void testTheHostAndTheEditorAgreeOnEveryDefaultPosition() {
// The criterion is that a host's reset-to-default and the editor's double-click land on the
// SAME value — and those are different code paths: ParameterInfo::defaultNormalizedValue
// comes from defaultNormalized (a per-CATEGORY switch), the editor's needle from
// deckParamNorm (a per-ID one). Asserting the param module against itself would not see the
// two disagree, and they carry three independently written full scales to disagree about.
using reasampler::instrument::ui::deckParamNorm;
const PlaySeconds defaults;
for (const ParamRow& row : exposedParams()) {
const double hostNorm = defaultNormalized(row.deck);
// The four that STORE their normalized position take it verbatim on both surfaces, and
// the two instance scalars are not in PlaySeconds at all — each read where it lives.
double editorNorm = 0.0;
switch (valueHomeFor(row.deck)) {
case ValueHome::InstanceScalar:
editorNorm = (row.deck == DeckParam::kMasterGain)
? reasampler::instrument::engine::masterGainNormFromLinear(1.0) // unity
: reasampler::instrument::ui::keyTrackNormFrom(
reasampler::instrument::map::InstrumentParams{}.keyTrack);
break;
case ValueHome::ParamSetNorm:
case ValueHome::ParamSet:
editorNorm = deckParamNorm(row.deck, defaults);
break;
case ValueHome::None:
CHECK_ID(false, row.id); // an exposed control with no home reads nothing
continue;
}
CHECK_ID(hostNorm == editorNorm, row.id);
}
}
static void testTheFiltersFourTakeTheirStoredNormVerbatim() {
// Their stored value IS the normalized one, so no taper may participate in their default:
// this fails the moment someone routes them through toNormalized(toPlain(x)).
@@ -181,6 +217,7 @@ static void testTheAddedDriveInverseUndoesTheFrozenLaw() {
int main() {
testEveryUnitStringAndRangeMatchesTheSpecifiedTable();
testEveryDefaultHasAnExactNormalizedPreimage();
testTheHostAndTheEditorAgreeOnEveryDefaultPosition();
testTheFiltersFourTakeTheirStoredNormVerbatim();
testToPlainIsMonotoneAcrossTheWholeTravel();
testTheEndpointsAreTheDeclaredPlainRange();