#![no_std]
#![deny(missing_docs)]
#![deny(clippy::unwrap_used)]
#![deny(clippy::expect_used)]
#[cfg(feature = "std")]
extern crate std;
#[cfg(all(not(feature = "libm"), not(feature = "std")))]
compile_error!("at least one of \"libm\", \"std\" features has to be enabled");
extern crate alloc;
mod macros;
use alloc::{boxed::Box, vec, vec::Vec};
use core::iter::Sum;
use core::marker::PhantomData;
use core::ops::Add;
use num_traits::{Bounded, Float, FloatConst, NumCast, One};
pub trait Precision: Float + FloatConst + Sum + Send + Sync + 'static {}
impl<T> Precision for T where T: Float + FloatConst + Sum + Send + Sync + 'static {}
trait Two {
fn two() -> Self;
}
impl<T> Two for T
where
T: One + Add<Output = T>,
{
#[inline(always)]
fn two() -> Self {
T::one() + T::one()
}
}
pub trait SampleType: NumCast + Bounded {}
impl<T> SampleType for T where T: NumCast + Bounded {}
pub struct Waveform<T: SampleType, P: Precision = f32> {
sample_rate: P,
components: Vec<PeriodicFunction<P>>,
_phantom: PhantomData<T>,
}
impl<T: SampleType, P: Precision> Waveform<T, P> {
pub fn new(sample_rate: impl Into<P>) -> Self {
let sample_rate = sample_rate.into();
Self::assert_sane(sample_rate);
Waveform {
sample_rate,
components: vec![],
_phantom: PhantomData,
}
}
pub fn with_components(
sample_rate: impl Into<P>,
components: Vec<PeriodicFunction<P>>,
) -> Self {
let sample_rate = sample_rate.into();
Self::assert_sane(sample_rate);
Waveform {
sample_rate,
components,
_phantom: PhantomData,
}
}
pub fn add_component(&mut self, component: PeriodicFunction<P>) {
self.components.push(component);
}
pub fn sample_rate(&self) -> &P {
&self.sample_rate
}
pub fn components(&self) -> &Vec<PeriodicFunction<P>> {
&self.components
}
pub fn iter(&self) -> WaveformIterator<T, P> {
WaveformIterator::<T, P> {
inner: self,
time: P::zero(),
}
}
#[inline(always)]
fn assert_sane(x: P) {
assert!(x.is_normal());
assert!(x.is_sign_positive());
}
}
impl<'a, T: SampleType, P: Precision> IntoIterator for &'a Waveform<T, P> {
type Item = T;
type IntoIter = WaveformIterator<'a, T, P>;
fn into_iter(self) -> Self::IntoIter {
WaveformIterator {
inner: self,
time: P::zero(),
}
}
}
#[derive(Clone, Copy)]
pub struct WaveformIterator<'a, T: SampleType, P: Precision> {
inner: &'a Waveform<T, P>,
time: P,
}
impl<'a, T: SampleType, P: Precision> WaveformIterator<'a, T, P> {
fn into_target_type_sanitized(sample: P) -> Option<T> {
let result = NumCast::from(sample);
result.or_else(|| {
if sample > P::zero() {
Some(T::max_value())
} else if sample < P::zero() {
Some(T::min_value())
} else {
None
}
})
}
fn increment_time(&mut self, n: usize) -> Result<(), ()> {
let new_time = self.time + (P::from(n).ok_or(())? * (P::one() / self.inner.sample_rate));
if new_time.is_finite() {
self.time = new_time;
} else {
self.time = (P::one() / self.inner.sample_rate) - (P::max_value() - self.time);
}
Ok(())
}
fn raw_sample(&self) -> P {
self.inner
.components
.iter()
.map(|x| x.sample(self.time))
.sum()
}
}
impl<'a, T: SampleType, P: Precision> Iterator for WaveformIterator<'a, T, P> {
type Item = T;
fn next(&mut self) -> Option<Self::Item> {
let sample = self.raw_sample();
self.increment_time(1).ok()?;
Self::into_target_type_sanitized(sample)
}
fn nth(&mut self, n: usize) -> Option<Self::Item> {
self.increment_time(n).ok()?;
self.next()
}
fn size_hint(&self) -> (usize, Option<usize>) {
(usize::MAX, None)
}
}
pub struct PeriodicFunction<P: Precision = f32> {
inner: Box<dyn Fn(P) -> P + Send + Sync>,
}
impl<P: Precision + 'static> PeriodicFunction<P> {
pub fn new(f: Box<dyn Fn(P) -> P + Send + Sync>) -> Self {
Self { inner: f }
}
pub fn custom<F: Fn(P) -> P + Send + Sync + 'static>(f: F) -> Self {
Self::new(Box::new(f))
}
pub fn dc_bias(bias: impl Into<P>) -> Self {
let bias = bias.into();
Self::new(Box::new(move |_| bias))
}
pub fn sawtooth(frequency: impl Into<P>, amplitude: impl Into<P>, phase: impl Into<P>) -> Self {
let frequency = frequency.into();
let amplitude = amplitude.into();
let phase = phase.into();
Self::new(Box::new(move |t| {
P::two() * amplitude * (t * frequency + phase).fract() - amplitude
}))
}
#[inline(always)]
pub fn sine(frequency: impl Into<P>, amplitude: impl Into<P>, phase: impl Into<P>) -> Self {
let frequency = frequency.into();
let amplitude = amplitude.into();
let phase = phase.into();
Self::new(Box::new(move |t| {
let radians = (P::two() * P::PI() * frequency * t) + (phase * P::two() * P::PI());
let sine = radians.sin();
sine * amplitude
}))
}
#[inline(always)]
pub fn square(frequency: impl Into<P>, amplitude: impl Into<P>, phase: impl Into<P>) -> Self {
let frequency = frequency.into();
let amplitude = amplitude.into();
let phase = phase.into();
Self::new(Box::new(move |t| {
let power = (P::two() * (t - phase) * frequency).floor();
amplitude * (P::one().neg()).powf(power)
}))
}
pub fn inner(&self) -> &(impl Fn(P) -> P + Send + Sync) {
&self.inner
}
pub fn sample(&self, t: P) -> P {
self.inner()(t)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{dc_bias, sawtooth, sine, square};
use alloc::{vec, vec::Vec};
use float_cmp::approx_eq;
use paste::paste;
const EPS: f32 = 1e-3;
#[test]
fn square_of_high_frequency() {
let square = PeriodicFunction::<f64>::square(u32::MAX, 1.0, 0.0);
assert!(square.sample(1.0).is_finite());
}
#[test]
fn sine_waveform_has_default_amplitude_of_one() {
let wf = Waveform::<f32>::with_components(100.0, vec![sine!(1.)]);
let samples = wf.iter().take(100).collect::<Vec<_>>();
assert_eq!(samples[25], 1.0);
assert_eq!(samples[75], -1.0);
}
#[test]
fn sine_waveform_as_integers_has_amplitude_of_one() {
let wf = Waveform::<i32>::with_components(100.0, vec![sine!(1.)]);
let samples = wf.iter().take(100).collect::<Vec<_>>();
assert_eq!(samples[25], 1);
assert_eq!(samples[75], -1);
}
#[test]
fn sine_waveform_with_bias_has_correct_amplitude() {
let wf = Waveform::<f32>::with_components(100.0, vec![sine!(1.), dc_bias!(5.)]);
let samples = wf.iter().take(100).collect::<Vec<_>>();
assert_eq!(samples[25], 6.0);
assert_eq!(samples[75], 4.0);
}
macro_rules! test_no_default_bias {
($($name:ident: $func:expr)*) => {
$(
paste! {
#[test]
fn [<default_ $name _waveforom_has_no_bias>]() {
let wf = Waveform::<f32, f64>::with_components(100.0, vec![$func]);
let bias = wf.iter().take(100).sum::<f32>() / 100.0;
assert!(approx_eq!(f32, bias, 0.0, epsilon = EPS));
}
}
)*
};
}
test_no_default_bias! {
sine: sine!(1.)
square: square!(1.)
}
#[test]
#[allow(clippy::iter_skip_next)]
fn waveform_iterator_is_infinite_single() {
let wf = Waveform::<f64>::new(f32::MIN_POSITIVE);
let mut iter = wf.iter().skip(usize::MAX);
assert_eq!(Some(0f64), iter.next());
assert_eq!(Some(0f64), iter.skip(usize::MAX).next())
}
#[test]
#[allow(clippy::iter_skip_next)]
fn waveform_iterator_is_infinite_double() {
let wf = Waveform::<f64, f64>::new(f64::MIN_POSITIVE);
let mut iter = wf.iter().skip(usize::MAX);
assert_eq!(Some(0f64), iter.next());
assert_eq!(Some(0f64), iter.skip(usize::MAX).next())
}
#[test]
fn oversaturated_amplitude_clips_to_max() {
let wf = Waveform::<u8>::with_components(100.0, vec![dc_bias!(300.)]);
let samples = wf.iter().take(1).collect::<Vec<_>>();
assert_eq!(samples.len(), 1);
assert_eq!(samples[0], u8::MAX);
}
#[test]
fn undersaturated_amplitude_clips_to_min() {
let wf = Waveform::<u8>::with_components(100.0, vec![dc_bias!(-300.)]);
let samples = wf.iter().take(1).collect::<Vec<_>>();
assert_eq!(samples.len(), 1);
assert_eq!(samples[0], u8::MIN);
}
macro_rules! test_wavefrom_panic {
($($name:ident: $sample_rate:expr)*) => {
$(
paste! {
#[test]
#[should_panic]
fn [<waveform_new_panics_on_ $name>]() {
Waveform::<f64>::new($sample_rate);
}
#[test]
#[should_panic]
fn [<waveform_with_components_panics_on_ $name>]() {
Waveform::<f64>::with_components($sample_rate, vec![]);
}
}
)*
};
}
test_wavefrom_panic! {
nan: f32::NAN
negative: -1f32
zero: 0.0
infinity: f32::INFINITY
negative_infinity: f32::NEG_INFINITY
}
macro_rules! test_size_hint {
() => {
let wf = Waveform::<f32>::new(44100.0);
assert_eq!((usize::MAX, None), wf.iter().size_hint());
};
($($component:expr),*) => {
let mut wf = Waveform::<f32>::new(44100.0);
$(
wf.add_component($component);
)*
assert_eq!((usize::MAX, None), wf.iter().size_hint());
};
}
#[test]
fn test_size_hint() {
test_size_hint!();
test_size_hint!(sine!(50.));
test_size_hint!(sine!(1.), sawtooth!(2.), square!(3.), dc_bias!(4.));
}
#[test]
#[allow(clippy::iter_nth_zero)]
#[allow(clippy::unwrap_used)]
fn nth_and_next_give_same_results() {
let wf = Waveform::<i32>::with_components(44100.0, vec![sine!(3000., i16::MAX)]);
let mut i1 = wf.iter();
let mut i2 = wf.iter();
for _ in 0..1000 {
assert_eq!(i1.next().unwrap(), i2.nth(0).unwrap());
}
}
#[test]
fn waveform_is_send() {
fn assert_send<T: Send>() {}
assert_send::<Waveform<f64>>();
}
#[test]
fn waveform_is_sync() {
fn assert_sync<T: Sync>() {}
assert_sync::<Waveform<f64>>();
}
}