Skip to main content

rill_core/buffer/
delay.rs

1//! # Delay line for audio effects
2
3use super::array_from_fn;
4use crate::buffer::{AtomicCell, AtomicStats, SignalBuffer, BufferStats};
5use crate::math::Transcendental;
6use core::marker::PhantomData;
7use core::ops::{Index, IndexMut};
8
9// ============================================================================
10// DelayLine
11// ============================================================================
12
13/// Delay line for audio effects
14///
15/// Provides a circular buffer for implementing delay, reverb,
16/// and other time-based effects.
17#[repr(align(64))]
18pub struct DelayLine<T: Transcendental, const MAX_DELAY: usize> {
19    /// Circular buffer storage using AtomicCell for each sample
20    buffer: [AtomicCell<T>; MAX_DELAY],
21
22    /// Current write position in the circular buffer
23    write_pos: usize,
24
25    /// Current delay in samples
26    delay_samples: usize,
27
28    /// Sample rate in Hz (for time-based delay setting)
29    sample_rate: f32,
30
31    /// Atomic statistics for performance monitoring
32    stats: AtomicStats,
33
34    /// Phantom data
35    _phantom: PhantomData<T>,
36}
37
38impl<T: Transcendental, const MAX_DELAY: usize> DelayLine<T, MAX_DELAY> {
39    /// Create a new delay line with the specified sample rate
40    ///
41    /// # Arguments
42    /// * `sample_rate` - Sample rate in Hz (e.g., 44100.0)
43    ///
44    /// # Panics
45    /// Panics if `MAX_DELAY` is 0.
46    pub fn new(sample_rate: f32) -> Self {
47        assert!(MAX_DELAY > 0, "DelayLine must have MAX_DELAY > 0");
48
49        // Create buffer with default values
50        let buffer = array_from_fn(|_| AtomicCell::new(T::ZERO));
51
52        Self {
53            buffer,
54            write_pos: 0,
55            delay_samples: 0,
56            sample_rate,
57            stats: AtomicStats::new(),
58            _phantom: PhantomData,
59        }
60    }
61
62    /// Set delay time in seconds
63    #[inline(always)]
64    pub fn set_delay(&mut self, delay_sec: f32) {
65        let samples = (delay_sec * self.sample_rate) as usize;
66        self.delay_samples = samples.min(MAX_DELAY - 1);
67    }
68
69    /// Set delay in samples
70    #[inline(always)]
71    pub fn set_delay_samples(&mut self, samples: usize) {
72        self.delay_samples = samples.min(MAX_DELAY - 1);
73    }
74
75    /// Get current delay in samples
76    #[inline(always)]
77    pub fn delay_samples(&self) -> usize {
78        self.delay_samples
79    }
80
81    /// Get maximum possible delay in samples
82    #[inline(always)]
83    pub const fn max_delay(&self) -> usize {
84        MAX_DELAY
85    }
86
87    /// Get the current sample rate
88    #[inline(always)]
89    pub fn sample_rate(&self) -> f32 {
90        self.sample_rate
91    }
92
93    /// Write a sample and return the delayed sample
94    #[inline(always)]
95    pub fn write(&mut self, input: T) -> T {
96        // Write current sample
97        self.buffer[self.write_pos].store(input);
98
99        // Calculate read position for delayed sample
100        let read_pos = if self.write_pos >= self.delay_samples {
101            self.write_pos - self.delay_samples
102        } else {
103            MAX_DELAY + self.write_pos - self.delay_samples
104        };
105
106        // Read delayed sample
107        let output = self.buffer[read_pos].load();
108
109        // Advance write position
110        self.write_pos = (self.write_pos + 1) % MAX_DELAY;
111
112        // Update statistics
113        self.stats.record_write();
114        self.stats.record_read();
115        self.stats.update_peak(MAX_DELAY);
116
117        output
118    }
119
120    /// Read the delayed sample without writing
121    #[inline(always)]
122    pub fn read(&self) -> T {
123        let read_pos = if self.write_pos >= self.delay_samples {
124            self.write_pos - self.delay_samples
125        } else {
126            MAX_DELAY + self.write_pos - self.delay_samples
127        };
128
129        self.buffer[read_pos].load()
130    }
131
132    /// Read sample at arbitrary delay (0 = most recent)
133    #[inline(always)]
134    pub fn read_delayed(&self, delay: usize) -> T {
135        debug_assert!(
136            delay < MAX_DELAY,
137            "Delay {} out of range (max {})",
138            delay,
139            MAX_DELAY
140        );
141
142        let read_pos = if self.write_pos >= delay + 1 {
143            self.write_pos - 1 - delay
144        } else {
145            MAX_DELAY + self.write_pos - 1 - delay
146        };
147
148        self.buffer[read_pos].load()
149    }
150
151    /// Read with linear interpolation between samples
152    #[inline(always)]
153    pub fn read_interpolated(&self, delay_frac: f32) -> T {
154        let delay_int = delay_frac.floor() as usize;
155        let frac = T::from_f32(delay_frac.fract());
156
157        let s1 = self.read_delayed(delay_int);
158
159        if delay_int == MAX_DELAY - 1 {
160            s1
161        } else {
162            let s2 = self.read_delayed(delay_int + 1);
163            s1 + (s2 - s1) * frac
164        }
165    }
166
167    /// Clear the delay line (fill with zeros)
168    pub fn clear(&mut self) {
169        for i in 0..MAX_DELAY {
170            self.buffer[i].store(T::ZERO);
171        }
172        self.write_pos = 0;
173        self.stats.reset();
174    }
175
176    /// Get current write position
177    #[inline(always)]
178    pub fn write_position(&self) -> usize {
179        self.write_pos
180    }
181}
182
183// ============================================================================
184// Index implementations
185// ============================================================================
186
187impl<T: Transcendental, const MAX_DELAY: usize> Index<usize> for DelayLine<T, MAX_DELAY> {
188    type Output = T;
189
190    fn index(&self, _index: usize) -> &Self::Output {
191        // This is a bit tricky with AtomicCell - we need to return a reference
192        // For now, we'll just return a reference to the AtomicCell's internal value
193        // but this requires unsafe code. In practice, direct indexing might not be needed.
194        unimplemented!("Direct indexing not supported with AtomicCell")
195    }
196}
197
198impl<T: Transcendental, const MAX_DELAY: usize> IndexMut<usize> for DelayLine<T, MAX_DELAY> {
199    fn index_mut(&mut self, _index: usize) -> &mut Self::Output {
200        unimplemented!("Direct indexing not supported with AtomicCell")
201    }
202}
203
204// ============================================================================
205// SignalBuffer Implementation
206// ============================================================================
207
208impl<T: Transcendental, const MAX_DELAY: usize> SignalBuffer<T> for DelayLine<T, MAX_DELAY> {
209    fn capacity(&self) -> usize {
210        MAX_DELAY
211    }
212
213    fn len(&self) -> usize {
214        MAX_DELAY
215    }
216
217    fn is_empty(&self) -> bool {
218        false
219    }
220
221    fn is_full(&self) -> bool {
222        true
223    }
224
225    fn clear(&mut self) {
226        self.clear();
227    }
228
229    fn stats(&self) -> BufferStats {
230        let mut stats = self.stats.snapshot();
231        stats.fill_level = 1.0;
232        stats
233    }
234
235    fn reset_stats(&mut self) {
236        self.stats.reset();
237    }
238}
239
240// ============================================================================
241// Tests
242// ============================================================================
243
244#[cfg(test)]
245mod tests {
246    use super::*;
247
248    #[test]
249    fn test_delay_line_basic() {
250        let mut delay = DelayLine::<f32, 1024>::new(44100.0);
251        delay.set_delay_samples(100);
252
253        for i in 0..200 {
254            let out = delay.write(i as f32);
255            if i >= 100 {
256                assert_eq!(out, (i - 100) as f32);
257            }
258        }
259    }
260
261    #[test]
262    fn test_delay_line_read_delayed() {
263        let mut delay = DelayLine::<f32, 1024>::new(44100.0);
264
265        for i in 0..1024 {
266            delay.write(i as f32);
267        }
268
269        assert_eq!(delay.read_delayed(0), 1023.0);
270        assert_eq!(delay.read_delayed(100), 923.0);
271    }
272
273    #[test]
274    fn test_delay_line_interpolation() {
275        let mut delay = DelayLine::<f32, 1024>::new(44100.0);
276
277        for i in 0..1024 {
278            delay.write(i as f32);
279        }
280
281        let val = delay.read_interpolated(100.5);
282        assert!((val - 922.5).abs() < 0.01);
283    }
284}