use core::{convert::Infallible, fmt};
use esp_hal::{
Async,
dma::DmaTxStreamBuf,
dma_tx_stream_buffer,
i2s::master::{Channels, DataFormat, I2s, I2sTx, I2sTxDmaTransfer, TdmConfig},
peripherals::{DMA_CH0, GPIO41, GPIO42, GPIO43, I2S0},
time::Rate,
};
use crate::Error;
pub const SAMPLE_RATE: u32 = 44_100;
const SPEAKER_BUFFER: usize = 4096;
const SPEAKER_CHUNK: usize = 512;
#[expect(missing_docs, reason = "the fields are the pins they're named after")]
#[derive(Debug)]
pub struct SpeakerPins {
pub i2s: I2S0<'static>,
pub dma: DMA_CH0<'static>,
pub bclk: GPIO41<'static>,
pub ws: GPIO43<'static>,
pub dout: GPIO42<'static>,
}
type SpeakerTransfer = I2sTxDmaTransfer<'static, Async, DmaTxStreamBuf>;
const SILENCE: [u8; 64] = [0; 64];
pub struct Speaker {
transfer: Option<SpeakerTransfer>,
}
impl Speaker {
pub async fn run(&mut self, fill: impl FnMut(&mut [i16])) -> Result<Infallible, Error> {
self.stream(fill, || ()).await
}
pub(crate) async fn stream(
&mut self,
mut fill: impl FnMut(&mut [i16]),
mut restarted: impl FnMut(),
) -> Result<Infallible, Error> {
let mut mono = [0i16; 256];
loop {
let transfer = self.transfer.as_mut().ok_or(Error::Speaker)?;
if transfer.wait_for_available_async().await.is_err() {
self.restart()?;
restarted();
continue;
}
loop {
let pushed = transfer.push_with(|bytes| {
debug_assert!(bytes.is_empty() || bytes.len() >= 4);
let frames = (bytes.len() / 4).min(mono.len());
fill(&mut mono[..frames]);
for (frame, sample) in bytes.chunks_exact_mut(4).zip(&mono[..frames]) {
let [lo, hi] = sample.to_le_bytes();
frame.copy_from_slice(&[lo, hi, lo, hi]);
}
frames * 4
});
if pushed == 0 {
break;
}
}
}
}
pub(crate) fn start(tx: I2sTx<'static, Async>) -> Result<Self, Error> {
let mut buffer = dma_tx_stream_buffer!(SPEAKER_BUFFER, SPEAKER_CHUNK);
while buffer.push(&SILENCE) > 0 {}
let transfer = tx.write(buffer).map_err(|_| Error::Speaker)?;
Ok(Self {
transfer: Some(transfer),
})
}
fn restart(&mut self) -> Result<(), Error> {
let transfer = self.transfer.take().ok_or(Error::Speaker)?;
let (tx, buffer) = transfer.stop();
let (descriptors, bytes) = buffer.split();
let mut buffer = DmaTxStreamBuf::new(descriptors, bytes).map_err(|_| Error::Speaker)?;
while buffer.push(&SILENCE) > 0 {}
self.transfer = Some(tx.write(buffer).map_err(|_| Error::Speaker)?);
Ok(())
}
}
pub fn speaker(pins: SpeakerPins) -> Result<Speaker, Error> {
let i2s = I2s::new(pins.i2s, pins.dma, i2s_config())
.map_err(|_| Error::Speaker)?
.into_async();
let tx = i2s
.i2s_tx
.with_bclk(pins.bclk)
.with_ws(pins.ws)
.with_dout(pins.dout)
.build();
Speaker::start(tx)
}
pub fn i2s_config() -> TdmConfig {
TdmConfig::new_tdm_philips()
.with_sample_rate(Rate::from_hz(SAMPLE_RATE))
.with_data_format(DataFormat::Data16Channel16)
.with_channels(Channels::STEREO)
}
impl fmt::Debug for Speaker {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Speaker")
.field("running", &self.transfer.is_some())
.finish_non_exhaustive()
}
}