use serde::{Deserialize, Serialize};
pub use abaco::dsp::{A4_FREQUENCY, A4_MIDI_NOTE, SEMITONES_PER_OCTAVE};
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VoiceState {
Idle,
Active,
Releasing,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub enum VoiceStealMode {
Oldest,
Quietest,
Lowest,
None,
}
#[derive(Debug, Clone)]
pub struct Voice {
pub(crate) state: VoiceState,
pub(crate) note: u8,
pub(crate) velocity: u8,
pub(crate) channel: u8,
pub(crate) envelope_level: f32,
pub(crate) age: u64,
pub(crate) pitch_bend: f32,
pub(crate) pressure: f32,
pub(crate) brightness: f32,
}
impl Voice {
pub fn new() -> Self {
Self {
state: VoiceState::Idle,
note: 0,
velocity: 0,
channel: 0,
envelope_level: 0.0,
age: 0,
pitch_bend: 0.0,
pressure: 0.0,
brightness: 0.0,
}
}
pub fn state(&self) -> VoiceState {
self.state
}
pub fn note(&self) -> u8 {
self.note
}
pub fn velocity(&self) -> u8 {
self.velocity
}
pub fn channel(&self) -> u8 {
self.channel
}
pub fn envelope_level(&self) -> f32 {
self.envelope_level
}
pub fn age(&self) -> u64 {
self.age
}
pub fn pitch_bend(&self) -> f32 {
self.pitch_bend
}
pub fn pressure(&self) -> f32 {
self.pressure
}
pub fn brightness(&self) -> f32 {
self.brightness
}
#[inline]
pub fn is_idle(&self) -> bool {
self.state == VoiceState::Idle
}
#[inline]
pub fn is_active(&self) -> bool {
matches!(self.state, VoiceState::Active | VoiceState::Releasing)
}
#[inline]
pub fn frequency(&self) -> f64 {
abaco::dsp::midi_to_freq(self.note as f64)
}
pub fn apply_per_note_cc(&mut self, controller: u8, value_normalized: f32) {
if controller == 74 {
self.brightness = value_normalized.clamp(0.0, 1.0);
}
}
}
impl Default for Voice {
fn default() -> Self {
Self::new()
}
}
#[must_use]
#[derive(Debug)]
pub struct VoiceManager {
voices: Vec<Voice>,
max_voices: usize,
steal_mode: VoiceStealMode,
}
impl VoiceManager {
pub fn new(max_voices: usize, steal_mode: VoiceStealMode) -> Self {
Self {
voices: (0..max_voices).map(|_| Voice::new()).collect(),
max_voices,
steal_mode,
}
}
pub fn note_on(&mut self, note: u8, velocity: u8, channel: u8) -> Option<usize> {
if let Some(idx) = self.voices.iter().position(|v| v.is_idle()) {
self.activate_voice(idx, note, velocity, channel);
return Some(idx);
}
let steal_idx = match self.steal_mode {
VoiceStealMode::Oldest => self
.voices
.iter()
.enumerate()
.max_by_key(|(_, v)| v.age)
.map(|(i, _)| i),
VoiceStealMode::Quietest => self
.voices
.iter()
.enumerate()
.min_by(|(_, a), (_, b)| {
a.envelope_level
.partial_cmp(&b.envelope_level)
.unwrap_or(std::cmp::Ordering::Equal)
})
.map(|(i, _)| i),
VoiceStealMode::Lowest => self
.voices
.iter()
.enumerate()
.min_by_key(|(_, v)| v.note)
.map(|(i, _)| i),
VoiceStealMode::None => None,
};
if let Some(idx) = steal_idx {
self.activate_voice(idx, note, velocity, channel);
Some(idx)
} else {
None
}
}
pub fn note_off(&mut self, note: u8, channel: u8) {
for voice in &mut self.voices {
if voice.state == VoiceState::Active && voice.note == note && voice.channel == channel {
voice.state = VoiceState::Releasing;
return;
}
}
}
pub fn free_voice(&mut self, index: usize) {
if let Some(voice) = self.voices.get_mut(index) {
voice.state = VoiceState::Idle;
voice.age = 0;
}
}
pub fn active_count(&self) -> usize {
self.voices.iter().filter(|v| !v.is_idle()).count()
}
pub fn tick_age(&mut self) {
for voice in &mut self.voices {
if voice.is_active() {
voice.age = voice.age.saturating_add(1);
}
}
}
pub fn reset(&mut self) {
for voice in &mut self.voices {
*voice = Voice::new();
}
}
pub fn voice(&self, index: usize) -> Option<&Voice> {
self.voices.get(index)
}
pub fn voice_mut(&mut self, index: usize) -> Option<&mut Voice> {
self.voices.get_mut(index)
}
pub fn capacity(&self) -> usize {
self.max_voices
}
pub fn steal_mode(&self) -> VoiceStealMode {
self.steal_mode
}
fn activate_voice(&mut self, idx: usize, note: u8, velocity: u8, channel: u8) {
let voice = &mut self.voices[idx];
voice.state = VoiceState::Active;
voice.note = note;
voice.velocity = velocity;
voice.channel = channel;
voice.envelope_level = 0.0;
voice.age = 0;
voice.pitch_bend = 0.0;
voice.pressure = 0.0;
voice.brightness = 0.0;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn voice_frequency() {
let mut voice = Voice::new();
voice.note = 69; assert!((voice.frequency() - 440.0).abs() < 0.01);
voice.note = 60; assert!((voice.frequency() - 261.63).abs() < 0.1);
voice.note = 0;
assert!(voice.frequency() > 0.0);
assert!(voice.frequency() < 10.0); }
#[test]
fn voice_state() {
let voice = Voice::new();
assert!(voice.is_idle());
assert!(!voice.is_active());
}
#[test]
fn voice_per_note_cc() {
let mut voice = Voice::new();
voice.apply_per_note_cc(74, 0.8);
assert!((voice.brightness() - 0.8).abs() < f32::EPSILON);
voice.apply_per_note_cc(1, 0.5);
assert!((voice.brightness() - 0.8).abs() < f32::EPSILON);
}
#[test]
fn voice_manager_basic() {
let mut mgr = VoiceManager::new(4, VoiceStealMode::Oldest);
assert_eq!(mgr.active_count(), 0);
let idx = mgr.note_on(60, 100, 0);
assert_eq!(idx, Some(0));
assert_eq!(mgr.active_count(), 1);
mgr.note_off(60, 0);
assert_eq!(mgr.voice(0).unwrap().state(), VoiceState::Releasing);
mgr.free_voice(0);
assert_eq!(mgr.active_count(), 0);
}
#[test]
fn voice_stealing_oldest() {
let mut mgr = VoiceManager::new(2, VoiceStealMode::Oldest);
mgr.note_on(60, 100, 0); mgr.tick_age(); mgr.note_on(62, 100, 0); mgr.tick_age();
let idx = mgr.note_on(64, 100, 0);
assert_eq!(idx, Some(0));
assert_eq!(mgr.voice(0).unwrap().note(), 64);
}
#[test]
fn voice_stealing_quietest() {
let mut mgr = VoiceManager::new(2, VoiceStealMode::Quietest);
mgr.note_on(60, 100, 0);
mgr.voice_mut(0).unwrap().envelope_level = 0.8;
mgr.note_on(62, 100, 0);
mgr.voice_mut(1).unwrap().envelope_level = 0.2;
let idx = mgr.note_on(64, 100, 0);
assert_eq!(idx, Some(1));
}
#[test]
fn voice_stealing_lowest() {
let mut mgr = VoiceManager::new(2, VoiceStealMode::Lowest);
mgr.note_on(72, 100, 0); mgr.note_on(48, 100, 0);
let idx = mgr.note_on(60, 100, 0);
assert_eq!(idx, Some(1));
assert_eq!(mgr.voice(1).unwrap().note(), 60);
}
#[test]
fn voice_stealing_none() {
let mut mgr = VoiceManager::new(2, VoiceStealMode::None);
mgr.note_on(60, 100, 0);
mgr.note_on(62, 100, 0);
let idx = mgr.note_on(64, 100, 0);
assert_eq!(idx, None);
}
#[test]
fn voice_manager_reset() {
let mut mgr = VoiceManager::new(4, VoiceStealMode::Oldest);
mgr.note_on(60, 100, 0);
mgr.note_on(62, 90, 0);
assert_eq!(mgr.active_count(), 2);
mgr.reset();
assert_eq!(mgr.active_count(), 0);
}
#[test]
fn voice_reuse_after_free() {
let mut mgr = VoiceManager::new(2, VoiceStealMode::None);
let _idx0 = mgr.note_on(60, 100, 0).unwrap();
let idx1 = mgr.note_on(62, 100, 0).unwrap();
assert_eq!(mgr.note_on(64, 100, 0), None);
mgr.free_voice(idx1);
let idx2 = mgr.note_on(64, 100, 0);
assert_eq!(idx2, Some(idx1)); }
#[test]
fn tick_age_only_active() {
let mut mgr = VoiceManager::new(2, VoiceStealMode::Oldest);
mgr.note_on(60, 100, 0);
mgr.tick_age();
mgr.tick_age();
assert_eq!(mgr.voice(0).unwrap().age(), 2);
assert_eq!(mgr.voice(1).unwrap().age(), 0); }
}