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
+17 -2
View File
@@ -45,6 +45,9 @@ inline double pitchRatio(int note, int rootNote) {
// ((note-root)*1.0 is exact in IEEE-754 for an integer-valued double, feeding the same
// std::pow call); 0.0 means every key plays the root pitch; 2.0 doubles the tracking rate.
// At the root note the offset is 0 regardless of keyTrack.
// "Not supplied" for Voice::start's two snapshot-defaulted note-on latches; see start().
inline constexpr double kLatchFromSnapshot = -1.0;
inline double keyTrackedRatio(int note, int rootNote, double keyTrack) {
const double semis = static_cast<double>(note - rootNote) * keyTrack;
return std::pow(2.0, semis / 12.0);
@@ -124,8 +127,16 @@ public:
// to consult gets exactly that; VoiceEngine::startVoice is what resolves the real value —
// sample.play.playRate is NOT read here, because the published block outranks the snapshot's
// possibly-stale copy of it.
//
// `keyTrack` and `lengthFraction` are the other two members of stretchRate's note-on-latched
// class and arrive the same way, for the same structural reason. Negative = not supplied,
// which reads the snapshot's own value (sample.keyTrack, effectiveLengthFraction(play)) —
// both are non-negative by domain, so the sentinel can never collide with a real one.
// VoiceEngine::startVoice always supplies them, resolved from the published block when there
// is one; the sentinel is for a caller that has no block to consult.
void start(int note, int velocity, const SampleData& sample, bool declickTakeover = false,
double stretchRate = 1.0);
double stretchRate = 1.0, double keyTrack = kLatchFromSnapshot,
double lengthFraction = kLatchFromSnapshot);
// Mono legato takeover: re-pitch this active voice to `note` without touching the
// amplitude envelope, read position, or shifter state — pitch moves, no re-attack. Both
@@ -201,7 +212,7 @@ private:
// shifter's transpose. Cold: note-on, legato retune, and a live block, never per frame.
void recomputeBaseRatio() {
if (sample_ == nullptr) return;
baseRatio_ = keyTrackedRatio(note_, sample_->rootNote, sample_->keyTrack) *
baseRatio_ = keyTrackedRatio(note_, sample_->rootNote, keyTrack_) *
velPitchRatio_ * pitchOffsetRatio_ * rateRatio_;
}
@@ -710,6 +721,10 @@ private:
double velPitchRatio_ = 1.0; // the velocity->pitch factor alone; retune re-applies it
double pitchOffsetRatio_ = 1.0; // the Pitch knob's factor — LIVE, re-applied by applyLive
double rateRatio_ = 1.0; // Rate's factor of the read increment; start() owns when it is 1
// Key-track, LATCHED at note-on beside the rate. Held here rather than re-read off the
// snapshot so a legato retune and a live block re-apply the note's own value; a published
// move reaches the next note only.
double keyTrack_ = kKeyTrackDefault;
// Whether this note is ACTUALLY taking the Preserve read — a Preserve voice whose shifters
// were never sized falls back to the varispeed one, and the two domains differ. Latched at
// note-on beside rateRatio_, which start() resolves from the same predicate.