microspectrogram 0.1.0

A simple `no_std` library for computing spectrograms.
Documentation
use core::num::NonZeroUsize;

pub enum Assert<const CHECK: bool> {}
pub trait IsTrue {}
impl IsTrue for Assert<true> {}

pub fn linspace<const N: usize>(start: f32, end: f32) -> [f32; N] {
    let step = (end - start) / (N - 1) as f32;
    let mut arr = [0.0; N];
    arr.iter_mut().enumerate().for_each(|(i, x)| {
        *x = start + step * i as f32;
    });
    arr
}

pub fn detrend_constant(arr: &mut [f32]) {
    let mut mean = 0.0;
    arr.iter().for_each(|x| {
        mean += x;
    });
    mean /= arr.len() as f32;

    arr.iter_mut().for_each(|x| {
        *x -= mean;
    });
}

pub struct OverlappingWindows<'a, T> {
    v: &'a [T],
    size: NonZeroUsize,
    step: NonZeroUsize,
}

pub fn overlapping_windows<T>(
    slice: &[T],
    size: NonZeroUsize,
    step: NonZeroUsize,
) -> OverlappingWindows<T> {
    OverlappingWindows {
        v: slice,
        size,
        step,
    }
}

impl<'a, T> Iterator for OverlappingWindows<'a, T> {
    type Item = &'a [T];

    #[inline]
    fn next(&mut self) -> Option<&'a [T]> {
        if self.size.get() > self.v.len() {
            None
        } else {
            let ret = Some(&self.v[..self.size.get()]);
            self.v = &self.v[self.step.get()..];
            ret
        }
    }
}

#[cfg(test)]
mod test {
    use core::{
        f32::consts::{FRAC_PI_3, PI},
        ops,
    };

    use num_traits::ToPrimitive;

    use super::*;

    fn all_close<T, const N: usize>(a: &[T; N], b: &[T; N]) -> bool
    where
        T: ops::Sub<Output = T> + PartialOrd + ToPrimitive + Copy,
    {
        const EPSILON: f32 = 1e-5;
        for i in 0..N {
            if (a[i] - b[i]).to_f32().unwrap() > EPSILON {
                return false;
            }
        }
        true
    }

    #[test]
    #[allow(clippy::excessive_precision)]
    fn test_linspace() {
        assert!(all_close(&linspace(1.0, 5.0), &[1.0, 2.0, 3.0, 4.0, 5.0]));
        assert!(all_close(&linspace(1.0, 5.0), &[1., 2.33333, 3.66666, 5.]));
        assert!(all_close(
            &linspace(-PI, PI),
            &[-PI, -FRAC_PI_3, FRAC_PI_3, PI]
        ));
    }

    #[test]
    fn test_detrend_constant() {
        let mut de = linspace(1.0, 5.0);
        detrend_constant(&mut de);
        assert_eq!(de, [-2., -1., 0., 1., 2.]);
    }

    #[test]
    fn test_overlapping_windows() {
        let data = [1, 2, 3, 4];
        let mut it = overlapping_windows(
            &data,
            NonZeroUsize::new(2).unwrap(),
            NonZeroUsize::new(1).unwrap(),
        );
        assert_eq!(it.next().unwrap(), &[1, 2]);
        assert_eq!(it.next().unwrap(), &[2, 3]);
        assert_eq!(it.next().unwrap(), &[3, 4]);
        assert!(it.next().is_none());

        let mut it = overlapping_windows(
            &data,
            NonZeroUsize::new(2).unwrap(),
            NonZeroUsize::new(2).unwrap(),
        );
        assert_eq!(it.next().unwrap(), &[1, 2]);
        assert_eq!(it.next().unwrap(), &[3, 4]);
        assert!(it.next().is_none());

        let data = [1, 2, 3, 4, 5, 6, 7, 8];
        let mut it = overlapping_windows(
            &data,
            NonZeroUsize::new(4).unwrap(),
            NonZeroUsize::new(2).unwrap(),
        );
        assert_eq!(it.next().unwrap(), &[1, 2, 3, 4]);
        assert_eq!(it.next().unwrap(), &[3, 4, 5, 6]);
        assert_eq!(it.next().unwrap(), &[5, 6, 7, 8]);
        assert!(it.next().is_none());

        let data = [1, 2, 3, 4, 5, 6, 7, 8, 9];
        let mut it = overlapping_windows(
            &data,
            NonZeroUsize::new(4).unwrap(),
            NonZeroUsize::new(2).unwrap(),
        );
        assert_eq!(it.next().unwrap(), &[1, 2, 3, 4]);
        assert_eq!(it.next().unwrap(), &[3, 4, 5, 6]);
        assert_eq!(it.next().unwrap(), &[5, 6, 7, 8]);
        assert!(it.next().is_none());
    }
}