Keep voices around while notes are releasing
This commit is contained in:
parent
06c5342724
commit
a16c14a6a8
|
|
@ -9,6 +9,7 @@ const DEFAULT_VOICES: usize = 8;
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct VoiceState {
|
struct VoiceState {
|
||||||
active: bool,
|
active: bool,
|
||||||
|
releasing: bool, // Note-off received, still processing (e.g. ADSR release)
|
||||||
note: u8,
|
note: u8,
|
||||||
age: u32, // For voice stealing
|
age: u32, // For voice stealing
|
||||||
pending_events: Vec<MidiEvent>, // MIDI events to send to this voice
|
pending_events: Vec<MidiEvent>, // MIDI events to send to this voice
|
||||||
|
|
@ -18,6 +19,7 @@ impl VoiceState {
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
active: false,
|
active: false,
|
||||||
|
releasing: false,
|
||||||
note: 0,
|
note: 0,
|
||||||
age: 0,
|
age: 0,
|
||||||
pending_events: Vec::new(),
|
pending_events: Vec::new(),
|
||||||
|
|
@ -145,9 +147,9 @@ impl VoiceAllocatorNode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Find a free voice, or steal the oldest one
|
/// Find a free voice, or steal one
|
||||||
|
/// Priority: inactive → oldest releasing → oldest held
|
||||||
fn find_voice_for_note_on(&mut self) -> usize {
|
fn find_voice_for_note_on(&mut self) -> usize {
|
||||||
// Only search within active voice_count
|
|
||||||
// First, look for an inactive voice
|
// First, look for an inactive voice
|
||||||
for (i, voice) in self.voices[..self.voice_count].iter().enumerate() {
|
for (i, voice) in self.voices[..self.voice_count].iter().enumerate() {
|
||||||
if !voice.active {
|
if !voice.active {
|
||||||
|
|
@ -155,7 +157,17 @@ impl VoiceAllocatorNode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// No free voices, steal the oldest one within voice_count
|
// No inactive voices — steal the oldest releasing voice
|
||||||
|
if let Some((i, _)) = self.voices[..self.voice_count]
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter(|(_, v)| v.releasing)
|
||||||
|
.max_by_key(|(_, v)| v.age)
|
||||||
|
{
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No releasing voices either — steal the oldest held voice
|
||||||
self.voices[..self.voice_count]
|
self.voices[..self.voice_count]
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
|
|
@ -164,13 +176,13 @@ impl VoiceAllocatorNode {
|
||||||
.unwrap_or(0)
|
.unwrap_or(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Find all voices playing a specific note
|
/// Find all voices playing a specific note (held, not yet releasing)
|
||||||
fn find_voices_for_note_off(&self, note: u8) -> Vec<usize> {
|
fn find_voices_for_note_off(&self, note: u8) -> Vec<usize> {
|
||||||
self.voices[..self.voice_count]
|
self.voices[..self.voice_count]
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.filter_map(|(i, v)| {
|
.filter_map(|(i, v)| {
|
||||||
if v.active && v.note == note {
|
if v.active && !v.releasing && v.note == note {
|
||||||
Some(i)
|
Some(i)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
|
@ -206,6 +218,7 @@ impl AudioNode for VoiceAllocatorNode {
|
||||||
// Stop voices beyond the new count
|
// Stop voices beyond the new count
|
||||||
for voice in &mut self.voices[new_count..] {
|
for voice in &mut self.voices[new_count..] {
|
||||||
voice.active = false;
|
voice.active = false;
|
||||||
|
voice.releasing = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -229,25 +242,26 @@ impl AudioNode for VoiceAllocatorNode {
|
||||||
if event.data2 > 0 {
|
if event.data2 > 0 {
|
||||||
let voice_idx = self.find_voice_for_note_on();
|
let voice_idx = self.find_voice_for_note_on();
|
||||||
self.voices[voice_idx].active = true;
|
self.voices[voice_idx].active = true;
|
||||||
|
self.voices[voice_idx].releasing = false;
|
||||||
self.voices[voice_idx].note = event.data1;
|
self.voices[voice_idx].note = event.data1;
|
||||||
self.voices[voice_idx].age = 0;
|
self.voices[voice_idx].age = 0;
|
||||||
|
|
||||||
// Store MIDI event for this voice to process
|
// Store MIDI event for this voice to process
|
||||||
self.voices[voice_idx].pending_events.push(*event);
|
self.voices[voice_idx].pending_events.push(*event);
|
||||||
} else {
|
} else {
|
||||||
// Velocity = 0 means note off - send to ALL voices playing this note
|
// Velocity = 0 means note off — mark releasing, keep active for ADSR release
|
||||||
let voice_indices = self.find_voices_for_note_off(event.data1);
|
let voice_indices = self.find_voices_for_note_off(event.data1);
|
||||||
for voice_idx in voice_indices {
|
for voice_idx in voice_indices {
|
||||||
self.voices[voice_idx].active = false;
|
self.voices[voice_idx].releasing = true;
|
||||||
self.voices[voice_idx].pending_events.push(*event);
|
self.voices[voice_idx].pending_events.push(*event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
0x80 => {
|
0x80 => {
|
||||||
// Note off - send to ALL voices playing this note
|
// Note off — mark releasing, keep active for ADSR release
|
||||||
let voice_indices = self.find_voices_for_note_off(event.data1);
|
let voice_indices = self.find_voices_for_note_off(event.data1);
|
||||||
for voice_idx in voice_indices {
|
for voice_idx in voice_indices {
|
||||||
self.voices[voice_idx].active = false;
|
self.voices[voice_idx].releasing = true;
|
||||||
self.voices[voice_idx].pending_events.push(*event);
|
self.voices[voice_idx].pending_events.push(*event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -322,6 +336,7 @@ impl AudioNode for VoiceAllocatorNode {
|
||||||
fn reset(&mut self) {
|
fn reset(&mut self) {
|
||||||
for voice in &mut self.voices {
|
for voice in &mut self.voices {
|
||||||
voice.active = false;
|
voice.active = false;
|
||||||
|
voice.releasing = false;
|
||||||
voice.pending_events.clear();
|
voice.pending_events.clear();
|
||||||
}
|
}
|
||||||
for graph in &mut self.voice_instances {
|
for graph in &mut self.voice_instances {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue