Skip to main content

rill_core/buffer/
tape.rs

1use crate::math::Transcendental;
2use crate::buffer::Buffer;
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 audio 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 { self.capacity }
51    /// Current write cursor position.
52    pub fn write_pos(&self) -> usize { self.write_pos }
53
54    /// Write a single sample and advance the write cursor.
55    #[inline(always)]
56    pub fn write(&mut self, sample: T) {
57        self.buffer[self.write_pos] = sample;
58        self.write_pos = (self.write_pos + 1) % self.capacity;
59    }
60
61    /// Read a sample at `delay` samples behind the write position.
62    #[inline(always)]
63    pub fn read(&self, delay: usize) -> T {
64        let d = delay.min(self.capacity - 1);
65        let read_pos = if self.write_pos > d {
66            self.write_pos - 1 - d
67        } else {
68            self.capacity + self.write_pos - 1 - d
69        };
70        self.buffer[read_pos]
71    }
72
73    /// Read with linear interpolation between samples.
74    #[inline(always)]
75    pub fn read_interpolated(&self, delay: f64) -> T {
76        let d = delay as usize;
77        let frac = T::from_f64(delay.fract());
78        let s1 = self.read(d);
79        let s2 = self.read(d + 1);
80        s1 + (s2 - s1) * frac
81    }
82
83    /// Write a full block of samples.
84    #[inline(always)]
85    pub fn write_block(&mut self, block: &[T]) {
86        let len = block.len().min(self.capacity);
87        for i in 0..len {
88            self.buffer[(self.write_pos + i) % self.capacity] = block[i];
89        }
90        self.write_pos = (self.write_pos + len) % self.capacity;
91    }
92
93    /// Read a full block starting at `delay` samples behind write position.
94    #[inline(always)]
95    pub fn read_block(&self, delay: usize, output: &mut [T]) {
96        let len = output.len().min(self.capacity);
97        let d = delay.min(self.capacity - 1);
98        for i in 0..len {
99            output[i] = self.read(d + len - 1 - i);
100        }
101    }
102
103    /// Fill the entire buffer with a constant value.
104    pub fn fill(&mut self, value: T) {
105        for slot in self.buffer.iter_mut() {
106            *slot = value;
107        }
108    }
109
110    /// Reset write position and zero the buffer.
111    pub fn clear(&mut self) {
112        for slot in self.buffer.iter_mut() {
113            *slot = T::ZERO;
114        }
115        self.write_pos = 0;
116    }
117}
118
119// ── Buffer trait impl ──────────────────────────────────────────────
120
121impl<T: Transcendental> Buffer<T> for TapeLoop<T> {
122    fn len(&self) -> usize {
123        self.capacity
124    }
125
126    fn as_slice(&self) -> &[T] {
127        &self.buffer
128    }
129
130    fn as_mut_slice(&mut self) -> &mut [T] {
131        &mut self.buffer
132    }
133
134    fn fill(&mut self, value: T)
135    where
136        T: Copy,
137    {
138        for slot in self.buffer.iter_mut() {
139            *slot = value;
140        }
141    }
142
143    fn copy_from(&mut self, src: &[T])
144    where
145        T: Copy,
146    {
147        let len = src.len().min(self.capacity);
148        self.buffer[..len].copy_from_slice(&src[..len]);
149    }
150}
151
152#[cfg(test)]
153mod tests {
154    use super::*;
155
156    #[test]
157    fn test_tape_basic_write_read() {
158        let mut tape = TapeLoop::<f32>::new(1024).unwrap();
159        tape.write(1.0);
160        tape.write(2.0);
161        tape.write(3.0);
162        assert_eq!(tape.read(0), 3.0);
163        assert_eq!(tape.read(1), 2.0);
164        assert_eq!(tape.read(2), 1.0);
165    }
166
167    #[test]
168    fn test_tape_wraparound() {
169        let mut tape = TapeLoop::<f32>::new(4).unwrap();
170        for i in 0..10 { tape.write(i as f32); }
171        assert_eq!(tape.read(0), 9.0);
172        assert_eq!(tape.read(1), 8.0);
173        assert_eq!(tape.read(2), 7.0);
174        assert_eq!(tape.read(3), 6.0);
175    }
176
177    #[test]
178    fn test_tape_block_ops() {
179        let mut tape = TapeLoop::<f32>::new(64).unwrap();
180        let block = [1.0f32; 64];
181        tape.write_block(&block);
182        let mut out = [0.0f32; 64];
183        tape.read_block(63, &mut out);
184        assert_eq!(out[0], 1.0);
185    }
186
187    #[test]
188    fn test_tape_large_capacity() {
189        let tape = TapeLoop::<f32>::new(1_000_000).unwrap();
190        assert_eq!(tape.capacity(), 1_000_000);
191    }
192
193    #[test]
194    fn test_tape_zero_capacity() {
195        assert!(TapeLoop::<f32>::new(0).is_none());
196    }
197
198    #[test]
199    fn test_read_interpolated() {
200        let mut tape = TapeLoop::<f32>::new(1024).unwrap();
201        tape.write(0.0);
202        tape.write(1.0);
203        let v = tape.read_interpolated(0.5);
204        assert!((v - 0.5).abs() < 0.01);
205    }
206}