use std::sync::{Arc, Mutex};
pub(crate) struct PlaybackState {
pub(crate) samples: Arc<Vec<f32>>,
pub(crate) source_channels: usize,
pub(crate) sample_rate: u32,
pub(crate) position_samples: usize,
pub(crate) playing: bool,
pub(crate) volume: f32,
dither_state: u64,
}
impl PlaybackState {
pub(crate) fn new(samples: Arc<Vec<f32>>, source_channels: usize, sample_rate: u32) -> Self {
Self {
samples,
source_channels,
sample_rate,
position_samples: 0,
playing: true,
volume: 1.0,
dither_state: 0x9e37_79b9_7f4a_7c15,
}
}
}
pub(crate) fn seek_position_samples(
position_seconds: f64,
sample_rate: u32,
channels: usize,
sample_count: usize,
) -> Option<usize> {
if !position_seconds.is_finite() || position_seconds < 0.0 || sample_rate == 0 || channels == 0
{
return None;
}
let total_frames = sample_count / channels;
let requested_frame = (position_seconds * sample_rate as f64).floor() as usize;
Some(requested_frame.min(total_frames) * channels)
}
pub(crate) fn render_samples(
data: &mut [f32],
output_channels: usize,
playback: &Arc<Mutex<PlaybackState>>,
) {
render_converted_samples(data, output_channels, playback, 0.0, |sample, _| sample);
}
pub(crate) fn render_i32_samples(
data: &mut [i32],
output_channels: usize,
playback: &Arc<Mutex<PlaybackState>>,
) {
render_converted_samples(data, output_channels, playback, 0, |sample, _| {
(sample.clamp(-1.0, 1.0) * i32::MAX as f32).round() as i32
});
}
pub(crate) fn render_i16_samples(
data: &mut [i16],
output_channels: usize,
playback: &Arc<Mutex<PlaybackState>>,
) {
render_converted_samples(data, output_channels, playback, 0, |sample, playback| {
let dithered = sample + tpdf_dither(playback, 1.0 / i16::MAX as f32);
(dithered.clamp(-1.0, 1.0) * i16::MAX as f32).round() as i16
});
}
pub(crate) fn render_u16_samples(
data: &mut [u16],
output_channels: usize,
playback: &Arc<Mutex<PlaybackState>>,
) {
render_converted_samples(
data,
output_channels,
playback,
32_768,
|sample, playback| {
let dithered = sample + tpdf_dither(playback, 1.0 / i16::MAX as f32);
(dithered.clamp(-1.0, 1.0) * i16::MAX as f32 + 32_768.0).round() as u16
},
);
}
fn render_converted_samples<T: Copy>(
data: &mut [T],
output_channels: usize,
playback: &Arc<Mutex<PlaybackState>>,
silence: T,
mut convert: impl FnMut(f32, &mut PlaybackState) -> T,
) {
if output_channels == 0 {
return;
}
let Ok(mut playback) = playback.lock() else {
data.fill(silence);
return;
};
for output_frame in data.chunks_mut(output_channels) {
if !playback.playing
|| playback.position_samples + playback.source_channels > playback.samples.len()
{
output_frame.fill(silence);
playback.playing = false;
continue;
}
for (output_channel, sample) in output_frame.iter_mut().enumerate() {
let source_channel = output_channel * playback.source_channels / output_channels;
let sample_index = playback.position_samples + source_channel;
let source_sample = playback.samples[sample_index];
*sample = convert(source_sample * playback.volume, &mut playback);
}
playback.position_samples += playback.source_channels;
}
}
fn tpdf_dither(playback: &mut PlaybackState, amplitude: f32) -> f32 {
(next_random(playback) - next_random(playback)) * amplitude
}
fn next_random(playback: &mut PlaybackState) -> f32 {
let mut state = playback.dither_state;
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
playback.dither_state = state;
(state as f64 / u64::MAX as f64) as f32
}