rosa 0.1.0

Audio feature extraction library matching librosa output
Documentation
use std::collections::VecDeque;

/// Fixed-capacity sliding window of feature frames.
///
/// Holds the last `capacity` frames. When full, the oldest frame
/// is dropped on push. Used by processors that need cross-frame
/// context (e.g., onset detection needs past frames for reference).
pub struct SlidingWindow {
    frames: VecDeque<Vec<f64>>,
    capacity: usize,
}

impl SlidingWindow {
    /// Create a new sliding window with the given maximum number of frames.
    pub fn new(capacity: usize) -> Self {
        Self {
            frames: VecDeque::with_capacity(capacity),
            capacity,
        }
    }

    /// Push a new frame. Drops the oldest if at capacity.
    pub fn push(&mut self, frame: Vec<f64>) {
        if self.frames.len() == self.capacity {
            self.frames.pop_front();
        }
        self.frames.push_back(frame);
    }

    /// Returns the number of frames currently held in the window.
    pub fn len(&self) -> usize {
        self.frames.len()
    }

    /// Returns `true` if the window contains no frames.
    pub fn is_empty(&self) -> bool {
        self.frames.is_empty()
    }

    /// Returns `true` if the window has reached its capacity.
    pub fn is_full(&self) -> bool {
        self.frames.len() == self.capacity
    }

    /// Access frame by index (0 = oldest in window).
    pub fn get(&self, index: usize) -> &[f64] {
        &self.frames[index]
    }

    /// Access most recent frame.
    pub fn latest(&self) -> Option<&[f64]> {
        self.frames.back().map(|v| v.as_slice())
    }

    /// Iterate frames oldest to newest.
    pub fn iter(&self) -> impl Iterator<Item = &[f64]> {
        self.frames.iter().map(|v| v.as_slice())
    }
}

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

    #[test]
    fn empty_window() {
        let w = SlidingWindow::new(3);
        assert_eq!(w.len(), 0);
        assert!(w.is_empty());
        assert!(!w.is_full());
        assert_eq!(w.latest(), None);
        assert_eq!(w.iter().count(), 0);
    }

    #[test]
    fn push_within_capacity() {
        let mut w = SlidingWindow::new(3);
        w.push(vec![1.0, 2.0]);
        w.push(vec![3.0, 4.0]);

        assert_eq!(w.len(), 2);
        assert!(!w.is_full());
        assert_eq!(w.get(0), &[1.0, 2.0]);
        assert_eq!(w.get(1), &[3.0, 4.0]);
        assert_eq!(w.latest(), Some([3.0, 4.0].as_slice()));
    }

    #[test]
    fn push_at_capacity() {
        let mut w = SlidingWindow::new(3);
        w.push(vec![1.0]);
        w.push(vec![2.0]);
        w.push(vec![3.0]);

        assert!(w.is_full());
        assert_eq!(w.len(), 3);
        assert_eq!(w.get(0), &[1.0]);
        assert_eq!(w.get(2), &[3.0]);
    }

    #[test]
    fn push_beyond_capacity_drops_oldest() {
        let mut w = SlidingWindow::new(3);
        w.push(vec![1.0]);
        w.push(vec![2.0]);
        w.push(vec![3.0]);
        w.push(vec![4.0]); // drops [1.0]

        assert!(w.is_full());
        assert_eq!(w.len(), 3);
        assert_eq!(w.get(0), &[2.0]); // oldest is now 2.0
        assert_eq!(w.get(1), &[3.0]);
        assert_eq!(w.get(2), &[4.0]);
        assert_eq!(w.latest(), Some([4.0].as_slice()));
    }

    #[test]
    fn is_full_transitions() {
        let mut w = SlidingWindow::new(2);
        assert!(!w.is_full());

        w.push(vec![1.0]);
        assert!(!w.is_full());

        w.push(vec![2.0]);
        assert!(w.is_full());

        w.push(vec![3.0]); // still full after overflow
        assert!(w.is_full());
    }

    #[test]
    fn iter_oldest_to_newest() {
        let mut w = SlidingWindow::new(3);
        w.push(vec![10.0]);
        w.push(vec![20.0]);
        w.push(vec![30.0]);

        let collected: Vec<&[f64]> = w.iter().collect();
        assert_eq!(collected, vec![[10.0].as_slice(), &[20.0], &[30.0]]);
    }

    #[test]
    fn iter_after_overflow() {
        let mut w = SlidingWindow::new(2);
        w.push(vec![1.0]);
        w.push(vec![2.0]);
        w.push(vec![3.0]);
        w.push(vec![4.0]);

        let collected: Vec<&[f64]> = w.iter().collect();
        assert_eq!(collected, vec![[3.0].as_slice(), &[4.0]]);
    }

    #[test]
    fn variable_frame_sizes() {
        let mut w = SlidingWindow::new(3);
        w.push(vec![1.0, 2.0]);
        w.push(vec![3.0, 4.0, 5.0]);
        w.push(vec![6.0]);

        assert_eq!(w.get(0), &[1.0, 2.0]);
        assert_eq!(w.get(1), &[3.0, 4.0, 5.0]);
        assert_eq!(w.get(2), &[6.0]);
    }
}