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
+18 -18
View File
@@ -96,7 +96,7 @@ static Run renderWithLive(SampleData& sample, LiveParams* block, int blockFrames
int changeAfter, const LiveValues* changed, int noteOffBlock = -1,
int velocity = 100) {
sample.live = block;
if (block) block->publish(foldLive(sample.play));
if (block) block->publish(foldLive(sample.play, sample.keyTrack));
VoiceEngine engine(1, sample);
engine.noteOn(kTestNote, velocity);
Run r;
@@ -320,7 +320,7 @@ static void testANoteStartedAfterAPublishSoundsThePublishedEnvelope() {
SampleData s = periodicSine(200000, 64.0); // adsr default: attack 0, sustain 1.0
LiveParams block;
s.live = █
LiveValues dialled = foldLive(s.play);
LiveValues dialled = foldLive(s.play, s.keyTrack);
dialled.adsr.attackFrames = 24000; // half a second of attack, dialled before the note
block.publish(dialled);
VoiceEngine engine(1, s);
@@ -345,7 +345,7 @@ static void testANoteStartedAfterAPublishSoundsThePublishedEnvelope() {
slow.play.adsr.attackFrames = 24000;
LiveParams block2;
slow.live = &block2;
LiveValues snappy = foldLive(slow.play);
LiveValues snappy = foldLive(slow.play, slow.keyTrack);
snappy.adsr.attackFrames = 0;
block2.publish(snappy);
VoiceEngine fast(1, slow);
@@ -368,7 +368,7 @@ static void assertLiveFieldMovesTheSoundingNote(const char* name, void (*rig)(Sa
rig(still);
rig(moved);
LiveParams blockA, blockB;
LiveValues target = foldLive(moved.play);
LiveValues target = foldLive(moved.play, moved.keyTrack);
mutate(target);
const Run baseline = renderWithLive(still, &blockA, 512, 24, -1, nullptr, noteOffBlock);
@@ -616,7 +616,7 @@ static void testEveryLiveFilterControlMovesTheSoundingNote() {
SampleData still = filteredSine();
SampleData moved = filteredSine();
LiveParams blockA, blockB;
LiveValues target = foldLive(moved.play);
LiveValues target = foldLive(moved.play, moved.keyTrack);
c.mutate(target);
const Run baseline = renderWithLive(still, &blockA, 512, 24, -1, nullptr);
@@ -688,7 +688,7 @@ static void testOneBlockServesTwoIndependentObservers() {
LiveParams block;
liveSnapshot.live = █
drainSnapshot.live = █
block.publish(foldLive(liveSnapshot.play));
block.publish(foldLive(liveSnapshot.play, liveSnapshot.keyTrack));
VoiceEngine liveEngine(1, liveSnapshot);
VoiceEngine drainEngine(1, drainSnapshot);
@@ -696,7 +696,7 @@ static void testOneBlockServesTwoIndependentObservers() {
drainEngine.noteOn(60, 100);
std::vector<AudioSample> a, b;
LiveValues moved = foldLive(liveSnapshot.play);
LiveValues moved = foldLive(liveSnapshot.play, liveSnapshot.keyTrack);
moved.filterSettings.cutoffNorm = 0.2f;
for (int blk = 0; blk < 24; ++blk) {
if (blk == 8) block.publish(moved);
@@ -746,7 +746,7 @@ static std::vector<AudioSample> renderPreserveCapable(SampleData& s, LiveParams&
const LiveValues* changed, int changeAfter,
int note) {
s.live = &block;
block.publish(foldLive(s.play));
block.publish(foldLive(s.play, s.keyTrack));
VoiceEngine engine(1, s, /*preserveVoiceCap=*/0, /*preserveWindowFrames=*/2048);
engine.noteOn(note, 100);
std::vector<AudioSample> out;
@@ -765,7 +765,7 @@ static void testARateChangeSpareTheSoundingNoteAndReachesTheNextOne() {
moved.play.pitchEngine = eng;
LiveParams blockA, blockB;
LiveValues halfRate = foldLive(moved.play);
LiveValues halfRate = foldLive(moved.play, moved.keyTrack);
halfRate.playRate = 0.5;
// At the ROOT note, so Preserve's shifter runs at shift 1.0 and never splices — the
@@ -795,7 +795,7 @@ static void testARateChangeSpareTheSoundingNoteAndReachesTheNextOne() {
fresh.play.pitchEngine = eng;
LiveParams block;
fresh.live = &block;
LiveValues published = foldLive(fresh.play);
LiveValues published = foldLive(fresh.play, fresh.keyTrack);
published.playRate = rate;
block.publish(published);
VoiceEngine engine(1, fresh, /*preserveVoiceCap=*/0, /*preserveWindowFrames=*/2048);
@@ -827,7 +827,7 @@ static void testAPitchOffsetChangeMovesTheSoundingNoteInBothEngines() {
moved.play.pitchEngine = eng;
LiveParams blockA, blockB;
LiveValues target = foldLive(moved.play);
LiveValues target = foldLive(moved.play, moved.keyTrack);
target.pitchOffsetSemitones = -12.0;
const std::vector<AudioSample> baseline =
@@ -859,7 +859,7 @@ static void testAPitchOffsetChangeMovesTheSoundingNoteInBothEngines() {
static std::size_t soundingBlocksWithPublishedPitch(SampleData& s, double offsetSemis,
std::size_t capFrames) {
LiveParams block;
LiveValues v = foldLive(s.play); // s.play keeps its own (zero) offset: the stale copy
LiveValues v = foldLive(s.play, s.keyTrack); // s.play keeps its own (zero) offset: the stale copy
v.pitchOffsetSemitones = offsetSemis;
block.publish(v);
s.live = &block;
@@ -891,7 +891,7 @@ static void testAPublishedPitchOffsetLeavesTheStagedAttackWallClock() {
s.play.trigAhd = AhdParams{kAttack, 0, 1.0, util::kCurveNeutral, util::kCurveNeutral};
LiveParams block;
LiveValues v = foldLive(s.play);
LiveValues v = foldLive(s.play, s.keyTrack);
v.pitchOffsetSemitones = semis;
block.publish(v);
s.live = &block;
@@ -960,12 +960,12 @@ static void testPitchRatioAndVelocityGainStayLatched() {
LiveParams block;
s.live = &block;
block.publish(foldLive(s.play));
block.publish(foldLive(s.play, s.keyTrack));
VoiceEngine engine(1, s);
engine.noteOn(72, 64); // an octave up: ratio 2.0
std::vector<AudioSample> out;
LiveValues hostile = foldLive(s.play);
LiveValues hostile = foldLive(s.play, s.keyTrack);
// Everything the block CAN carry, moved as far as it goes. None of it names velocity, the
// note, the pitch ratio, or the PCM — that is the property under test.
hostile.filterKeyTrack = 2.0;
@@ -998,11 +998,11 @@ static void testPitchRatioAndVelocityGainStayLatched() {
SampleData s2 = s;
LiveParams block2;
s2.live = &block2;
block2.publish(foldLive(s2.play));
block2.publish(foldLive(s2.play, s2.keyTrack));
VoiceEngine engine2(1, s2);
engine2.noteOn(72, 64);
std::vector<AudioSample> out2;
LiveValues quieter = foldLive(s2.play);
LiveValues quieter = foldLive(s2.play, s2.keyTrack);
quieter.adsr.sustainLevel = 0.25;
for (int blk = 0; blk < 8; ++blk) {
if (blk == 2) block2.publish(quieter);
@@ -1024,7 +1024,7 @@ static void testVelocityGainSurvivesAHostilePublishThatReallyLands() {
rig.play.pitchEnv.shape.decayFrames = 24000;
rig.play.pitchEnv.peakSemitones = 3.0;
LiveValues hostile = foldLive(rig.play);
LiveValues hostile = foldLive(rig.play, rig.keyTrack);
hostile.filterKeyTrack = 2.0;
hostile.filterSettings.cutoffNorm = 0.9f;
hostile.filterModAmount = -1.0;