synfx_dsp/delay.rs
1// Copyright (c) 2021-2022 Weird Constructor <weirdconstructor@gmail.com>
2// This file is a part of synfx-dsp. Released under GPL-3.0-or-later.
3// See README.md and COPYING for details.
4
5//! Interpolated delay line implementation and all-pass/comb filter implementations based on that.
6
7use crate::cubic_interpolate;
8use crate::{f, Flt};
9
10/// Default size of the delay buffer: 5 seconds at 8 times 48kHz
11const DEFAULT_DELAY_BUFFER_SAMPLES: usize = 8 * 48000 * 5;
12
13/// This is a delay buffer/line with linear and cubic interpolation.
14///
15/// It's the basic building block underneath the all-pass filter, comb filters and delay effects.
16/// You can use linear and cubic and no interpolation to access samples in the past. Either
17/// by sample offset or time (millisecond) based.
18#[derive(Debug, Clone, Default)]
19pub struct DelayBuffer<F: Flt> {
20 data: Vec<F>,
21 wr: usize,
22 srate: F,
23}
24
25impl<F: Flt> DelayBuffer<F> {
26 /// Creates a delay buffer with about 5 seconds of capacity at 8*48000Hz sample rate.
27 pub fn new() -> Self {
28 Self { data: vec![f(0.0); DEFAULT_DELAY_BUFFER_SAMPLES], wr: 0, srate: f(44100.0) }
29 }
30
31 /// Creates a delay buffer with the given amount of samples capacity.
32 pub fn new_with_size(size: usize) -> Self {
33 Self { data: vec![f(0.0); size], wr: 0, srate: f(44100.0) }
34 }
35
36 /// Sets the sample rate that is used for milliseconds => sample conversion.
37 pub fn set_sample_rate(&mut self, srate: F) {
38 self.srate = srate;
39 }
40
41 /// Reset the delay buffer contents and write position.
42 pub fn reset(&mut self) {
43 self.data.fill(f(0.0));
44 self.wr = 0;
45 }
46
47 /// Feed one sample into the delay line and increment the write pointer.
48 /// Please note: For sample accurate feedback you need to retrieve the
49 /// output of the delay line before feeding in a new signal.
50 #[inline]
51 pub fn feed(&mut self, input: F) {
52 self.data[self.wr] = input;
53 self.wr = (self.wr + 1) % self.data.len();
54 }
55
56 /// Combines [DelayBuffer::cubic_interpolate_at] and [DelayBuffer::feed]
57 /// into one convenient function.
58 #[inline]
59 pub fn next_cubic(&mut self, delay_time_ms: F, input: F) -> F {
60 let res = self.cubic_interpolate_at(delay_time_ms);
61 self.feed(input);
62 res
63 }
64
65 /// Combines [DelayBuffer::linear_interpolate_at] and [DelayBuffer::feed]
66 /// into one convenient function.
67 #[inline]
68 pub fn next_linear(&mut self, delay_time_ms: F, input: F) -> F {
69 let res = self.linear_interpolate_at(delay_time_ms);
70 self.feed(input);
71 res
72 }
73
74 /// Combines [DelayBuffer::nearest_at] and [DelayBuffer::feed]
75 /// into one convenient function.
76 #[inline]
77 pub fn next_nearest(&mut self, delay_time_ms: F, input: F) -> F {
78 let res = self.nearest_at(delay_time_ms);
79 self.feed(input);
80 res
81 }
82
83 /// Shorthand for [DelayBuffer::cubic_interpolate_at].
84 #[inline]
85 pub fn tap_c(&self, delay_time_ms: F) -> F {
86 self.cubic_interpolate_at(delay_time_ms)
87 }
88
89 /// Shorthand for [DelayBuffer::cubic_interpolate_at].
90 #[inline]
91 pub fn tap_n(&self, delay_time_ms: F) -> F {
92 self.nearest_at(delay_time_ms)
93 }
94
95 /// Shorthand for [DelayBuffer::cubic_interpolate_at].
96 #[inline]
97 pub fn tap_l(&self, delay_time_ms: F) -> F {
98 self.linear_interpolate_at(delay_time_ms)
99 }
100
101 /// Fetch a sample from the delay buffer at the given tim with linear interpolation.
102 ///
103 /// * `delay_time_ms` - Delay time in milliseconds.
104 #[inline]
105 pub fn linear_interpolate_at(&self, delay_time_ms: F) -> F {
106 self.linear_interpolate_at_s((delay_time_ms * self.srate) / f(1000.0))
107 }
108
109 /// Fetch a sample from the delay buffer at the given offset with linear interpolation.
110 ///
111 /// * `s_offs` - Sample offset in samples.
112 #[inline]
113 pub fn linear_interpolate_at_s(&self, s_offs: F) -> F {
114 let data = &self.data[..];
115 let len = data.len();
116 let offs = s_offs.floor().to_usize().unwrap_or(0) % len;
117 let fract = s_offs.fract();
118
119 // one extra offset, because feed() advances self.wr to the next writing position!
120 let i = (self.wr + len) - (offs + 1);
121 let x0 = data[i % len];
122 let x1 = data[(i - 1) % len];
123
124 let res = x0 + fract * (x1 - x0);
125 //d// eprintln!(
126 //d// "INTERP: {:6.4} x0={:6.4} x1={:6.4} fract={:6.4} => {:6.4}",
127 //d// s_offs.to_f64().unwrap_or(0.0),
128 //d// x0.to_f64().unwrap(),
129 //d// x1.to_f64().unwrap(),
130 //d// fract.to_f64().unwrap(),
131 //d// res.to_f64().unwrap(),
132 //d// );
133 res
134 }
135
136 /// Fetch a sample from the delay buffer at the given time with cubic interpolation.
137 ///
138 /// * `delay_time_ms` - Delay time in milliseconds.
139 #[inline]
140 pub fn cubic_interpolate_at(&self, delay_time_ms: F) -> F {
141 self.cubic_interpolate_at_s((delay_time_ms * self.srate) / f(1000.0))
142 }
143
144 /// Fetch a sample from the delay buffer at the given offset with cubic interpolation.
145 ///
146 /// * `s_offs` - Sample offset in samples into the past of the [DelayBuffer]
147 /// from the current write (or the "now") position.
148 #[inline]
149 pub fn cubic_interpolate_at_s(&self, s_offs: F) -> F {
150 let data = &self.data[..];
151 let len = data.len();
152 let offs = s_offs.floor().to_usize().unwrap_or(0) % len;
153 let fract = s_offs.fract();
154
155 // (offs + 1) offset for compensating that self.wr points to the next
156 // unwritten position.
157 // Additional (offs + 1 + 1) offset for cubic_interpolate, which
158 // interpolates into the past through the delay buffer.
159 let i = (self.wr + len) - (offs + 2);
160 let res = cubic_interpolate(data, len, i, f::<F>(1.0) - fract);
161 // eprintln!(
162 // "cubic at={} ({:6.4}) res={:6.4}",
163 // i % len,
164 // s_offs.to_f64().unwrap(),
165 // res.to_f64().unwrap()
166 // );
167 res
168 }
169
170 /// Fetch a sample from the delay buffer at the given time without any interpolation.
171 ///
172 /// * `delay_time_ms` - Delay time in milliseconds.
173 #[inline]
174 pub fn nearest_at(&self, delay_time_ms: F) -> F {
175 let len = self.data.len();
176 let offs = ((delay_time_ms * self.srate) / f(1000.0)).floor().to_usize().unwrap_or(0) % len;
177 // (offs + 1) one extra offset, because feed() advances
178 // self.wr to the next writing position!
179 let idx = ((self.wr + len) - (offs + 1)) % len;
180 self.data[idx]
181 }
182
183 /// Fetch a sample from the delay buffer at the given number of samples in the past.
184 #[inline]
185 pub fn at(&self, delay_sample_count: usize) -> F {
186 let len = self.data.len();
187 // (delay_sample_count + 1) one extra offset, because feed() advances self.wr to
188 // the next writing position!
189 let idx = ((self.wr + len) - (delay_sample_count + 1)) % len;
190 self.data[idx]
191 }
192}
193
194/// Default size of the delay buffer: 1 seconds at 8 times 48kHz
195const DEFAULT_ALLPASS_COMB_SAMPLES: usize = 8 * 48000;
196
197/// An all-pass filter based on a delay line.
198#[derive(Debug, Clone, Default)]
199pub struct AllPass<F: Flt> {
200 delay: DelayBuffer<F>,
201}
202
203impl<F: Flt> AllPass<F> {
204 /// Creates a new all-pass filter with about 1 seconds space for samples.
205 pub fn new() -> Self {
206 Self { delay: DelayBuffer::new_with_size(DEFAULT_ALLPASS_COMB_SAMPLES) }
207 }
208
209 /// Set the sample rate for millisecond based access.
210 pub fn set_sample_rate(&mut self, srate: F) {
211 self.delay.set_sample_rate(srate);
212 }
213
214 /// Reset the internal delay buffer.
215 pub fn reset(&mut self) {
216 self.delay.reset();
217 }
218
219 /// Access the internal delay at the given amount of milliseconds in the past.
220 #[inline]
221 pub fn delay_tap_n(&self, time_ms: F) -> F {
222 self.delay.tap_n(time_ms)
223 }
224
225 /// Retrieve the next (cubic interpolated) sample from the all-pass
226 /// filter while feeding in the next.
227 ///
228 /// * `time_ms` - Delay time in milliseconds.
229 /// * `g` - Feedback factor (usually something around 0.7 is common)
230 /// * `v` - The new input sample to feed the filter.
231 #[inline]
232 pub fn next(&mut self, time_ms: F, g: F, v: F) -> F {
233 let s = self.delay.cubic_interpolate_at(time_ms);
234 let input = v + -g * s;
235 self.delay.feed(input);
236 input * g + s
237 }
238
239 /// Retrieve the next (linear interpolated) sample from the all-pass
240 /// filter while feeding in the next.
241 ///
242 /// * `time_ms` - Delay time in milliseconds.
243 /// * `g` - Feedback factor (usually something around 0.7 is common)
244 /// * `v` - The new input sample to feed the filter.
245 #[inline]
246 pub fn next_linear(&mut self, time_ms: F, g: F, v: F) -> F {
247 let s = self.delay.linear_interpolate_at(time_ms);
248 let input = v + -g * s;
249 self.delay.feed(input);
250 input * g + s
251 }
252}
253
254#[derive(Debug, Clone)]
255pub struct Comb {
256 delay: DelayBuffer<f32>,
257}
258
259impl Comb {
260 pub fn new() -> Self {
261 Self { delay: DelayBuffer::new_with_size(DEFAULT_ALLPASS_COMB_SAMPLES) }
262 }
263
264 pub fn set_sample_rate(&mut self, srate: f32) {
265 self.delay.set_sample_rate(srate);
266 }
267
268 pub fn reset(&mut self) {
269 self.delay.reset();
270 }
271
272 #[inline]
273 pub fn delay_tap_c(&self, time_ms: f32) -> f32 {
274 self.delay.tap_c(time_ms)
275 }
276
277 #[inline]
278 pub fn delay_tap_n(&self, time_ms: f32) -> f32 {
279 self.delay.tap_n(time_ms)
280 }
281
282 #[inline]
283 pub fn next_feedback(&mut self, time: f32, g: f32, v: f32) -> f32 {
284 let s = self.delay.cubic_interpolate_at(time);
285 let v = v + s * g;
286 self.delay.feed(v);
287 v
288 }
289
290 #[inline]
291 pub fn next_feedforward(&mut self, time: f32, g: f32, v: f32) -> f32 {
292 let s = self.delay.next_cubic(time, v);
293 v + s * g
294 }
295}