use crate::dma_sai::DmaSai1Tx;
use crate::sai::Sai1Audio;
use crate::wav::WavHeader;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PollResult {
Idle,
Playing,
NeedRefill {
buf: *mut u8,
file_offset: u32,
max_bytes: usize,
},
Finished,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum PlayState {
Idle,
Ready,
Playing,
Draining,
}
pub struct AudioPlayer {
dma: DmaSai1Tx,
buf0: *mut u8,
buf1: *mut u8,
buf_bytes: usize,
state: PlayState,
data_file_offset: u32,
data_total: u32,
bytes_queued: u32,
last_target: u8,
}
impl AudioPlayer {
pub fn new(buf0: *mut u8, buf1: *mut u8, buf_bytes: usize) -> Self {
Self {
dma: DmaSai1Tx::new(),
buf0,
buf1,
buf_bytes,
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((self.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;
}
self.dma.enable_clock();
let periph_addr = sai.tx_data_register_addr();
self.dma
.configure(periph_addr, self.buf0, self.buf1, self.buf_bytes);
sai.enable_dma_tx();
self.dma.start();
self.state = PlayState::Playing;
}
pub fn poll(&mut self) -> PollResult {
match self.state {
PlayState::Idle => PollResult::Idle,
PlayState::Ready => PollResult::Idle, PlayState::Playing => self.poll_playing(),
PlayState::Draining => self.poll_draining(),
}
}
pub fn refill_done(&mut self, pcm_bytes: usize) {
self.bytes_queued += pcm_bytes as u32;
let target = self.dma.current_target();
let buf = if target == 0 { self.buf1 } else { self.buf0 };
clean_dcache(buf, self.buf_bytes);
if self.bytes_queued >= self.data_total {
self.state = PlayState::Draining;
}
}
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_playing(&mut self) -> PollResult {
if !self.dma.transfer_complete() {
return PollResult::Playing;
}
self.dma.clear_transfer_complete();
let current = self.dma.current_target();
let (free_buf, _free_idx) = if current == 1 {
(self.buf0, 0u8)
} else {
(self.buf1, 1u8)
};
self.last_target = current;
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, self.buf_bytes);
let file_offset = self.data_file_offset + self.bytes_queued;
PollResult::NeedRefill {
buf: free_buf,
file_offset,
max_bytes,
}
}
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
}
}
fn clean_dcache(ptr: *const u8, len: usize) {
const DCCMVAC: *mut u32 = 0xE000_EF68 as *mut u32;
const CACHE_LINE: usize = 32;
let start = (ptr as usize) & !(CACHE_LINE - 1);
let end = ((ptr as usize) + len + CACHE_LINE - 1) & !(CACHE_LINE - 1);
let mut addr = start;
while addr < end {
unsafe {
DCCMVAC.write_volatile(addr as u32);
}
addr += CACHE_LINE;
}
unsafe {
core::arch::asm!("dsb sy", options(nostack, preserves_flags));
}
}