use serde::{Deserialize, Serialize};
use smallvec::SmallVec;
const VOICE_INLINE: usize = 16;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub enum PolyMode {
Poly,
Mono,
Legato,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub enum StealMode {
Oldest,
Quietest,
Lowest,
None,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Voice {
active: bool,
pub note: u8,
pub velocity: f32,
age: u64,
pub amplitude: f32,
pub pitch_bend: f32,
pub pressure: f32,
pub brightness: f32,
}
impl Voice {
#[inline]
#[must_use]
pub fn is_active(&self) -> bool {
self.active
}
#[inline]
#[must_use]
pub fn age(&self) -> u64 {
self.age
}
}
impl Default for Voice {
fn default() -> Self {
Self {
active: false,
note: 0,
velocity: 0.0,
age: 0,
amplitude: 0.0,
pitch_bend: 0.0,
pressure: 0.0,
brightness: 0.5,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VoiceManager {
pub(crate) voices: SmallVec<[Voice; VOICE_INLINE]>,
max_voices: usize,
pub poly_mode: PolyMode,
pub steal_mode: StealMode,
}
impl VoiceManager {
#[must_use]
pub fn new(max_voices: usize, poly_mode: PolyMode, steal_mode: StealMode) -> Self {
let n = max_voices.clamp(1, 128);
Self {
voices: (0..n).map(|_| Voice::default()).collect(),
max_voices: n,
poly_mode,
steal_mode,
}
}
#[must_use]
pub fn note_on(&mut self, note: u8, velocity: f32) -> Option<usize> {
match self.poly_mode {
PolyMode::Mono | PolyMode::Legato => {
let v = &mut self.voices[0];
v.active = true;
v.note = note;
v.velocity = velocity;
v.age = 0;
Some(0)
}
PolyMode::Poly => {
if let Some(idx) = self.voices.iter().position(|v| !v.active) {
let v = &mut self.voices[idx];
v.active = true;
v.note = note;
v.velocity = velocity;
v.age = 0;
return Some(idx);
}
let idx = self.find_steal_target()?;
let v = &mut self.voices[idx];
v.active = true;
v.note = note;
v.velocity = velocity;
v.age = 0;
Some(idx)
}
}
}
pub fn note_off(&mut self, note: u8) -> Option<usize> {
if let Some(idx) = self.voices.iter().position(|v| v.active && v.note == note) {
self.voices[idx].active = false;
Some(idx)
} else {
None
}
}
fn find_steal_target(&self) -> Option<usize> {
match self.steal_mode {
StealMode::None => None,
StealMode::Oldest => self
.voices
.iter()
.enumerate()
.filter(|(_, v)| v.active)
.max_by_key(|(_, v)| v.age)
.map(|(i, _)| i),
StealMode::Quietest => self
.voices
.iter()
.enumerate()
.filter(|(_, v)| v.active)
.min_by(|(_, a), (_, b)| {
a.amplitude
.partial_cmp(&b.amplitude)
.unwrap_or(std::cmp::Ordering::Equal)
})
.map(|(i, _)| i),
StealMode::Lowest => self
.voices
.iter()
.enumerate()
.filter(|(_, v)| v.active)
.min_by_key(|(_, v)| v.note)
.map(|(i, _)| i),
}
}
pub fn tick(&mut self) {
for v in &mut self.voices {
if v.active {
v.age = v.age.saturating_add(1);
}
}
}
#[must_use]
pub fn active_count(&self) -> usize {
self.voices.iter().filter(|v| v.active).count()
}
#[inline]
#[must_use]
pub fn voices(&self) -> &[Voice] {
&self.voices
}
#[inline]
pub fn voice_mut(&mut self, index: usize) -> Option<&mut Voice> {
self.voices.get_mut(index)
}
#[must_use]
pub fn max_voices(&self) -> usize {
self.max_voices
}
pub fn all_notes_off(&mut self) {
for v in &mut self.voices {
v.active = false;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_note_on_off() {
let mut vm = VoiceManager::new(4, PolyMode::Poly, StealMode::Oldest);
let idx = vm.note_on(60, 0.8).unwrap();
assert_eq!(vm.active_count(), 1);
assert_eq!(vm.voices()[idx].note, 60);
vm.note_off(60);
assert_eq!(vm.active_count(), 0);
}
#[test]
fn test_poly_fill_and_steal() {
let mut vm = VoiceManager::new(2, PolyMode::Poly, StealMode::Oldest);
let _ = vm.note_on(60, 0.8);
vm.tick();
let _ = vm.note_on(64, 0.7);
vm.tick();
assert_eq!(vm.active_count(), 2);
let idx = vm.note_on(67, 0.9).unwrap();
assert_eq!(vm.voices()[idx].note, 67);
assert_eq!(vm.active_count(), 2);
}
#[test]
fn test_steal_none() {
let mut vm = VoiceManager::new(1, PolyMode::Poly, StealMode::None);
let _ = vm.note_on(60, 0.8);
assert!(vm.note_on(64, 0.7).is_none());
}
#[test]
fn test_mono_mode() {
let mut vm = VoiceManager::new(4, PolyMode::Mono, StealMode::Oldest);
let _ = vm.note_on(60, 0.8);
let _ = vm.note_on(64, 0.7);
assert_eq!(vm.voices()[0].note, 64);
assert_eq!(vm.active_count(), 1);
}
#[test]
fn test_all_notes_off() {
let mut vm = VoiceManager::new(4, PolyMode::Poly, StealMode::Oldest);
let _ = vm.note_on(60, 0.8);
let _ = vm.note_on(64, 0.7);
vm.all_notes_off();
assert_eq!(vm.active_count(), 0);
}
#[test]
fn test_serde_roundtrip() {
let mut vm = VoiceManager::new(4, PolyMode::Poly, StealMode::Oldest);
let _ = vm.note_on(60, 0.8);
let json = serde_json::to_string(&vm).unwrap();
let back: VoiceManager = serde_json::from_str(&json).unwrap();
assert_eq!(vm.active_count(), back.active_count());
assert_eq!(vm.max_voices(), back.max_voices());
}
}