use core::time::Duration;
use crate::units::{Hertz, Percent};
pub const SAMPLE_RATE: u32 = 44_100;
const FADE: u32 = SAMPLE_RATE / 500;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub enum Waveform {
#[default]
Square,
Triangle,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Tone {
waveform: Waveform,
amplitude: i32,
step: u32,
phase: u32,
total: u32,
left: u32,
}
impl Tone {
#[must_use]
pub fn new(frequency: Hertz, duration: impl Into<Duration>) -> Self {
let samples = duration.into().as_micros() * u128::from(SAMPLE_RATE) / 1_000_000;
let total = u32::try_from(samples).unwrap_or(u32::MAX);
let rate = u64::from(SAMPLE_RATE);
let step = ((u64::from(frequency.0) << 32) + rate / 2) / rate;
Self {
waveform: Waveform::Square,
amplitude: 0,
step: u32::try_from(step).unwrap_or(u32::MAX),
phase: 0,
total,
left: total,
}
.with_level(Percent::new(50))
}
#[must_use]
pub const fn with_waveform(self, waveform: Waveform) -> Self {
Self { waveform, ..self }
}
#[must_use]
pub fn with_level(self, level: Percent) -> Self {
let amplitude = i32::from(i16::MAX) * i32::from(level.get()) / 100;
Self { amplitude, ..self }
}
#[must_use]
pub const fn is_done(&self) -> bool {
self.left == 0
}
pub fn fill(&mut self, samples: &mut [i16]) {
for sample in samples {
*sample = self.next().unwrap_or(0);
}
}
}
impl Iterator for Tone {
type Item = i16;
fn next(&mut self) -> Option<i16> {
let played = self.total - self.left;
self.left = self.left.checked_sub(1)?;
let wave = match self.waveform {
Waveform::Square if self.phase < 1 << 31 => 32767,
Waveform::Square => -32768,
Waveform::Triangle => {
let x = i32::try_from(self.phase >> 15).unwrap_or(0);
if x < 1 << 16 { x - 32768 } else { 98303 - x }
}
};
self.phase = self.phase.wrapping_add(self.step);
let fade = i32::try_from(played.min(self.left + 1).min(FADE)).unwrap_or(0);
let sample = wave * self.amplitude / 32767 * fade / i32::try_from(FADE).unwrap_or(1);
Some(i16::try_from(sample).unwrap_or(0))
}
fn size_hint(&self) -> (usize, Option<usize>) {
let left = usize::try_from(self.left).unwrap_or(usize::MAX);
(left, Some(left))
}
}
impl ExactSizeIterator for Tone {}
#[cfg(test)]
mod tests {
use super::*;
fn tone(waveform: Waveform) -> Vec<i16> {
Tone::new(Hertz(441), Duration::from_secs(1))
.with_waveform(waveform)
.with_level(Percent::FULL)
.collect()
}
#[test]
fn a_tone_lasts_its_duration() {
let tone = Tone::new(Hertz(440), Duration::from_millis(10));
assert_eq!(tone.len(), 441);
assert_eq!(tone.count(), 441);
}
#[test]
fn a_square_wave_is_high_for_half_a_cycle_then_low() {
let samples = tone(Waveform::Square);
assert!(samples[200..250].iter().all(|&sample| sample == 32767));
assert!(samples[250..300].iter().all(|&sample| sample == -32768));
}
#[test]
fn a_triangle_wave_rises_for_half_a_cycle_then_falls() {
let samples = tone(Waveform::Triangle);
assert_eq!(samples[200], -32768);
assert!(samples[200..250].is_sorted());
assert!(samples[250] > 32000);
assert!(samples[250..300].iter().rev().is_sorted());
}
#[test]
fn it_fades_in_and_out() {
let samples = tone(Waveform::Square);
assert_eq!(samples[0], 0);
assert!(samples[1].unsigned_abs() < 1000);
assert!(
samples
.last()
.is_some_and(|sample| sample.unsigned_abs() < 1000)
);
}
#[test]
fn its_level_scales_it() {
let samples: Vec<i16> = Tone::new(Hertz(441), Duration::from_secs(1))
.with_level(Percent::new(50))
.collect();
assert_eq!(samples[200], 16383);
}
#[test]
fn filling_goes_on_with_silence_once_its_done() {
let mut tone = Tone::new(Hertz(441), Duration::from_millis(1));
let mut samples = [1; 64];
tone.fill(&mut samples);
assert!(tone.is_done());
assert!(samples[44..].iter().all(|&sample| sample == 0));
}
}