use std::sync::{
Arc,
atomic::{AtomicBool, AtomicU32, AtomicUsize, Ordering},
};
use crate::{Error, Result};
const MAX_RATE: u32 = 192_000;
const UNITY: u32 = 65_536;
struct VoiceState {
stopped: AtomicBool,
finished: AtomicBool,
volume: AtomicU32,
position: AtomicUsize,
handles: AtomicUsize,
}
pub struct SfxVoiceHandle {
state: Arc<VoiceState>,
}
impl SfxVoiceHandle {
pub fn stop(&self) {
self.state.stopped.store(true, Ordering::Release);
}
pub fn set_volume(&self, volume: f32) {
let volume = if volume.is_finite() {
volume.clamp(0.0, 1.0)
} else {
0.0
};
self.state
.volume
.store((volume * UNITY as f32).round() as u32, Ordering::Release);
}
pub fn is_finished(&self) -> bool {
self.state.finished.load(Ordering::Acquire) || self.state.stopped.load(Ordering::Acquire)
}
pub fn samples_played(&self) -> usize {
self.state.position.load(Ordering::Acquire)
}
}
impl Clone for SfxVoiceHandle {
fn clone(&self) -> Self {
self.state.handles.fetch_add(1, Ordering::Relaxed);
Self {
state: Arc::clone(&self.state),
}
}
}
impl Drop for SfxVoiceHandle {
fn drop(&mut self) {
if self.state.handles.fetch_sub(1, Ordering::AcqRel) == 1 {
self.stop();
}
}
}
struct Voice {
samples: Arc<[u8]>,
state: Arc<VoiceState>,
source_rate: u32,
position: usize,
phase: u32,
finished: bool,
}
impl Voice {
fn inactive(&self) -> bool {
self.finished || self.state.stopped.load(Ordering::Acquire)
}
fn next(&mut self, output_rate: u32) -> i64 {
if self.inactive() {
self.finished = true;
self.state.finished.store(true, Ordering::Release);
return 0;
}
let signed = i64::from(self.samples[self.position]) - 128;
let value = signed * i64::from(self.state.volume.load(Ordering::Acquire));
self.phase += self.source_rate;
self.position = self
.position
.saturating_add((self.phase / output_rate) as usize)
.min(self.samples.len());
self.phase %= output_rate;
self.state.position.store(self.position, Ordering::Release);
if self.position == self.samples.len() {
self.finished = true;
self.state.finished.store(true, Ordering::Release);
}
value
}
}
pub struct SfxMixer {
voices: Vec<Option<Voice>>,
sample_rate: u32,
}
impl SfxMixer {
pub fn new(voice_limit: usize, sample_rate: u32) -> Result<Self> {
if !(1..=1024).contains(&voice_limit) {
return Err(Error::Config("SFX voice limit must be between 1 and 1024"));
}
Self::validate_rate(sample_rate)?;
Ok(Self {
voices: std::iter::repeat_with(|| None).take(voice_limit).collect(),
sample_rate,
})
}
fn validate_rate(rate: u32) -> Result<()> {
if !(1..=MAX_RATE).contains(&rate) {
return Err(Error::Config(
"SFX sample rate must be between 1 and 192000 Hz",
));
}
Ok(())
}
pub fn trigger(
&mut self,
samples: impl Into<Arc<[u8]>>,
source_rate: u32,
) -> Result<SfxVoiceHandle> {
Self::validate_rate(source_rate)?;
let samples = samples.into();
if samples.is_empty() {
return Err(Error::Config("SFX sample must not be empty"));
}
let state = Arc::new(VoiceState {
stopped: AtomicBool::new(false),
finished: AtomicBool::new(false),
volume: AtomicU32::new(UNITY),
position: AtomicUsize::new(0),
handles: AtomicUsize::new(1),
});
let voice = Voice {
samples,
state: Arc::clone(&state),
source_rate,
position: 0,
phase: 0,
finished: false,
};
let index = self
.voices
.iter()
.position(|slot| slot.as_ref().is_none_or(Voice::inactive))
.unwrap_or(0);
if let Some(previous) = self.voices[index].take() {
previous.state.finished.store(true, Ordering::Release);
}
self.voices[index..].rotate_left(1);
*self
.voices
.last_mut()
.expect("validated nonzero voice limit") = Some(voice);
Ok(SfxVoiceHandle { state })
}
pub fn render(&mut self, out: &mut [u8]) {
for sample in out {
let mut sum = 0_i64;
for voice in self.voices.iter_mut().flatten() {
sum += voice.next(self.sample_rate);
}
*sample = ((sum / i64::from(UNITY)).clamp(-128, 127) + 128) as u8;
}
}
pub fn sample_rate(&self) -> u32 {
self.sample_rate
}
pub fn active_voices(&self) -> usize {
self.voices
.iter()
.flatten()
.filter(|voice| !voice.inactive())
.count()
}
}
impl Drop for SfxMixer {
fn drop(&mut self) {
for voice in self.voices.iter().flatten() {
voice.state.finished.store(true, Ordering::Release);
}
}
}