Release held VST3 notes on reset instead of discarding them

Fixes #441

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
NetterSilverandClaude Haiku 5.5 committed 2026-10-15 04:00:26 +02:00
1 parent 3a28a65769
commit ea0e4719fc
2 files changed
+30 -1

No files matched your search

+7 -1
View File
@@ -213,8 +213,14 @@ impl Plugin for Vst3Plugin {
}
fn reset(&mut self) {
self.notes.clear();
// Keep pending releases and release whatever is still held, so the processor's voices stop
// on the next block instead of sounding on while the host forgets them.
self.notes.retain(|e| matches!(e, NoteEvent::Off { .. }));
for e in &mut self.notes {
e.set_time(0);
}
self.controls.clear();
self.all_notes_off();
self.held = [false; 128];
}
+23
View File
@@ -144,6 +144,29 @@ fn synth_plays_notes_at_their_offsets() {
assert!(mono[0].iter().all(|&x| (x - 1.0).abs() < 1e-6), "the note is still held");
}
#[test]
fn reset_after_all_notes_off_still_releases_held_voices() {
let mut p = soundcraft_vst3_host::create(&id_of("Fixture Synth")).unwrap();
p.prepare(48_000.0, 64, 2);
p.note_on(0, 60, 127);
let mut io = vec![vec![0.0f32; 64], vec![0.0f32; 64]];
p.process(&mut io, 64);
assert!((io[0][63] - 1.0).abs() < 1e-6, "the note sounds");
// Mixer::reset calls these two in a row on an instrument (stop / seek).
p.all_notes_off();
p.reset();
let mut io = vec![vec![9.0f32; 64], vec![9.0f32; 64]];
p.process(&mut io, 64);
assert!(io[0].iter().all(|x| x.abs() < 1e-6), "voice still sounding after reset: {}", io[0][63]);
// A reset alone also releases what the host still holds.
p.note_on(0, 61, 127);
p.process(&mut io, 64);
assert!((io[0][63] - 1.0).abs() < 1e-6);
p.reset();
p.process(&mut io, 64);
assert!(io[0].iter().all(|x| x.abs() < 1e-6), "voice still sounding after reset: {}", io[0][63]);
}
#[test]
fn many_instances_create_and_drop_cleanly() {
let (g, s) = (id_of("Fixture Gain"), id_of("Fixture Synth"));