Skip to main content

rill_core/buffer/
tape.rs

1use crate::buffer::Buffer;
2use crate::math::Transcendental;
3
4/// Heap-allocated ring buffer for tape delay — single-threaded.
5///
6/// Unlike [`DelayLine`](super::DelayLine), `TapeLoop` does NOT use const generics
7/// for its capacity — the buffer is allocated on the heap at runtime.
8/// This allows arbitrarily large delay lines (millions of samples) without
9/// stack overflow.
10///
11/// # Thread safety
12///
13/// `TapeLoop` is **not** thread-safe. It uses plain `T`, not `AtomicCell`,
14/// because it is only accessed from the single signal thread.
15///
16/// # Example
17///
18/// ```rust
19/// use rill_core::buffer::TapeLoop;
20///
21/// let mut tape = TapeLoop::<f32>::new(96000).unwrap();
22/// tape.write(0.5);
23/// let sample = tape.read(100);
24/// ```
25#[derive(Debug)]
26pub struct TapeLoop<T> {
27    buffer: Box<[T]>,
28    capacity: usize,
29    write_pos: usize,
30}
31
32impl<T: Transcendental> TapeLoop<T> {
33    /// Allocate a new tape loop with the given capacity (in samples).
34    pub fn new(capacity: usize) -> Option<Self> {
35        if capacity == 0 {
36            return None;
37        }
38        let mut vec = Vec::with_capacity(capacity);
39        for _ in 0..capacity {
40            vec.push(T::ZERO);
41        }
42        Some(Self {
43            buffer: vec.into_boxed_slice(),
44            capacity,
45            write_pos: 0,
46        })
47    }
48
49    /// Maximum capacity in samples.
50    pub fn capacity(&self) -> usize {
51        self.capacity
52    }
53    /// Current write cursor position.
54    pub fn write_pos(&self) -> usize {
55        self.write_pos
56    }
57
58    /// Write a single sample and advance the write cursor.
59    #[inline(always)]
60    pub fn write(&mut self, sample: T) {
61        self.buffer[self.write_pos] = sample;
62        self.write_pos = (self.write_pos + 1) % self.capacity;
63    }
64
65    /// Read a sample at `delay` samples behind the write position.
66    #[inline(always)]
67    pub fn read(&self, delay: usize) -> T {
68        let d = delay.min(self.capacity - 1);
69        let read_pos = if self.write_pos > d {
70            self.write_pos - 1 - d
71        } else {
72            self.capacity + self.write_pos - 1 - d
73        };
74        self.buffer[read_pos]
75    }
76
77    /// Read with linear interpolation between samples.
78    #[inline(always)]
79    pub fn read_interpolated(&self, delay: f64) -> T {
80        let d = delay as usize;
81        let frac = T::from_f64(delay.fract());
82        let s1 = self.read(d);
83        let s2 = self.read(d + 1);
84        s1 + (s2 - s1) * frac
85    }
86
87    /// Write a full block of samples.
88    #[inline(always)]
89    pub fn write_block(&mut self, block: &[T]) {
90        let len = block.len().min(self.capacity);
91        for (i, &b) in block.iter().enumerate().take(len) {
92            self.buffer[(self.write_pos + i) % self.capacity] = b;
93        }
94        self.write_pos = (self.write_pos + len) % self.capacity;
95    }
96
97    /// Read a full block starting at `delay` samples behind write position.
98    #[inline(always)]
99    pub fn read_block(&self, delay: usize, output: &mut [T]) {
100        let len = output.len().min(self.capacity);
101        let d = delay.min(self.capacity - 1);
102        for (i, out) in output.iter_mut().enumerate().take(len) {
103            *out = self.read(d + len - 1 - i);
104        }
105    }
106
107    /// Fill the entire buffer with a constant value.
108    pub fn fill(&mut self, value: T) {
109        for slot in self.buffer.iter_mut() {
110            *slot = value;
111        }
112    }
113
114    /// Reset write position and zero the buffer.
115    pub fn clear(&mut self) {
116        for slot in self.buffer.iter_mut() {
117            *slot = T::ZERO;
118        }
119        self.write_pos = 0;
120    }
121}
122
123// ── Buffer trait impl ──────────────────────────────────────────────
124
125impl<T: Transcendental> Buffer<T> for TapeLoop<T> {
126    fn capacity(&self) -> usize {
127        self.capacity
128    }
129
130    fn len(&self) -> usize {
131        self.capacity
132    }
133
134    fn as_slice(&self) -> &[T] {
135        &self.buffer
136    }
137
138    fn as_mut_slice(&mut self) -> &mut [T] {
139        &mut self.buffer
140    }
141
142    fn fill(&mut self, value: T) {
143        for slot in self.buffer.iter_mut() {
144            *slot = value;
145        }
146    }
147
148    fn copy_from(&mut self, src: &[T]) {
149        let len = src.len().min(self.capacity);
150        self.buffer[..len].copy_from_slice(&src[..len]);
151    }
152
153    fn clear(&mut self) {
154        for slot in self.buffer.iter_mut() {
155            *slot = T::ZERO;
156        }
157        self.write_pos = 0;
158    }
159}
160
161#[cfg(test)]
162mod tests {
163    use super::*;
164
165    #[test]
166    fn test_tape_basic_write_read() {
167        let mut tape = TapeLoop::<f32>::new(1024).unwrap();
168        tape.write(1.0);
169        tape.write(2.0);
170        tape.write(3.0);
171        assert_eq!(tape.read(0), 3.0);
172        assert_eq!(tape.read(1), 2.0);
173        assert_eq!(tape.read(2), 1.0);
174    }
175
176    #[test]
177    fn test_tape_wraparound() {
178        let mut tape = TapeLoop::<f32>::new(4).unwrap();
179        for i in 0..10 {
180            tape.write(i as f32);
181        }
182        assert_eq!(tape.read(0), 9.0);
183        assert_eq!(tape.read(1), 8.0);
184        assert_eq!(tape.read(2), 7.0);
185        assert_eq!(tape.read(3), 6.0);
186    }
187
188    #[test]
189    fn test_tape_block_ops() {
190        let mut tape = TapeLoop::<f32>::new(64).unwrap();
191        let block = [1.0f32; 64];
192        tape.write_block(&block);
193        let mut out = [0.0f32; 64];
194        tape.read_block(63, &mut out);
195        assert_eq!(out[0], 1.0);
196    }
197
198    #[test]
199    fn test_tape_large_capacity() {
200        let tape = TapeLoop::<f32>::new(1_000_000).unwrap();
201        assert_eq!(tape.capacity(), 1_000_000);
202    }
203
204    #[test]
205    fn test_tape_zero_capacity() {
206        assert!(TapeLoop::<f32>::new(0).is_none());
207    }
208
209    #[test]
210    fn test_read_interpolated() {
211        let mut tape = TapeLoop::<f32>::new(1024).unwrap();
212        tape.write(0.0);
213        tape.write(1.0);
214        let v = tape.read_interpolated(0.5);
215        assert!((v - 0.5).abs() < 0.01);
216    }
217}