use std::time::Duration;
use sim_lib_sound_core::{Frequency, Tone};
use sim_lib_sound_timbre::Timbre;
use crate::{SoundBridgeError, TimbreBank};
#[derive(Clone, Debug, PartialEq)]
pub struct ScheduledTone {
pub start: Duration,
pub tone: Tone,
pub pan: f32,
pub channel: u8,
pub key: u8,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum VoicePhase {
Held,
SustainHeld,
Released,
}
#[derive(Clone, Debug, PartialEq)]
pub struct Voice {
pub channel: u8,
pub key: u8,
pub started_at: Duration,
pub release_at: Option<Duration>,
pub release_end: Option<Duration>,
pub frequency: Frequency,
pub amplitude_gain: f64,
pub pan: f32,
pub phase: VoicePhase,
pub timbre: Timbre,
}
impl Voice {
fn to_scheduled_tone(&self) -> ScheduledTone {
let released_at = self.release_at.unwrap_or(self.started_at);
let total_duration =
released_at.saturating_sub(self.started_at) + self.timbre.default_envelope.release;
let tone = self
.timbre
.render(self.frequency, total_duration)
.amplify(self.amplitude_gain);
ScheduledTone {
start: self.started_at,
tone,
pan: self.pan,
channel: self.channel,
key: self.key,
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct VoicePool {
polyphony_limit: usize,
voices: Vec<Voice>,
emitted: Vec<ScheduledTone>,
stolen_voice_count: usize,
}
impl VoicePool {
pub fn new(polyphony_limit: usize) -> Result<Self, SoundBridgeError> {
if polyphony_limit == 0 {
return Err(SoundBridgeError::ZeroPolyphony);
}
Ok(Self {
polyphony_limit,
voices: Vec::new(),
emitted: Vec::new(),
stolen_voice_count: 0,
})
}
pub fn polyphony_limit(&self) -> usize {
self.polyphony_limit
}
pub fn active_voice_count(&self) -> usize {
self.voices.len()
}
pub fn voices(&self) -> &[Voice] {
&self.voices
}
pub fn emitted(&self) -> &[ScheduledTone] {
&self.emitted
}
pub fn stolen_voice_count(&self) -> usize {
self.stolen_voice_count
}
pub fn drain_emitted(&mut self) -> Vec<ScheduledTone> {
std::mem::take(&mut self.emitted)
}
pub fn note_on(&mut self, mut voice: Voice, now: Duration) {
self.reap_finished(now);
self.note_off(voice.channel, voice.key, now, false);
if self.voices.len() >= self.polyphony_limit {
self.steal_voice(now);
}
voice.phase = VoicePhase::Held;
voice.release_at = None;
voice.release_end = None;
self.voices.push(voice);
}
pub fn note_off(&mut self, channel: u8, key: u8, now: Duration, sustain: bool) {
self.reap_finished(now);
if let Some(voice) = self
.voices
.iter_mut()
.rev()
.find(|voice| voice.channel == channel && voice.key == key)
{
if sustain {
voice.phase = VoicePhase::SustainHeld;
} else {
voice.phase = VoicePhase::Released;
voice.release_at = Some(now);
voice.release_end = Some(now + voice.timbre.default_envelope.release);
}
}
}
pub fn release_channel_sustain(&mut self, channel: u8, now: Duration) {
self.reap_finished(now);
for voice in &mut self.voices {
if voice.channel == channel && voice.phase == VoicePhase::SustainHeld {
voice.phase = VoicePhase::Released;
voice.release_at = Some(now);
voice.release_end = Some(now + voice.timbre.default_envelope.release);
}
}
}
pub fn transpose_channel_cents(&mut self, channel: u8, cents: f64) {
for voice in &mut self.voices {
if voice.channel == channel && voice.phase != VoicePhase::Released {
voice.frequency = voice.frequency.shift_cents(cents);
}
}
}
pub fn reap_finished(&mut self, now: Duration) {
let mut keep = Vec::with_capacity(self.voices.len());
for voice in self.voices.drain(..) {
if voice.release_end.is_some_and(|release_end| {
release_end <= now && voice.phase == VoicePhase::Released
}) {
self.emitted.push(voice.to_scheduled_tone());
} else {
keep.push(voice);
}
}
self.voices = keep;
}
pub fn flush_all(&mut self, now: Duration) {
for voice in &mut self.voices {
if voice.phase != VoicePhase::Released {
voice.phase = VoicePhase::Released;
voice.release_at = Some(now);
voice.release_end = Some(now + voice.timbre.default_envelope.release);
}
}
let max_end = self
.voices
.iter()
.filter_map(|voice| voice.release_end)
.max()
.unwrap_or(now);
self.reap_finished(max_end);
}
fn steal_voice(&mut self, now: Duration) {
let release_candidate = self
.voices
.iter()
.enumerate()
.filter(|(_, voice)| voice.phase == VoicePhase::Released)
.min_by_key(|(_, voice)| voice.release_at.unwrap_or(voice.started_at))
.map(|(index, _)| index);
let active_candidate = self
.voices
.iter()
.enumerate()
.min_by_key(|(_, voice)| voice.started_at)
.map(|(index, _)| index);
let index = release_candidate.or(active_candidate).unwrap_or(0);
let mut stolen = self.voices.swap_remove(index);
if stolen.release_at.is_none() {
stolen.release_at = Some(now);
stolen.release_end = Some(now);
}
self.stolen_voice_count += 1;
self.emitted.push(stolen.to_scheduled_tone());
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct BridgeChannelState {
pub bank_msb: u8,
pub bank_lsb: u8,
pub program: u8,
pub volume: f64,
pub expression: f64,
pub pan: f32,
pub sustain: bool,
pub pitch_bend_cents: f64,
}
impl Default for BridgeChannelState {
fn default() -> Self {
Self {
bank_msb: 0,
bank_lsb: 0,
program: 0,
volume: 1.0,
expression: 1.0,
pan: 0.0,
sustain: false,
pitch_bend_cents: 0.0,
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct BridgeOptions {
pub polyphony_limit: usize,
pub bend_range_cents: f64,
}
impl Default for BridgeOptions {
fn default() -> Self {
Self {
polyphony_limit: 32,
bend_range_cents: 200.0,
}
}
}
impl BridgeOptions {
pub fn new(polyphony_limit: usize, bend_range_cents: f64) -> Result<Self, SoundBridgeError> {
if polyphony_limit == 0 {
return Err(SoundBridgeError::ZeroPolyphony);
}
Ok(Self {
polyphony_limit,
bend_range_cents,
})
}
}
pub(crate) fn tone_gain(channel: &BridgeChannelState, velocity: u8) -> f64 {
let velocity_gain = f64::from(velocity) / 127.0;
velocity_gain * channel.volume * channel.expression
}
pub(crate) fn current_timbre<'a>(bank: &'a TimbreBank, channel: &BridgeChannelState) -> &'a Timbre {
bank.get(channel.bank_msb, channel.bank_lsb, channel.program)
}