#[derive(Debug, Clone)]
pub struct WindowIter {
start: usize,
total: usize,
win: usize,
hop: usize,
include_partial: bool,
}
impl WindowIter {
pub fn new(total: usize, win: usize, hop: usize) -> Self {
Self {
start: 0,
total,
win,
hop,
include_partial: false,
}
}
pub fn include_partial(mut self) -> Self {
self.include_partial = true;
self
}
}
impl Iterator for WindowIter {
type Item = (usize, usize);
fn next(&mut self) -> Option<Self::Item> {
if self.start >= self.total {
return None;
}
let end = if self.include_partial {
(self.start + self.win).min(self.total)
} else if self.start + self.win > self.total {
return None;
} else {
self.start + self.win
};
let item = (self.start, end);
self.start += self.hop;
Some(item)
}
}
#[derive(Debug, Clone)]
pub struct WindowBuffer {
buf: Vec<f32>,
win: usize,
hop: usize,
next_start: usize,
}
impl WindowBuffer {
pub fn new(win: usize, hop: usize) -> Self {
Self {
buf: Vec::new(),
win,
hop,
next_start: 0,
}
}
pub fn extend(&mut self, samples: &[f32]) {
self.buf.extend_from_slice(samples);
}
pub fn try_pop(&mut self) -> Option<(usize, Vec<f32>)> {
if self.buf.len() < self.win {
return None;
}
let start = self.next_start;
let window = self.buf[..self.win].to_vec();
self.next_start += self.hop;
self.buf.drain(..self.hop);
Some((start, window))
}
pub fn flush(&mut self) -> Option<(usize, Vec<f32>)> {
if self.buf.is_empty() {
return None;
}
let start = self.next_start;
let mut padded = self.buf.clone();
if padded.len() < self.win {
padded.resize(self.win, 0.0f32);
}
self.buf.clear();
Some((start, padded))
}
pub fn is_empty(&self) -> bool {
self.buf.is_empty()
}
pub fn len(&self) -> usize {
self.buf.len()
}
pub fn clear(&mut self) {
self.buf.clear();
}
pub fn reset_start(&mut self) {
self.next_start = 0;
}
pub fn set_next_start(&mut self, start: usize) {
self.next_start = start;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn window_iter_complete_only() {
let ranges: Vec<_> = WindowIter::new(10, 3, 2).collect();
assert_eq!(ranges, vec![(0, 3), (2, 5), (4, 7), (6, 9)]);
}
#[test]
fn window_iter_include_partial() {
let ranges: Vec<_> = WindowIter::new(10, 3, 2).include_partial().collect();
assert_eq!(ranges, vec![(0, 3), (2, 5), (4, 7), (6, 9), (8, 10)]);
}
#[test]
fn window_iter_empty() {
let ranges: Vec<_> = WindowIter::new(0, 3, 2).collect();
assert!(ranges.is_empty());
}
#[test]
fn window_iter_shorter_than_win() {
let ranges: Vec<_> = WindowIter::new(2, 3, 2).collect();
assert!(ranges.is_empty());
let ranges: Vec<_> = WindowIter::new(2, 3, 2).include_partial().collect();
assert_eq!(ranges, vec![(0, 2)]);
}
#[test]
fn window_buffer_pop_and_flush() {
let mut buf = WindowBuffer::new(4, 2);
buf.extend(&[1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
let (s1, w1) = buf.try_pop().unwrap();
assert_eq!(s1, 0);
assert_eq!(w1, vec![1.0, 2.0, 3.0, 4.0]);
let (s2, w2) = buf.try_pop().unwrap();
assert_eq!(s2, 2);
assert_eq!(w2, vec![3.0, 4.0, 5.0, 6.0]);
assert!(buf.try_pop().is_none());
let (s3, w3) = buf.flush().unwrap();
assert_eq!(s3, 4);
assert_eq!(w3, vec![5.0, 6.0, 0.0, 0.0]);
}
#[test]
fn window_buffer_flush_empty() {
let mut buf = WindowBuffer::new(4, 2);
assert!(buf.flush().is_none());
}
}
#[cfg(test)]
mod prop_tests {
use super::*;
use proptest::prelude::*;
proptest! {
#![proptest_config(ProptestConfig {
cases: 1000,
..ProptestConfig::default()
})]
#[test]
fn window_iter_start_less_than_end(
total in 0usize..10000,
win in 1usize..5000,
hop in 1usize..5000,
include_partial in any::<bool>(),
) {
let iter = if include_partial {
WindowIter::new(total, win, hop).include_partial()
} else {
WindowIter::new(total, win, hop)
};
for (start, end) in iter {
prop_assert!(start < end, "start {} must be < end {}", start, end);
}
}
#[test]
fn window_iter_end_le_total(
total in 0usize..10000,
win in 1usize..5000,
hop in 1usize..5000,
include_partial in any::<bool>(),
) {
let iter = if include_partial {
WindowIter::new(total, win, hop).include_partial()
} else {
WindowIter::new(total, win, hop)
};
for (_, end) in iter {
prop_assert!(end <= total, "end {} must be <= total {}", end, total);
}
}
#[test]
fn window_iter_no_partial(
total in 0usize..10000,
win in 1usize..5000,
hop in 1usize..5000,
) {
for (start, end) in WindowIter::new(total, win, hop) {
prop_assert_eq!(
end - start,
win,
"window [{}..{}] must be full size {} when include_partial=false",
start, end, win
);
}
}
#[test]
fn window_iter_partial_last(
total in 0usize..10000,
win in 1usize..5000,
hop in 1usize..5000,
) {
let ranges: Vec<_> = WindowIter::new(total, win, hop).include_partial().collect();
for (start, end) in &ranges {
prop_assert!(
*end == (*start + win).min(total),
"window [{}..{}] must equal ({} + {}).min({})",
start, end, start, win, total
);
}
}
}
}