use crate::dma_sai::DmaSai1Tx;
use crate::hwcore::dca::{Bank, DbufCpu, DbufRead, DcaCacheCtx, DcaDoubleBuf};
use crate::sai::Sai1Audio;
use crate::wav::WavHeader;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PollResult {
Idle,
Playing,
Finished,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum PlayState {
Idle,
Ready,
Playing,
Draining,
}
enum DcaState<const BUF_BYTES: usize> {
Cpu(DbufCpu<'static, 'static, u8, BUF_BYTES>),
Active(DbufRead<'static, 'static, u8, BUF_BYTES>),
Transitioning,
}
pub struct AudioPlayer<const BUF_BYTES: usize> {
dma: DmaSai1Tx,
scb: cortex_m::peripheral::SCB,
dca: DcaState<BUF_BYTES>,
state: PlayState,
data_file_offset: u32,
data_total: u32,
bytes_queued: u32,
last_target: u8,
}
impl<const BUF_BYTES: usize> AudioPlayer<BUF_BYTES> {
pub unsafe fn new(
dca: &'static mut DcaDoubleBuf<'static, u8, BUF_BYTES>, ) -> Self {
let scb = unsafe { cortex_m::Peripherals::steal().SCB };
let dca_cpu: DbufCpu<'static, 'static, u8, BUF_BYTES> = unsafe {
let p = dca as *mut DcaDoubleBuf<'static, u8, BUF_BYTES>;
(*p).cpu()
};
Self {
dma: DmaSai1Tx::new(),
scb,
dca: DcaState::Cpu(dca_cpu),
state: PlayState::Idle,
data_file_offset: 0,
data_total: 0,
bytes_queued: 0,
last_target: 0,
}
}
pub fn prepare(&mut self, header: &WavHeader) -> bool {
if header.sample_rate != 48_000 || header.bits_per_sample != 16 || header.num_channels != 2
{
return false;
}
self.data_file_offset = header.data_offset;
self.data_total = header.data_length;
self.bytes_queued = core::cmp::min((BUF_BYTES as u32) * 2, header.data_length);
self.last_target = 0;
self.state = PlayState::Ready;
true
}
pub fn start(&mut self, sai: &Sai1Audio) {
if self.state != PlayState::Ready {
return;
}
let prev = core::mem::replace(&mut self.dca, DcaState::Transitioning);
let cpu = match prev {
DcaState::Cpu(c) => c,
DcaState::Active(_) | DcaState::Transitioning => {
self.dca = prev;
return;
}
};
self.dma.enable_clock();
let mut ctx = DcaCacheCtx::new(&mut self.scb);
let dbuf_read = cpu.start_double_buffer_read(&mut ctx);
let m0 = dbuf_read.m0_dma_addr().raw() as *const u8;
let m1 = dbuf_read.m1_dma_addr().raw() as *const u8;
drop(ctx);
let periph_addr = sai.tx_data_register_addr();
self.dma.configure(periph_addr, m0, m1, BUF_BYTES);
sai.enable_dma_tx();
self.dma.start();
self.dca = DcaState::Active(dbuf_read);
self.state = PlayState::Playing;
}
pub fn poll_refill<F>(&mut self, refill: F) -> PollResult
where
F: FnOnce(&mut [u8], u32) -> usize,
{
match self.state {
PlayState::Idle | PlayState::Ready => return PollResult::Idle,
PlayState::Playing => {}
PlayState::Draining => return self.poll_draining(),
}
if !self.dma.transfer_complete() {
return PollResult::Playing;
}
self.dma.clear_transfer_complete();
let remaining = self.data_total.saturating_sub(self.bytes_queued);
if remaining == 0 {
self.state = PlayState::Draining;
return PollResult::Playing;
}
let max_bytes = core::cmp::min(remaining as usize, BUF_BYTES);
let file_offset = self.data_file_offset + self.bytes_queued;
let target_bit = self.dma.current_target();
let current_target = if target_bit == 0 { Bank::M0 } else { Bank::M1 };
self.last_target = target_bit;
let dbuf_read = match &mut self.dca {
DcaState::Active(d) => d,
_ => return PollResult::Idle,
};
let mut ctx = DcaCacheCtx::new(&mut self.scb);
let mut guard = dbuf_read.bank_guard(&mut ctx, current_target);
let bank_slice: &mut [u8; BUF_BYTES] = guard.as_mut_slice();
let pcm_bytes = refill(&mut bank_slice[..max_bytes], file_offset);
let new_target_bit = self.dma.current_target();
let new_target = if new_target_bit == 0 {
Bank::M0
} else {
Bank::M1
};
let _ = guard.release(&mut ctx, new_target);
self.bytes_queued += pcm_bytes as u32;
if self.bytes_queued >= self.data_total {
self.state = PlayState::Draining;
}
PollResult::Playing
}
pub fn stop(&mut self, sai: &Sai1Audio) {
self.dma.stop();
sai.disable_dma_tx();
self.state = PlayState::Idle;
}
pub fn is_playing(&self) -> bool {
matches!(self.state, PlayState::Playing | PlayState::Draining)
}
fn poll_draining(&mut self) -> PollResult {
if self.dma.transfer_complete() {
self.dma.clear_transfer_complete();
self.state = PlayState::Idle;
return PollResult::Finished;
}
PollResult::Playing
}
}