Skip to main content

embedded_audio/
hal.rs

1/// Driver hook: apply one duty compare value per audio sample tick.
2pub trait PwmDutySink {
3    fn set_duty(&mut self, duty: u16);
4}
5
6impl<F: FnMut(u16)> PwmDutySink for F {
7    fn set_duty(&mut self, duty: u16) {
8        self(duty);
9    }
10}
11
12/// Run one engine tick and push duty to the sink.
13#[inline]
14pub fn tick_into<S: PwmDutySink, const N: usize>(
15    engine: &mut crate::AudioEngine<'_, N>,
16    sink: &mut S,
17) -> u16 {
18    let duty = engine.tick();
19    sink.set_duty(duty);
20    duty
21}
22
23/// Fill `buf` via the engine and write each duty to the sink (DMA kick-off helper).
24pub fn fill_buffer_into<S: PwmDutySink, const N: usize>(
25    engine: &mut crate::AudioEngine<'_, N>,
26    buf: &mut [u16],
27    sink: &mut S,
28) -> usize {
29    let n = engine.fill_duty_buffer(buf);
30    if let Some(&duty) = buf.last() {
31        sink.set_duty(duty);
32    }
33    n
34}
35
36/// Double-buffer DMA helper to fill half-buffer callbacks (e.g. Embassy / STM32 DMA ISR).
37pub fn fill_dma_half_buffers<const N: usize>(
38    engine: &mut crate::AudioEngine<'_, N>,
39    half_buf: &mut [u16],
40) -> usize {
41    engine.fill_duty_buffer(half_buf)
42}
43
44/// In-memory duty buffer for DMA (no hardware attached).
45#[derive(Debug, PartialEq, Eq)]
46pub struct DutyBuffer<'a> {
47    pub buf: &'a mut [u16],
48    pub cursor: usize,
49}
50
51impl<'a> DutyBuffer<'a> {
52    pub const fn new(buf: &'a mut [u16]) -> Self {
53        Self { buf, cursor: 0 }
54    }
55}
56
57impl PwmDutySink for DutyBuffer<'_> {
58    fn set_duty(&mut self, duty: u16) {
59        if self.cursor < self.buf.len() {
60            self.buf[self.cursor] = duty;
61            self.cursor += 1;
62        }
63    }
64}
65
66/// Double-buffer ping-pong manager for DMA audio streaming.
67///
68/// Designed to work seamlessly with async DMA drivers (e.g. Embassy `dac.write()`,
69/// `sai.write()`, `timer.write_dma()`, etc.) or IRQ-driven DMA half-transfer callbacks.
70#[derive(Debug, Clone)]
71pub struct DmaDoubleBuffer<T, const N: usize> {
72    buffer: [[T; N]; 2],
73    active_half: usize,
74}
75
76impl<T: Copy + Default, const N: usize> DmaDoubleBuffer<T, N> {
77    pub const HALF_SIZE: usize = N;
78    pub const TOTAL_SIZE: usize = N * 2;
79
80    pub fn new() -> Self {
81        Self {
82            buffer: [[T::default(); N]; 2],
83            active_half: 0,
84        }
85    }
86
87    pub const fn from_buffers(buf0: [T; N], buf1: [T; N]) -> Self {
88        Self {
89            buffer: [buf0, buf1],
90            active_half: 0,
91        }
92    }
93
94    /// Get reference to both half buffers.
95    pub fn buffers(&self) -> &[[T; N]; 2] {
96        &self.buffer
97    }
98
99    /// Get mutable reference to both half buffers.
100    pub fn buffers_mut(&mut self) -> &mut [[T; N]; 2] {
101        &mut self.buffer
102    }
103
104    /// Get current active half-buffer slice to fill with new samples.
105    pub fn current_half_mut(&mut self) -> &mut [T; N] {
106        &mut self.buffer[self.active_half]
107    }
108
109    /// Get current active half-buffer slice.
110    pub fn current_half(&self) -> &[T; N] {
111        &self.buffer[self.active_half]
112    }
113
114    /// Swap active half-buffer index and return mutable slice for the next batch.
115    pub fn swap_and_get_next(&mut self) -> &mut [T; N] {
116        self.active_half ^= 1;
117        self.current_half_mut()
118    }
119
120    /// Fill the inactive buffer half using `fill_fn`, then swap to make it active and return its slice.
121    pub fn fill_and_swap<F>(&mut self, mut fill_fn: F) -> &mut [T; N]
122    where
123        F: FnMut(&mut [T; N]),
124    {
125        let next_half = self.active_half ^ 1;
126        fill_fn(&mut self.buffer[next_half]);
127        self.active_half = next_half;
128        &mut self.buffer[next_half]
129    }
130}
131
132impl<T: Copy + Default, const N: usize> Default for DmaDoubleBuffer<T, N> {
133    fn default() -> Self {
134        Self::new()
135    }
136}