use crate::{ChiptunePlayerBase, PlaybackState};
pub const DEFAULT_CACHE_SIZE: usize = 512;
#[derive(Clone)]
pub struct SampleCache {
samples: Vec<f32>,
channel_outputs: Vec<[f32; 3]>,
pos: usize,
len: usize,
size: usize,
}
impl SampleCache {
#[must_use]
pub fn new(size: usize) -> Self {
Self {
samples: vec![0.0; size],
channel_outputs: vec![[0.0; 3]; size],
pos: 0,
len: 0,
size,
}
}
#[inline]
#[must_use]
pub fn needs_refill(&self) -> bool {
self.pos >= self.len
}
pub fn sample_buffer_mut(&mut self) -> &mut [f32] {
&mut self.samples[..self.size]
}
pub fn fill_channel_outputs(&mut self, outputs: [f32; 3]) {
self.channel_outputs[..self.size].fill(outputs);
}
pub fn mark_filled(&mut self) {
self.pos = 0;
self.len = self.size;
}
#[inline]
pub fn next_sample(&mut self) -> f32 {
if self.len == 0 {
return 0.0;
}
let sample = self.samples[self.pos];
self.pos += 1;
sample
}
#[must_use]
pub fn channel_outputs(&self) -> [f32; 3] {
if self.pos > 0 && self.pos <= self.len {
self.channel_outputs[self.pos - 1]
} else if !self.channel_outputs.is_empty() {
self.channel_outputs[0]
} else {
[0.0, 0.0, 0.0]
}
}
pub fn reset(&mut self) {
self.pos = 0;
self.len = 0;
}
#[must_use]
pub fn size(&self) -> usize {
self.size
}
}
impl Default for SampleCache {
fn default() -> Self {
Self::new(DEFAULT_CACHE_SIZE)
}
}
pub trait CacheablePlayer: ChiptunePlayerBase {
fn get_channel_outputs(&self) -> [f32; 3];
fn on_cache_refill(&mut self) {}
}
pub struct CachedPlayer<P: CacheablePlayer> {
player: P,
cache: SampleCache,
}
impl<P: CacheablePlayer> CachedPlayer<P> {
pub fn new(player: P, cache_size: usize) -> Self {
Self {
player,
cache: SampleCache::new(cache_size),
}
}
pub fn with_default_cache(player: P) -> Self {
Self::new(player, DEFAULT_CACHE_SIZE)
}
pub fn inner(&self) -> &P {
&self.player
}
pub fn inner_mut(&mut self) -> &mut P {
&mut self.player
}
pub fn into_inner(self) -> P {
self.player
}
pub fn generate_sample(&mut self) -> f32 {
if self.cache.needs_refill() {
self.refill_cache();
}
self.cache.next_sample()
}
pub fn cached_channel_outputs(&self) -> [f32; 3] {
self.cache.channel_outputs()
}
pub fn reset_cache(&mut self) {
self.cache.reset();
}
fn refill_cache(&mut self) {
self.player.on_cache_refill();
self.player
.generate_samples_into(self.cache.sample_buffer_mut());
self.cache
.fill_channel_outputs(self.player.get_channel_outputs());
self.cache.mark_filled();
}
}
impl<P: CacheablePlayer> ChiptunePlayerBase for CachedPlayer<P> {
fn play(&mut self) {
self.player.play();
}
fn pause(&mut self) {
self.player.pause();
}
fn stop(&mut self) {
self.player.stop();
self.reset_cache();
}
fn state(&self) -> PlaybackState {
self.player.state()
}
fn generate_samples_into(&mut self, buffer: &mut [f32]) {
self.player.generate_samples_into(buffer);
}
fn sample_rate(&self) -> u32 {
self.player.sample_rate()
}
fn set_channel_mute(&mut self, channel: usize, mute: bool) {
self.player.set_channel_mute(channel, mute);
}
fn is_channel_muted(&self, channel: usize) -> bool {
self.player.is_channel_muted(channel)
}
fn playback_position(&self) -> f32 {
self.player.playback_position()
}
fn subsong_count(&self) -> usize {
self.player.subsong_count()
}
fn current_subsong(&self) -> usize {
self.player.current_subsong()
}
fn set_subsong(&mut self, index: usize) -> bool {
if self.player.set_subsong(index) {
self.reset_cache();
true
} else {
false
}
}
fn psg_count(&self) -> usize {
self.player.psg_count()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sample_cache_basic() {
let mut cache = SampleCache::new(4);
assert!(cache.needs_refill());
cache
.sample_buffer_mut()
.copy_from_slice(&[1.0, 2.0, 3.0, 4.0]);
cache.fill_channel_outputs([0.1, 0.2, 0.3]);
cache.mark_filled();
assert!(!cache.needs_refill());
assert_eq!(cache.next_sample(), 1.0);
assert_eq!(cache.next_sample(), 2.0);
assert_eq!(cache.channel_outputs(), [0.1, 0.2, 0.3]);
}
#[test]
fn test_sample_cache_reset() {
let mut cache = SampleCache::new(4);
cache
.sample_buffer_mut()
.copy_from_slice(&[1.0, 2.0, 3.0, 4.0]);
cache.mark_filled();
let _ = cache.next_sample();
cache.reset();
assert!(cache.needs_refill());
}
struct MockPlayer {
samples_generated: usize,
state: PlaybackState,
}
impl MockPlayer {
fn new() -> Self {
Self {
samples_generated: 0,
state: PlaybackState::Playing,
}
}
}
impl ChiptunePlayerBase for MockPlayer {
fn play(&mut self) {
self.state = PlaybackState::Playing;
}
fn pause(&mut self) {
self.state = PlaybackState::Paused;
}
fn stop(&mut self) {
self.state = PlaybackState::Stopped;
}
fn state(&self) -> PlaybackState {
self.state
}
fn generate_samples_into(&mut self, buffer: &mut [f32]) {
for (i, sample) in buffer.iter_mut().enumerate() {
*sample = (self.samples_generated + i) as f32 * 0.001;
}
self.samples_generated += buffer.len();
}
}
impl CacheablePlayer for MockPlayer {
fn get_channel_outputs(&self) -> [f32; 3] {
[0.1, 0.2, 0.3]
}
}
#[test]
fn test_cached_player_generates_samples() {
let player = MockPlayer::new();
let mut cached = CachedPlayer::new(player, 16);
let s1 = cached.generate_sample();
let s2 = cached.generate_sample();
assert!((s1 - 0.0).abs() < 0.0001);
assert!((s2 - 0.001).abs() < 0.0001);
}
#[test]
fn test_cached_player_channel_outputs() {
let player = MockPlayer::new();
let mut cached = CachedPlayer::new(player, 16);
let _ = cached.generate_sample();
let channels = cached.cached_channel_outputs();
assert_eq!(channels, [0.1, 0.2, 0.3]);
}
#[test]
fn test_cache_reset_on_stop() {
let player = MockPlayer::new();
let mut cached = CachedPlayer::new(player, 16);
let _ = cached.generate_sample();
cached.stop();
assert!(cached.cache.needs_refill());
}
}