PITCH/RATE deck: Rate and Pitch knobs compounded into one read increment, on a three-state commit predicate and payload v16

This commit is contained in:
2026-08-02 05:34:33 -04:00
parent ef59265e7a
commit 248f2f3842
32 changed files with 1098 additions and 163 deletions
+4 -2
View File
@@ -69,14 +69,16 @@ scattered `#ifdef`s in the VST shell, except the one described below).
(`DEF_CLASS2` / `INLINE_UID` / `FUID` from `pluginfactory.h` + `funknown.h`).
**The three commit tiers (Θ-W3).** An edit reaches the audio by exactly one of three routes, and
which route a control takes is decided once, by the pure `isLiveDeckParam` / `liveCommitFor` pair
which route a control takes is decided once, by the pure `deckParamCommit` / `liveCommitFor` pair
(`core/instrument/ui/deck_groups`) that the editor's `dragCommitsLive` only maps onto — see
`core/instrument/CLAUDE.md`'s "Live parameter delivery" for the rule and its rationale.
1. **Full reload**`reloadInstrument`: bridge read, WAV re-decode, fresh engine, snapshot swap.
2. **Engine rebuild**`rebuildVoiceEngine`: same drain-slot swap around the already-decoded
`SampleData`. Voice count / mode / mono trigger.
3. **Live**`publishLiveParams` (and `masterGain_`, the original of the shape): a lock-free
publish the audio thread observes at block boundaries. No rebuild, no snapshot, no disk.
publish the audio thread observes at block boundaries. No rebuild, no snapshot, no disk. The
pure predicate splits this tier by WHO READS the published value (`Live` vs `NoteOnLatched`);
the route out of the editor is the same one either way.
The editor's `commitLive` is the tier-3 peer of `commitAndReload`; why it still writes the
parameter set is recorded at its declaration in `reasampler_editor.h`, and why `liveParams_` is
+10
View File
@@ -211,6 +211,16 @@ std::string ReaSamplerEditor::deckValueLabel(int id) const {
snprintf(buf, sizeof(buf), "%+.1fst", play.pitchEnv.peakSemitones); break;
case ParamControl::kKeyTrack:
snprintf(buf, sizeof(buf), "%.0f%%", params_.keyTrack * 100.0); break;
case ParamControl::kRate: {
// One decimal below 100 % only: the taper is linear in semitones, so the lower half
// spends 50 percentage points on the same twelve semitones the upper half spends
// 100 on — a whole percent is twice as coarse a step down there.
const double pct = play.playRate * 100.0;
snprintf(buf, sizeof(buf), pct < 100.0 ? "%.1f%%" : "%.0f%%", pct);
break;
}
case ParamControl::kPitch:
snprintf(buf, sizeof(buf), "%+.1fst", play.pitchOffsetSemitones); break;
case ParamControl::kVoiceCount:
snprintf(buf, sizeof(buf), "%d", voiceCount_); break;
case ParamControl::kMasterGain:
+3 -1
View File
@@ -73,6 +73,8 @@ void ReaSamplerEditor::paintDeck(LICE_IBitmap* bmp, const FaceLayout& fl) {
case ParamControl::kTrigHold: return "Hold";
case ParamControl::kTrigDecay: return "Decay";
case ParamControl::kKeyTrack: return "Key Trk";
case ParamControl::kRate: return "Rate";
case ParamControl::kPitch: return "Pitch";
case ParamControl::kPitchEnvAttack: return "P.Att";
case ParamControl::kPitchEnvHold: return "P.Hold";
case ParamControl::kPitchEnvDecay: return "P.Dec";
@@ -109,7 +111,7 @@ void ReaSamplerEditor::paintDeck(LICE_IBitmap* bmp, const FaceLayout& fl) {
const char* caption = "";
switch (g.id) {
case kGroupAmpEnv: caption = "AMP ENVELOPE"; break;
case kGroupPitch: caption = "PITCH"; break;
case kGroupPitch: caption = "PITCH/RATE"; break;
case kGroupPitchEnv: caption = "PITCH ENV"; break;
case kGroupFilter: caption = "FILTER"; break;
case kGroupFilterEnv: caption = "FILTER ENV"; break;
+4 -1
View File
@@ -190,7 +190,10 @@ bool ReaSamplerEditor::dragCommitsLive(DragKind kind, int paramId) const {
const LiveDragKind k = kind == DragKind::kDeckKnob ? LiveDragKind::kDeckKnob
: kind == DragKind::kEnvNode ? LiveDragKind::kEnvNode
: LiveDragKind::kOther;
return instrument::ui::liveCommitFor(k, paramId);
// Live and NoteOnLatched take the SAME route out of here — one publish, no reload — so the
// shell's question is only "does this reload?". Which voices then read the published value
// is the engine's business (deck_groups.h).
return instrument::ui::liveCommitFor(k, paramId) != instrument::ui::LiveCommit::Reload;
}
void ReaSamplerEditor::closeCurvePopup() {
+2 -2
View File
@@ -276,14 +276,14 @@ private:
// instrument off the audio thread. UI thread only.
void commitAndReload();
// The live peer of commitAndReload for a continuously-valued control (isLiveDeckParam):
// The live peer of commitAndReload for a continuously-valued control (deckParamCommit):
// the same parameter-set write — so a saved project carries the edit exactly as before —
// followed by a live publish instead of a rebuild, so the note already sounding follows
// the knob. Does not repaint; callers already do. UI thread only.
void commitLive();
// Whether an in-flight drag commits live rather than through a reload. A deck knob is
// live per isLiveDeckParam; an envelope-node drag is live in EITHER mode — see
// live per deckParamCommit; an envelope-node drag is live in EITHER mode — see
// liveCommitFor (deck_groups.h) for why.
bool dragCommitsLive(DragKind kind, int paramId = -1) const;