rescale_core/buffer/
delay_line.rs1use super::ring_buffer::RingBuffer;
9
10const INTERPOLATION_HEADROOM: usize = 4;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum InterpolationType {
15 None,
17 Linear,
19 Cubic,
21}
22
23#[derive(Debug, Clone)]
24pub struct DelayLine {
25 ring: RingBuffer<f32>,
26 max_delay: usize,
27}
28
29impl DelayLine {
30 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 #[inline(always)]
40 pub fn push(&mut self, sample: f32) {
41 self.ring.push(sample);
42 }
43
44 pub fn read_delay(&self, delay_samples: f32, interp: InterpolationType) -> f32 {
49 self.read_past(self.clamp_delay(delay_samples), interp)
50 }
51
52 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 #[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 pub fn clear(&mut self) {
76 self.ring.clear();
77 }
78
79 #[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 #[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 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 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 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}