use rusty_esp_core::error::{Error, Result};
use rusty_esp_core::pcm::{PcmBlock, PcmFormat, SampleFormat};
use rusty_esp_core::time::Micros;
use crate::put_i16;
pub trait AudioSource {
fn format(&self) -> PcmFormat;
fn read<'b>(&mut self, out: &'b mut [u8]) -> Result<PcmBlock<'b>>;
}
pub trait AudioSink {
fn format(&self) -> PcmFormat;
fn write(&mut self, block: PcmBlock<'_>) -> Result<()>;
}
#[derive(Debug, Clone)]
pub struct SineSource {
format: PcmFormat,
freq_hz: f32,
amplitude: i16,
phase: f32,
next: Micros,
}
impl SineSource {
pub fn new(format: PcmFormat, freq_hz: f32, amplitude: i16) -> Result<Self> {
if format.sample != SampleFormat::I16 {
return Err(Error::Unsupported);
}
if freq_hz.is_nan() || freq_hz <= 0.0 || freq_hz * 2.0 > format.sample_rate_hz as f32 {
return Err(Error::InvalidFormat);
}
Ok(SineSource {
format,
freq_hz,
amplitude,
phase: 0.0,
next: Micros::ZERO,
})
}
pub fn reset(&mut self) {
self.phase = 0.0;
self.next = Micros::ZERO;
}
}
impl AudioSource for SineSource {
fn format(&self) -> PcmFormat {
self.format
}
fn read<'b>(&mut self, out: &'b mut [u8]) -> Result<PcmBlock<'b>> {
let fb = self.format.frame_bytes();
if out.is_empty() || out.len() % fb != 0 {
return Err(Error::InvalidGeometry);
}
let step = self.freq_hz / self.format.sample_rate_hz as f32;
let amp = f32::from(self.amplitude);
for frame in out.chunks_exact_mut(fb) {
let v = libm::roundf(amp * libm::sinf(core::f32::consts::TAU * self.phase)) as i16;
for ch in frame.chunks_exact_mut(2) {
put_i16(ch, v);
}
self.phase += step;
if self.phase >= 1.0 {
self.phase -= 1.0;
}
}
let ts = self.next;
let block = PcmBlock::new(self.format, ts, out)?;
self.next = block.end();
Ok(block)
}
}
#[derive(Debug, Clone, Default)]
pub struct CountingSink {
format: Option<PcmFormat>,
pub blocks: u64,
pub frames: u64,
pub bytes: u64,
pub last_end: Micros,
pub rejected: u64,
}
impl CountingSink {
#[must_use]
pub fn new() -> Self {
Self::default()
}
}
impl AudioSink for CountingSink {
fn format(&self) -> PcmFormat {
self.format.unwrap_or(PcmFormat::PCM16_16K_MONO)
}
fn write(&mut self, block: PcmBlock<'_>) -> Result<()> {
match self.format {
None => self.format = Some(block.format),
Some(f) if f != block.format => {
self.rejected += 1;
return Err(Error::InvalidFormat);
}
Some(_) => {}
}
self.blocks += 1;
self.frames += block.frames() as u64;
self.bytes += block.data.len() as u64;
self.last_end = block.end();
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::get_i16;
#[test]
fn sine_is_periodic_and_timestamped() {
let f = PcmFormat::PCM16_16K_MONO;
let mut src = SineSource::new(f, 1000.0, 10_000).unwrap();
let mut buf = [0u8; 640]; let b = src.read(&mut buf).unwrap();
assert_eq!(b.timestamp, Micros::ZERO);
assert_eq!(b.end(), Micros(20_000));
assert_eq!(get_i16(&buf[8..]), 10_000);
assert_eq!(get_i16(&buf[24..]), -10_000);
assert_eq!(get_i16(&buf[0..]), 0);
let b2 = src.read(&mut buf).unwrap();
assert_eq!(b2.timestamp, Micros(20_000));
assert_eq!(get_i16(&buf[8..]), 10_000);
}
#[test]
fn sine_rejects_bad_setups() {
let f32fmt = PcmFormat::new(16_000, 1, SampleFormat::F32).unwrap();
assert_eq!(
SineSource::new(f32fmt, 440.0, 1).err(),
Some(Error::Unsupported)
);
assert_eq!(
SineSource::new(PcmFormat::PCM16_16K_MONO, 9000.0, 1).err(),
Some(Error::InvalidFormat)
);
let mut s = SineSource::new(PcmFormat::PCM16_16K_MONO, 440.0, 1).unwrap();
assert_eq!(s.read(&mut [0u8; 3]).err(), Some(Error::InvalidGeometry));
}
#[test]
fn counting_sink_holds_its_format() {
let mut sink = CountingSink::new();
let f = PcmFormat::PCM16_16K_MONO;
let data = [0u8; 64];
sink.write(PcmBlock::new(f, Micros::ZERO, &data).unwrap())
.unwrap();
let other = PcmFormat::PCM16_48K_STEREO;
assert_eq!(
sink.write(PcmBlock::new(other, Micros::ZERO, &data).unwrap())
.err(),
Some(Error::InvalidFormat)
);
assert_eq!((sink.blocks, sink.frames, sink.rejected), (1, 32, 1));
assert_eq!(sink.last_end, Micros(2_000));
}
}