fix(voice): CC 120 hard-stops voices (incl. Trigger); Mono sizes voices_ to 1; comments corrected

CC 120 -> allSoundsOff/hardStop (immediate silence, stops Trigger one-shots); CC 123 -> allNotesOff/releaseAll (release, unchanged). Mono VoiceEngine sizes voices_ to 1 structurally. Legato same-note re-press edge documented. Three new tests.
This commit is contained in:
2026-07-27 22:08:56 -04:00
parent cc64def2d0
commit 080a7be8ca
4 changed files with 146 additions and 37 deletions
+21 -9
View File
@@ -766,16 +766,28 @@ tresult PLUGIN_API ReaSamplerProcessor::process(ProcessData& data) {
if (inst) inst->engine.noteOff(e.noteOff.pitch);
if (drain) drain->engine.noteOff(e.noteOff.pitch);
} else if (e.type == Event::kLegacyMIDICCOutEvent) {
// PANIC (Phase S voice-review Major #2): CC 123 (All Notes Off) / CC 120 (All
// Sound Off) reset the engine — clear the mono held stack and release every
// voice, live AND drain, engine AND preview card — so a phantom held-stack
// entry left by a lost note-off can never be resurrected by the mono fallback
// and sustain forever. REAPER delivers raw input MIDI CC to a VST3 instrument
// as kLegacyMIDICCOut events on the INPUT event list (a REAPER-ism — the type
// is nominally an output event; DAW-verify, see handoff). allNotesOff /
// releaseAll are RT-safe (no allocation, bounded scans).
// PANIC (Phase S voice-review Major #2): REAPER delivers raw input MIDI CC to a
// VST3 instrument as kLegacyMIDICCOut events on the INPUT event list (a REAPER-ism
// — the type is nominally an output event; DAW-verify, see handoff).
// CC 123 (All Notes Off): release semantics — Gate voices enter their AHDSR
// release tail; Trigger one-shots play through their bounded play length.
// CC 120 (All Sounds Off): hard-stop semantics — immediate silence regardless
// of play mode, including Trigger one-shots that ignore CC 123. This is the
// true "panic" for a ringing one-shot (e.g. a full-length capture).
// Both clear the mono held stack. Both apply to live AND drain, engine AND preview.
// allNotesOff / allSoundsOff / releaseAll / hardStop are RT-safe (no allocation,
// bounded scans).
const auto cc = static_cast<int>(e.midiCCOut.controlNumber);
if (cc == kCtrlAllNotesOff || cc == kCtrlAllSoundsOff) {
if (cc == kCtrlAllSoundsOff) {
if (inst) {
inst->engine.allSoundsOff();
inst->preview.hardStop();
}
if (drain) {
drain->engine.allSoundsOff();
drain->preview.hardStop();
}
} else if (cc == kCtrlAllNotesOff) {
if (inst) {
inst->engine.allNotesOff();
inst->preview.releaseAll();