Skip to main content

rescale_core/buffer/
delay_line.rs

1//! DelayLine dengan interpolasi pecahan (Fractional Delay).
2//!
3//! Semantik utama: [`DelayLine::read_delay`] selalu diukur terhadap sampel
4//! **terakhir yang di-push** (delay `0.0` = sampel terbaru, `1.0` = satu sampel
5//! sebelumnya). Pembaca yang harus mengambil nilai sebelum `push` memakai
6//! [`DelayLine::read_delay_before_push`].
7
8use super::ring_buffer::RingBuffer;
9
10/// Cadangan slot di ring buffer untuk interpolasi cubic (membutuhkan `past + 2`).
11const INTERPOLATION_HEADROOM: usize = 4;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum InterpolationType {
15    /// Bulatkan ke delay integer terdekat.
16    None,
17    /// Interpolasi linear (efisien, mulus).
18    Linear,
19    /// Interpolasi cubic Hermite 4 titik (kualitas tertinggi).
20    Cubic,
21}
22
23#[derive(Debug, Clone)]
24pub struct DelayLine {
25    ring: RingBuffer<f32>,
26    max_delay: usize,
27}
28
29impl DelayLine {
30    /// Membuat DelayLine baru dengan delay maksimum dalam sample.
31    pub fn new(max_delay_samples: usize) -> Self {
32        Self {
33            ring: RingBuffer::new(max_delay_samples + INTERPOLATION_HEADROOM),
34            max_delay: max_delay_samples,
35        }
36    }
37
38    /// Memasukkan sample audio baru ke delay line.
39    #[inline(always)]
40    pub fn push(&mut self, sample: f32) {
41        self.ring.push(sample);
42    }
43
44    /// Membaca sinyal tertunda relatif terhadap sampel terakhir yang di-push.
45    ///
46    /// `delay_samples = 1.0` berarti `x[n - 1]` bila `x[n]` adalah sampel yang
47    /// terakhir di-push.
48    pub fn read_delay(&self, delay_samples: f32, interp: InterpolationType) -> f32 {
49        self.read_past(self.clamp_delay(delay_samples), interp)
50    }
51
52    /// Varian untuk pembaca yang membaca **sebelum** `push` (mis. efek feedback
53    /// yang perlu nilai tertunda untuk menghitung input berikutnya).
54    ///
55    /// Hasilnya setara dengan `read_delay(delay_samples)` **setelah** sampel baru
56    /// di-push; pembaca menghitung satu langkah lebih muda, bukan menebak posisi.
57    pub fn read_delay_before_push(&self, delay_samples: f32, interp: InterpolationType) -> f32 {
58        let one_sample_younger = self.clamp_delay(delay_samples) - 1.0;
59        self.read_past(one_sample_younger.max(0.0), interp)
60    }
61
62    /// Memproses satu sample: push `input` lalu kembalikan output delay.
63    #[inline]
64    pub fn process_sample(
65        &mut self,
66        input: f32,
67        delay_samples: f32,
68        interp: InterpolationType,
69    ) -> f32 {
70        self.push(input);
71        self.read_delay(delay_samples, interp)
72    }
73
74    /// Reset internal delay memory.
75    pub fn clear(&mut self) {
76        self.ring.clear();
77    }
78
79    /// Batasi delay ke rentang yang valid agar indeks pembacaan aman.
80    #[inline(always)]
81    fn clamp_delay(&self, delay_samples: f32) -> f32 {
82        delay_samples.clamp(0.0, self.max_delay as f32)
83    }
84
85    /// Membaca sampel sejauh `past` langkah ke belakang (0 = sampel terbaru).
86    #[inline(always)]
87    fn read_past(&self, past: f32, interp: InterpolationType) -> f32 {
88        match interp {
89            InterpolationType::None => self.ring.read_past(past.round() as usize),
90            InterpolationType::Linear => self.interpolate_linear(past),
91            InterpolationType::Cubic => self.interpolate_cubic(past),
92        }
93    }
94
95    #[inline(always)]
96    fn interpolate_linear(&self, past: f32) -> f32 {
97        let index = past.floor() as usize;
98        let fraction = past - index as f32;
99        let older = self.ring.read_past(index);
100        let newer = self.ring.read_past(index + 1);
101        older + fraction * (newer - older)
102    }
103
104    #[inline(always)]
105    fn interpolate_cubic(&self, past: f32) -> f32 {
106        let index = past.floor() as usize;
107        let fraction = past - index as f32;
108
109        let ym1 = if index > 0 {
110            self.ring.read_past(index - 1)
111        } else {
112            self.ring.read_past(0)
113        };
114        let y0 = self.ring.read_past(index);
115        let y1 = self.ring.read_past(index + 1);
116        let y2 = self.ring.read_past(index + 2);
117
118        let c0 = y0;
119        let c1 = 0.5 * (y1 - ym1);
120        let c2 = ym1 - 2.5 * y0 + 2.0 * y1 - 0.5 * y2;
121        let c3 = 0.5 * (y2 - ym1) + 1.5 * (y0 - y1);
122
123        ((c3 * fraction + c2) * fraction + c1) * fraction + c0
124    }
125}
126
127#[cfg(test)]
128mod tests {
129    use super::*;
130
131    /// Delay line dengan tiga sampel berurutan: 0.0, 10.0, 20.0.
132    fn delay_line_with_ramp() -> DelayLine {
133        let mut line = DelayLine::new(16);
134        line.push(0.0);
135        line.push(10.0);
136        line.push(20.0);
137        line
138    }
139
140    #[test]
141    fn integer_delay_is_relative_to_the_last_pushed_sample() {
142        let line = delay_line_with_ramp();
143
144        assert!((line.read_delay(0.0, InterpolationType::None) - 20.0).abs() < 1e-4);
145        assert!((line.read_delay(1.0, InterpolationType::None) - 10.0).abs() < 1e-4);
146        assert!((line.read_delay(2.0, InterpolationType::None) - 0.0).abs() < 1e-4);
147    }
148
149    #[test]
150    fn fractional_delay_interpolates_between_adjacent_samples() {
151        let line = delay_line_with_ramp();
152
153        // delay 1.5 = rata-rata x[n-1] dan x[n-2] = (10 + 0) / 2.
154        let value = line.read_delay(1.5, InterpolationType::Linear);
155        assert!((value - 5.0).abs() < 1e-4, "expected 5.0, got {value}");
156    }
157
158    #[test]
159    fn before_push_variant_matches_post_push_delay() {
160        let line = delay_line_with_ramp();
161
162        // Pembaca pre-push melihat satu sampel lebih muda: delay 1 = x[n-1] = 20.0.
163        assert!((line.read_delay_before_push(1.0, InterpolationType::None) - 20.0).abs() < 1e-4);
164        assert!((line.read_delay_before_push(2.0, InterpolationType::None) - 10.0).abs() < 1e-4);
165    }
166
167    #[test]
168    fn process_sample_outputs_input_delayed_by_requested_amount() {
169        let mut line = DelayLine::new(8);
170
171        let first_output = line.process_sample(1.0, 2.0, InterpolationType::None);
172        let second_output = line.process_sample(0.0, 2.0, InterpolationType::None);
173        let third_output = line.process_sample(0.0, 2.0, InterpolationType::None);
174
175        assert!((first_output - 0.0).abs() < 1e-6);
176        assert!((second_output - 0.0).abs() < 1e-6);
177        assert!(
178            (third_output - 1.0).abs() < 1e-6,
179            "impulse harus muncul setelah tepat 2 sampel"
180        );
181    }
182}