rusty_esp_audio_core/
source.rs1use 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
13pub trait AudioSource {
15 fn format(&self) -> PcmFormat;
17
18 fn read<'b>(&mut self, out: &'b mut [u8]) -> Result<PcmBlock<'b>>;
21}
22
23pub trait AudioSink {
25 fn format(&self) -> PcmFormat;
27
28 fn write(&mut self, block: PcmBlock<'_>) -> Result<()>;
30}
31
32#[derive(Debug, Clone)]
35pub struct SineSource {
36 format: PcmFormat,
37 freq_hz: f32,
38 amplitude: i16,
39 phase: f32,
41 next: Micros,
42}
43
44impl SineSource {
45 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 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#[derive(Debug, Clone, Default)]
100pub struct CountingSink {
101 format: Option<PcmFormat>,
102 pub blocks: u64,
104 pub frames: u64,
106 pub bytes: u64,
108 pub last_end: Micros,
110 pub rejected: u64,
112}
113
114impl CountingSink {
115 #[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]; let b = src.read(&mut buf).unwrap();
155 assert_eq!(b.timestamp, Micros::ZERO);
156 assert_eq!(b.end(), Micros(20_000));
157 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 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}