Skip to main content

rill_core_dsp/generators/
reader.rs

1use rill_core::interpolate::Interpolate;
2use rill_core::Transcendental;
3
4fn len_remainder(pos: f64, len: f64) -> f64 {
5    let r = pos % len;
6    if r < 0.0 { r + len } else { r }
7}
8
9/// Heap-allocated buffer reader with fractional-position interpolation.
10///
11/// When `wrap` is true the position wraps modulo `len` (periodic / wavetable mode).
12/// When `wrap` is false the position clamps at buffer boundaries (sample mode).
13pub struct InterpolatedReader<T> {
14    buffer: Box<[T]>,
15    position: f64,
16    rate: f64,
17    cubic: bool,
18    wrap: bool,
19}
20
21impl<T: Transcendental + Copy> InterpolatedReader<T> {
22    pub fn new(buffer: Vec<T>) -> Self {
23        Self {
24            buffer: buffer.into_boxed_slice(),
25            position: 0.0,
26            rate: 1.0,
27            cubic: false,
28            wrap: false,
29        }
30    }
31
32    pub fn from_boxed(buffer: Box<[T]>) -> Self {
33        Self {
34            buffer,
35            position: 0.0,
36            rate: 1.0,
37            cubic: false,
38            wrap: false,
39        }
40    }
41
42    #[inline(always)]
43    pub fn len(&self) -> usize {
44        self.buffer.len()
45    }
46
47    #[inline(always)]
48    pub fn is_empty(&self) -> bool {
49        self.buffer.is_empty()
50    }
51
52    #[inline(always)]
53    pub fn position(&self) -> f64 {
54        self.position
55    }
56
57    #[inline(always)]
58    pub fn set_position(&mut self, pos: f64) {
59        self.position = pos;
60    }
61
62    #[inline(always)]
63    pub fn rate(&self) -> f64 {
64        self.rate
65    }
66
67    #[inline(always)]
68    pub fn set_rate(&mut self, rate: f64) {
69        self.rate = rate;
70    }
71
72    #[inline(always)]
73    pub fn is_cubic(&self) -> bool {
74        self.cubic
75    }
76
77    #[inline(always)]
78    pub fn set_cubic(&mut self, cubic: bool) {
79        self.cubic = cubic;
80    }
81
82    #[inline(always)]
83    pub fn is_wrap(&self) -> bool {
84        self.wrap
85    }
86
87    #[inline(always)]
88    pub fn set_wrap(&mut self, wrap: bool) {
89        self.wrap = wrap;
90    }
91
92    pub fn set_buffer(&mut self, buffer: Vec<T>) {
93        self.buffer = buffer.into_boxed_slice();
94        self.position = 0.0;
95    }
96
97    #[inline(always)]
98    pub fn as_slice(&self) -> &[T] {
99        &self.buffer
100    }
101
102    /// Wrap-aware linear interpolation.
103    fn read_wrap_linear(&self, pos: f64) -> T {
104        let len = self.len();
105        let i0 = pos.floor() as usize % len;
106        let i1 = (i0 + 1) % len;
107        let frac = T::from_f64(pos.fract());
108        let a = self.buffer[i0];
109        let b = self.buffer[i1];
110        a + (b - a) * frac
111    }
112
113    /// Wrap-aware cubic Hermite interpolation (periodic boundary).
114    fn read_wrap_cubic(&self, pos: f64) -> T {
115        let len = self.len();
116        let i = pos.floor() as usize;
117        let i0 = (i + len - 1) % len;
118        let i1 = i % len;
119        let i2 = (i + 1) % len;
120        let i3 = (i + 2) % len;
121        let frac = T::from_f64(pos.fract());
122
123        let c0 = self.buffer[i1];
124        let c1 = (self.buffer[i2] - self.buffer[i0]) * T::from_f32(0.5);
125        let c2 = self.buffer[i0] * T::from_f32(-1.5)
126            + self.buffer[i1] * T::from_f32(2.0)
127            + self.buffer[i2] * T::from_f32(-0.5);
128        let c3 = self.buffer[i0] * T::from_f32(-0.5)
129            + self.buffer[i1] * T::from_f32(1.5)
130            + self.buffer[i2] * T::from_f32(-1.5)
131            + self.buffer[i3] * T::from_f32(0.5);
132
133        let f2 = frac * frac;
134        let f3 = f2 * frac;
135        c0 + c1 * frac + c2 * f2 + c3 * f3
136    }
137
138    /// Read a single sample at the current position without advancing.
139    #[inline(always)]
140    pub fn read_one(&self) -> T {
141        if self.is_empty() {
142            return T::ZERO;
143        }
144        let pos = if self.wrap {
145            len_remainder(self.position, self.len() as f64)
146        } else {
147            self.position
148        };
149        if self.cubic && self.len() >= 4 {
150            if self.wrap {
151                self.read_wrap_cubic(pos)
152            } else {
153                self.buffer.interpolate_cubic(pos)
154            }
155        } else if self.wrap {
156            self.read_wrap_linear(pos)
157        } else {
158            self.buffer.interpolate_linear(pos)
159        }
160    }
161
162    /// Advance position by `self.rate` (one sample).
163    #[inline(always)]
164    pub fn advance(&mut self) {
165        self.position += self.rate;
166    }
167
168    /// Read the next block of samples, advancing `self.position`.
169    pub fn render_block(&mut self, output: &mut [T]) {
170        if self.is_empty() {
171            for s in output.iter_mut() {
172                *s = T::ZERO;
173            }
174            return;
175        }
176
177        if self.wrap {
178            let len_f = self.len() as f64;
179            if self.cubic && self.len() >= 4 {
180                for s in output.iter_mut() {
181                    *s = self.read_wrap_cubic(len_remainder(self.position, len_f));
182                    self.position += self.rate;
183                }
184            } else {
185                for s in output.iter_mut() {
186                    *s = self.read_wrap_linear(len_remainder(self.position, len_f));
187                    self.position += self.rate;
188                }
189            }
190        } else if self.cubic {
191            for s in output.iter_mut() {
192                *s = self.buffer.interpolate_cubic(self.position);
193                self.position += self.rate;
194            }
195        } else {
196            for s in output.iter_mut() {
197                *s = self.buffer.interpolate_linear(self.position);
198                self.position += self.rate;
199            }
200        }
201    }
202}
203
204#[cfg(test)]
205mod tests {
206    use super::*;
207
208    #[test]
209    fn test_basic_read() {
210        let buf = vec![0.0, 1.0, 2.0, 3.0, 4.0];
211        let mut reader = InterpolatedReader::new(buf);
212        let mut out = [0.0f64; 4];
213        reader.render_block(&mut out);
214        assert_eq!(out, [0.0, 1.0, 2.0, 3.0]);
215        assert!((reader.position() - 4.0).abs() < 1e-10);
216    }
217
218    #[test]
219    fn test_rate_half() {
220        let buf = vec![0.0, 2.0, 4.0, 6.0, 8.0];
221        let mut reader = InterpolatedReader::new(buf);
222        reader.set_rate(0.5);
223        let mut out = [0.0f64; 4];
224        reader.render_block(&mut out);
225        assert!((out[0] - 0.0).abs() < 1e-10);
226        assert!((out[1] - 1.0).abs() < 1e-10);
227        assert!((out[2] - 2.0).abs() < 1e-10);
228        assert!((out[3] - 3.0).abs() < 1e-10);
229        assert!((reader.position() - 2.0).abs() < 1e-10);
230    }
231
232    #[test]
233    fn test_empty_buffer() {
234        let buf: Vec<f64> = vec![];
235        let mut reader = InterpolatedReader::new(buf);
236        let mut out = [1.0f64; 4];
237        reader.render_block(&mut out);
238        assert_eq!(out, [0.0; 4]);
239    }
240
241    #[test]
242    fn test_set_buffer() {
243        let buf = vec![0.0, 0.0];
244        let mut reader = InterpolatedReader::new(buf);
245        reader.set_position(10.0);
246        reader.set_buffer(vec![10.0, 20.0, 30.0]);
247        assert_eq!(reader.position(), 0.0);
248        assert_eq!(reader.len(), 3);
249    }
250
251    #[test]
252    fn test_wrap_linear() {
253        let buf = vec![0.0, 1.0, 2.0, 3.0];
254        let mut reader = InterpolatedReader::new(buf);
255        reader.set_wrap(true);
256        reader.set_position(3.5);
257        let mut out = [0.0f64; 2];
258        reader.render_block(&mut out);
259        assert!((out[0] - 1.5).abs() < 1e-10, "wrap 3.5 -> 1.5, got {}", out[0]);
260        assert!((out[1] - 0.5).abs() < 1e-10, "wrap 4.5 -> 0.5, got {}", out[1]);
261    }
262
263    #[test]
264    fn test_wrap_cubic_at_boundary() {
265        let buf = vec![0.0, 1.0, 2.0, 3.0];
266        let mut reader = InterpolatedReader::new(buf);
267        reader.set_wrap(true);
268        reader.set_cubic(true);
269        reader.set_position(0.0);
270        let mut out = [0.0f64; 1];
271        reader.render_block(&mut out);
272        assert!((out[0] - 0.0).abs() < 1e-10, "cubic wrap at 0 -> 0, got {}", out[0]);
273    }
274
275    #[test]
276    fn test_clamp_at_end() {
277        let buf = vec![10.0, 20.0];
278        let mut reader = InterpolatedReader::new(buf);
279        reader.set_position(5.0);
280        let mut out = [0.0f64; 3];
281        reader.render_block(&mut out);
282        assert_eq!(out, [20.0, 20.0, 20.0]);
283    }
284}