rosa 0.1.0

Audio feature extraction library matching librosa output
Documentation
/// Zero-copy overlapping frames over a signal.
/// Each frame is a contiguous slice of the original data.
/// Equivalent to librosa's `util.frame()`.
pub struct FrameIter<'a> {
    data: &'a [f64],
    frame_length: usize,
    hop_length: usize,
    current: usize,
    n_frames: usize,
}

impl<'a> FrameIter<'a> {
    /// Create a new frame iterator.
    /// `n_frames = 1 + (data.len() - frame_length) / hop_length` if `data.len() >= frame_length`, else 0.
    pub fn new(data: &'a [f64], frame_length: usize, hop_length: usize) -> Self {
        let n_frames = if data.len() >= frame_length {
            1 + (data.len() - frame_length) / hop_length
        } else {
            0
        };
        Self {
            data,
            frame_length,
            hop_length,
            current: 0,
            n_frames,
        }
    }

    /// Number of frames.
    pub fn n_frames(&self) -> usize {
        self.n_frames
    }

    /// Random access to frame by index.
    pub fn frame(&self, i: usize) -> &[f64] {
        assert!(
            i < self.n_frames,
            "frame index {} out of bounds for {} frames",
            i,
            self.n_frames
        );
        let start = i * self.hop_length;
        &self.data[start..start + self.frame_length]
    }
}

impl<'a> Iterator for FrameIter<'a> {
    type Item = &'a [f64];

    fn next(&mut self) -> Option<Self::Item> {
        if self.current >= self.n_frames {
            return None;
        }
        let start = self.current * self.hop_length;
        self.current += 1;
        Some(&self.data[start..start + self.frame_length])
    }

    fn size_hint(&self) -> (usize, Option<usize>) {
        let rem = self.n_frames - self.current;
        (rem, Some(rem))
    }
}

impl ExactSizeIterator for FrameIter<'_> {}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_basic_framing() {
        // 10 samples, frame_length=4, hop_length=2 → 4 frames
        let data: Vec<f64> = (0..10).map(|i| i as f64).collect();
        let iter = FrameIter::new(&data, 4, 2);
        assert_eq!(iter.n_frames(), 4);

        let frames: Vec<&[f64]> = iter.collect();
        assert_eq!(frames.len(), 4);
        assert_eq!(frames[0], &[0.0, 1.0, 2.0, 3.0]);
        assert_eq!(frames[1], &[2.0, 3.0, 4.0, 5.0]);
        assert_eq!(frames[2], &[4.0, 5.0, 6.0, 7.0]);
        assert_eq!(frames[3], &[6.0, 7.0, 8.0, 9.0]);
    }

    #[test]
    fn test_frame_equals_data_length() {
        let data = vec![1.0, 2.0, 3.0, 4.0];
        let iter = FrameIter::new(&data, 4, 1);
        assert_eq!(iter.n_frames(), 1);
        let frames: Vec<&[f64]> = iter.collect();
        assert_eq!(frames[0], &[1.0, 2.0, 3.0, 4.0]);
    }

    #[test]
    fn test_data_shorter_than_frame() {
        let data = vec![1.0, 2.0];
        let iter = FrameIter::new(&data, 4, 1);
        assert_eq!(iter.n_frames(), 0);
        assert_eq!(iter.count(), 0);
    }

    #[test]
    fn test_hop_length_one() {
        let data: Vec<f64> = (0..6).map(|i| i as f64).collect();
        let iter = FrameIter::new(&data, 3, 1);
        // 1 + (6-3)/1 = 4 frames
        assert_eq!(iter.n_frames(), 4);
    }

    #[test]
    fn test_iterator_count_matches_n_frames() {
        let data: Vec<f64> = (0..20).map(|i| i as f64).collect();
        let iter = FrameIter::new(&data, 5, 3);
        let n = iter.n_frames();
        assert_eq!(iter.count(), n);
    }

    #[test]
    fn test_random_access_matches_sequential() {
        let data: Vec<f64> = (0..15).map(|i| i as f64).collect();
        let iter = FrameIter::new(&data, 4, 2);
        let n = iter.n_frames();

        let sequential: Vec<Vec<f64>> = FrameIter::new(&data, 4, 2).map(|f| f.to_vec()).collect();

        for i in 0..n {
            let random = iter.frame(i);
            assert_eq!(random, sequential[i].as_slice());
        }
    }

    #[test]
    fn test_size_hint_exact() {
        let data: Vec<f64> = (0..10).map(|i| i as f64).collect();
        let mut iter = FrameIter::new(&data, 4, 2);
        assert_eq!(iter.len(), 4);
        iter.next();
        assert_eq!(iter.len(), 3);
        iter.next();
        assert_eq!(iter.len(), 2);
    }
}