Skip to main content

rusty_esp_audio_core/
source.rs

1//! Where blocks come from and where they go.
2//!
3//! An [`AudioSource`] fills a caller buffer with exactly one block; an
4//! [`AudioSink`] consumes one. A PDM microphone, an I2S codec, a UDP socket
5//! and a WAV file are all one of these, so a pipeline never knows which.
6
7use rusty_esp_core::error::{Error, Result};
8use rusty_esp_core::pcm::{PcmBlock, PcmFormat, SampleFormat};
9use rusty_esp_core::time::Micros;
10
11use crate::put_i16;
12
13/// Produces fixed-size blocks of PCM into caller memory.
14pub trait AudioSource {
15    /// Rate, channels and encoding of every block this source produces.
16    fn format(&self) -> PcmFormat;
17
18    /// Fill `out` completely (it must be a whole number of frames) and return
19    /// the block over it, stamped with the capture time of its first frame.
20    fn read<'b>(&mut self, out: &'b mut [u8]) -> Result<PcmBlock<'b>>;
21}
22
23/// Consumes blocks of PCM.
24pub trait AudioSink {
25    /// The format this sink accepts.
26    fn format(&self) -> PcmFormat;
27
28    /// Take one block. `Err(Busy)` means "dropped, try the next one".
29    fn write(&mut self, block: PcmBlock<'_>) -> Result<()>;
30}
31
32/// A deterministic test tone: a sine at `freq_hz` and peak `amplitude`, the
33/// same on every channel, with timestamps that advance by block duration.
34#[derive(Debug, Clone)]
35pub struct SineSource {
36    format: PcmFormat,
37    freq_hz: f32,
38    amplitude: i16,
39    /// Phase in cycles, kept in `[0, 1)` so precision does not drift.
40    phase: f32,
41    next: Micros,
42}
43
44impl SineSource {
45    /// A tone in `format`, which must be [`SampleFormat::I16`].
46    pub fn new(format: PcmFormat, freq_hz: f32, amplitude: i16) -> Result<Self> {
47        if format.sample != SampleFormat::I16 {
48            return Err(Error::Unsupported);
49        }
50        if freq_hz.is_nan() || freq_hz <= 0.0 || freq_hz * 2.0 > format.sample_rate_hz as f32 {
51            return Err(Error::InvalidFormat);
52        }
53        Ok(SineSource {
54            format,
55            freq_hz,
56            amplitude,
57            phase: 0.0,
58            next: Micros::ZERO,
59        })
60    }
61
62    /// Restart the tone at phase zero and time zero.
63    pub fn reset(&mut self) {
64        self.phase = 0.0;
65        self.next = Micros::ZERO;
66    }
67}
68
69impl AudioSource for SineSource {
70    fn format(&self) -> PcmFormat {
71        self.format
72    }
73
74    fn read<'b>(&mut self, out: &'b mut [u8]) -> Result<PcmBlock<'b>> {
75        let fb = self.format.frame_bytes();
76        if out.is_empty() || out.len() % fb != 0 {
77            return Err(Error::InvalidGeometry);
78        }
79        let step = self.freq_hz / self.format.sample_rate_hz as f32;
80        let amp = f32::from(self.amplitude);
81        for frame in out.chunks_exact_mut(fb) {
82            let v = libm::roundf(amp * libm::sinf(core::f32::consts::TAU * self.phase)) as i16;
83            for ch in frame.chunks_exact_mut(2) {
84                put_i16(ch, v);
85            }
86            self.phase += step;
87            if self.phase >= 1.0 {
88                self.phase -= 1.0;
89            }
90        }
91        let ts = self.next;
92        let block = PcmBlock::new(self.format, ts, out)?;
93        self.next = block.end();
94        Ok(block)
95    }
96}
97
98/// Counts what it is given and remembers where the stream has got to.
99#[derive(Debug, Clone, Default)]
100pub struct CountingSink {
101    format: Option<PcmFormat>,
102    /// Blocks accepted.
103    pub blocks: u64,
104    /// Sample frames accepted.
105    pub frames: u64,
106    /// Bytes accepted.
107    pub bytes: u64,
108    /// End timestamp of the last block.
109    pub last_end: Micros,
110    /// Blocks refused because their format did not match the first one.
111    pub rejected: u64,
112}
113
114impl CountingSink {
115    /// A sink that accepts the format of its first block and holds it to that.
116    #[must_use]
117    pub fn new() -> Self {
118        Self::default()
119    }
120}
121
122impl AudioSink for CountingSink {
123    fn format(&self) -> PcmFormat {
124        self.format.unwrap_or(PcmFormat::PCM16_16K_MONO)
125    }
126
127    fn write(&mut self, block: PcmBlock<'_>) -> Result<()> {
128        match self.format {
129            None => self.format = Some(block.format),
130            Some(f) if f != block.format => {
131                self.rejected += 1;
132                return Err(Error::InvalidFormat);
133            }
134            Some(_) => {}
135        }
136        self.blocks += 1;
137        self.frames += block.frames() as u64;
138        self.bytes += block.data.len() as u64;
139        self.last_end = block.end();
140        Ok(())
141    }
142}
143
144#[cfg(test)]
145mod tests {
146    use super::*;
147    use crate::get_i16;
148
149    #[test]
150    fn sine_is_periodic_and_timestamped() {
151        let f = PcmFormat::PCM16_16K_MONO;
152        let mut src = SineSource::new(f, 1000.0, 10_000).unwrap();
153        let mut buf = [0u8; 640]; // 20 ms
154        let b = src.read(&mut buf).unwrap();
155        assert_eq!(b.timestamp, Micros::ZERO);
156        assert_eq!(b.end(), Micros(20_000));
157        // 1 kHz at 16 kHz: 16 samples per cycle; sample 4 is the peak.
158        assert_eq!(get_i16(&buf[8..]), 10_000);
159        assert_eq!(get_i16(&buf[24..]), -10_000);
160        assert_eq!(get_i16(&buf[0..]), 0);
161        let b2 = src.read(&mut buf).unwrap();
162        assert_eq!(b2.timestamp, Micros(20_000));
163        // 20 ms is a whole number of cycles, so the block repeats exactly.
164        assert_eq!(get_i16(&buf[8..]), 10_000);
165    }
166
167    #[test]
168    fn sine_rejects_bad_setups() {
169        let f32fmt = PcmFormat::new(16_000, 1, SampleFormat::F32).unwrap();
170        assert_eq!(
171            SineSource::new(f32fmt, 440.0, 1).err(),
172            Some(Error::Unsupported)
173        );
174        assert_eq!(
175            SineSource::new(PcmFormat::PCM16_16K_MONO, 9000.0, 1).err(),
176            Some(Error::InvalidFormat)
177        );
178        let mut s = SineSource::new(PcmFormat::PCM16_16K_MONO, 440.0, 1).unwrap();
179        assert_eq!(s.read(&mut [0u8; 3]).err(), Some(Error::InvalidGeometry));
180    }
181
182    #[test]
183    fn counting_sink_holds_its_format() {
184        let mut sink = CountingSink::new();
185        let f = PcmFormat::PCM16_16K_MONO;
186        let data = [0u8; 64];
187        sink.write(PcmBlock::new(f, Micros::ZERO, &data).unwrap())
188            .unwrap();
189        let other = PcmFormat::PCM16_48K_STEREO;
190        assert_eq!(
191            sink.write(PcmBlock::new(other, Micros::ZERO, &data).unwrap())
192                .err(),
193            Some(Error::InvalidFormat)
194        );
195        assert_eq!((sink.blocks, sink.frames, sink.rejected), (1, 32, 1));
196        assert_eq!(sink.last_end, Micros(2_000));
197    }
198}