use crate::{
math::{cubic_hermite, lerp},
resources::window::Window,
};
#[derive(Clone)]
pub struct ResourceDelay {
cursor: usize,
mask: usize, window: Window,
}
impl ResourceDelay {
pub fn new(window: Window) -> Self {
let size = window.len;
assert!(size.is_power_of_two(), "Buffer size must be power of 2");
Self {
cursor: 0,
mask: size - 1, window,
}
}
#[inline(always)]
pub fn push(&mut self, data: &mut [f32], val: f32) {
debug_assert_eq!(self.window.len, data.len());
data[self.cursor] = val;
self.cursor = (self.cursor + 1) & self.mask;
}
#[inline(always)]
fn read_idx(&self, delay: usize) -> usize {
self.cursor.wrapping_add(self.mask).wrapping_sub(delay) & self.mask
}
#[inline(always)]
pub fn get_offset(&self, data: &[f32], delay_samples: usize) -> f32 {
debug_assert_eq!(self.window.len, data.len());
let idx = self.read_idx(delay_samples.min(self.mask));
unsafe { *data.get_unchecked(idx) }
}
#[inline(always)]
pub fn get_delay_linear(&self, data: &[f32], offset: f32) -> f32 {
debug_assert_eq!(self.window.len, data.len());
let floor = offset as usize;
let base = self.read_idx(floor.min(self.mask));
let prev = base.wrapping_sub(1) & self.mask;
let (a, b) = unsafe { (*data.get_unchecked(base), *data.get_unchecked(prev)) };
lerp(a, b, offset - floor as f32)
}
#[inline(always)]
pub fn get_delay_cubic(&self, data: &[f32], offset: f32) -> f32 {
debug_assert_eq!(self.window.len, data.len());
let floor = offset.floor() as usize;
let i1 = self.read_idx(floor.min(self.mask));
let i0 = i1.wrapping_add(1) & self.mask;
let i2 = i1.wrapping_sub(1) & self.mask;
let i3 = i1.wrapping_sub(2) & self.mask;
let (a, b, c, d) = unsafe {
(
*data.get_unchecked(i0),
*data.get_unchecked(i1),
*data.get_unchecked(i2),
*data.get_unchecked(i3),
)
};
cubic_hermite(a, b, c, d, offset - floor as f32)
}
#[inline(always)]
pub fn get_window(&self) -> Window {
self.window
}
}
pub struct DelayLineView<'a> {
pub delay: &'a ResourceDelay,
pub data: &'a [f32],
}
pub struct DelayLineViewMut<'a> {
pub delay: &'a mut ResourceDelay,
pub data: &'a mut [f32],
}
impl<'a> DelayLineView<'a> {
#[inline(always)]
pub fn read_linear(&self, offset: f32) -> f32 {
self.delay.get_delay_linear(self.data, offset)
}
#[inline(always)]
pub fn read_cubic(&self, offset: f32) -> f32 {
self.delay.get_delay_cubic(self.data, offset)
}
}
impl<'a> DelayLineViewMut<'a> {
#[inline(always)]
pub fn push(&mut self, val: f32) {
self.delay.push(self.data, val);
}
#[inline(always)]
pub fn read_linear(&self, offset: f32) -> f32 {
self.delay.get_delay_linear(self.data, offset)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::resources::window::Window;
#[test]
fn test_push_and_wrap() {
let size = 4;
let mut buffer = vec![0.0; size];
let mut delay = ResourceDelay::new(Window {
len: size,
start: 0,
});
delay.push(&mut buffer, 1.0);
delay.push(&mut buffer, 2.0);
delay.push(&mut buffer, 3.0);
delay.push(&mut buffer, 4.0);
assert_eq!(delay.cursor, 0);
delay.push(&mut buffer, 5.0);
assert_eq!(buffer[0], 5.0);
assert_eq!(delay.cursor, 1);
}
#[test]
fn test_get_offset_logic() {
let size = 4;
let mut buffer = vec![0.0; size];
let mut delay = ResourceDelay::new(Window {
len: size,
start: 0,
});
delay.push(&mut buffer, 10.0); delay.push(&mut buffer, 20.0); delay.push(&mut buffer, 30.0);
assert_eq!(delay.get_offset(&buffer, 0), 30.0);
assert_eq!(delay.get_offset(&buffer, 1), 20.0);
assert_eq!(delay.get_offset(&buffer, 2), 10.0);
}
#[test]
#[should_panic]
fn test_non_power_of_two() {
let _ = ResourceDelay::new(Window { len: 3, start: 0 });
}
#[test]
fn test_boundary_clamping() {
let size = 4;
let buffer = vec![1.0, 2.0, 3.0, 4.0];
let delay = ResourceDelay::new(Window {
len: size,
start: 0,
});
let val = delay.get_offset(&buffer, 100);
assert!(val > 0.0);
}
}