Mozzi  version v2.0
sound synthesis library for Arduino
AudioDelayFeedback.h
1 /*
2  * AudioDelayFeedback.h
3  *
4  * This file is part of Mozzi.
5  *
6  * Copyright 2012-2024 Tim Barrass and the Mozzi Team
7  *
8  * Mozzi is licensed under the GNU Lesser General Public Licence (LGPL) Version 2.1 or later.
9  *
10  */
11 
12 #ifndef AUDIODELAY_FEEDBACK_H_
13 #define AUDIODELAY_FEEDBACK_H_
14 
15 #include <Arduino.h>
16 
17 #include "mozzi_utils.h"
18 #include "meta.h"
19 
20 enum interpolation_types {LINEAR,ALLPASS};
21 
22 
23 /** Audio delay line with feedback for comb filter, flange, chorus and short echo effects.
24 @tparam NUM_BUFFER_SAMPLES is the length of the delay buffer in samples, and should be a
25 power of two. The maximum delay length which will fit in an atmega328 is half
26 that of a plain AudioDelay object, in this case 256 cells, or about 15
27 milliseconds. AudioDelayFeedback uses int16_t sized cells to accomodate the higher
28 amplitude of direct input to the delay as well as the feedback, without losing
29 precision. Output is only the delay line signal. If you want to mix the delay
30 with the input, do it in your sketch. AudioDelayFeedback uses more processing and memory
31 than a plain AudioDelay, but allows for more dramatic effects with feedback.
32 @tparam INTERP_TYPE a choice of LINEAR (default) or ALLPASS interpolation. LINEAR is better
33 for sweeping delay times, ALLPASS may be better for reverb-like effects.
34 */
35 template <uint16_t NUM_BUFFER_SAMPLES, int8_t INTERP_TYPE = LINEAR>
37 {
38 
39 public:
40  /** Constructor.
41  */
43  {}
44 
45 
46  /** Constructor.
47  @param delaytime_cells delay time expressed in cells.
48  For example, 128 cells delay at MOZZI_AUDIO_RATE 16384 would produce a time delay of 128/16384 = 0.0078125 s = 7.8 ms
49  Put another way, num_cells = delay_seconds * MOZZI_AUDIO_RATE.
50  */
52  {}
53 
54 
55  /** Constructor.
56  @param delaytime_cells delay time expressed in cells.
57  For example, 128 cells delay at MOZZI_AUDIO_RATE 16384 would produce a time delay of 128/16384 = 0.0078125 s = 7.8 ms
58  Put another way, num_cells = delay_seconds * MOZZI_AUDIO_RATE.
59  @param feedback_level is the feedback level from -128 to 127 (representing -1 to 1).
60  */
61  AudioDelayFeedback(uint16_t delaytime_cells, int8_t feedback_level): write_pos(0), _feedback_level(feedback_level), _delaytime_cells(delaytime_cells)
62  {}
63 
64 
65 
66  /** Input a value to the delay and retrieve the signal in the delay line at the position delaytime_cells.
67  @param input the signal input.
68  @note slower than next(int8_t input, uint16_t delaytime_cells)
69  */
70  inline
72  {
73  // chooses a different next() function depending on whether the
74  // the template parameter is LINEAR(default if none provided) or ALLPASS.
75  // See meta.h.
76  return next(input, Int2Type<INTERP_TYPE>());
77  }
78 
79 
80 
81  /** Input a value to the delay, retrieve the signal in the delay line at the position delaytime_cells, and add feedback from the output to the input.
82  @param input the signal input.
83  @param delaytime_cells indicates the delay time in terms of cells in the delay buffer.
84  It doesn't change the stored internal value of _delaytime_cells.
85  @note Timing: 4us
86  */
87  inline
89  {
90  //setPin13High();
91  ++write_pos &= (NUM_BUFFER_SAMPLES - 1);
92  uint16_t read_pos = (write_pos - delaytime_cells) & (NUM_BUFFER_SAMPLES - 1);
93  // < 1us to here
94  int16_t delay_sig = delay_array[read_pos]; // read the delay buffer
95  // with this line, the method takes 18us
96  //int8_t feedback_sig = (int8_t) min(max(((delay_sig * _feedback_level)/128),-128),127); // feedback clipped
97  // this line, the whole method takes 4us... Compiler doesn't optimise pow2 divides. Why?
98  int8_t feedback_sig = (int8_t) min(max(((delay_sig * _feedback_level)>>7),-128),127); // feedback clipped
99  delay_array[write_pos] = (int16_t) input + feedback_sig; // write to buffer
100  //setPin13Low();
101  return delay_sig;
102  }
103 
104 
105 
106  /** Input a value to the delay, retrieve the signal in the delay line at the interpolated fractional position delaytime_cells, and add feedback from the output to the input.
107  @param input the signal input.
108  @param delaytime_cells is a fractional number to set the delay time in terms of cells
109  or partial cells in the delay buffer. It doesn't change the stored internal
110  value of _delaytime_cells.
111  */
112  inline
114  {
115  //setPin13High();
116  ++write_pos &= (NUM_BUFFER_SAMPLES - 1);
117 
118  uint16_t index = Q16n16_to_Q16n0(delaytime_cells);
119  uint16_t fraction = (uint16_t) delaytime_cells; // keeps low word
120 
121  uint16_t read_pos1 = (write_pos - index) & (NUM_BUFFER_SAMPLES - 1);
122  int16_t delay_sig1 = delay_array[read_pos1]; // read the delay buffer
123 
124  uint16_t read_pos2 = (write_pos - (index+1)) & (NUM_BUFFER_SAMPLES - 1);
125  int16_t delay_sig2 = delay_array[read_pos2]; // read the delay buffer
126 
127 
128  int16_t difference = delay_sig2 - delay_sig1;
129  int16_t delay_sig_fraction = (int16_t)((int32_t)((int32_t) fraction * difference) >> 16);
130 
131  int16_t delay_sig = delay_sig1+delay_sig_fraction;
132 
133  //int16_t delay_sig = delay_sig1 + ((int32_t)delay_sig2*fraction)>>16;
134 
135  int8_t feedback_sig = (int8_t) min(max((((int16_t)(delay_sig * _feedback_level))>>7),-128),127); // feedback clipped
136  delay_array[write_pos] = (int16_t) input + feedback_sig; // write to buffer
137  //setPin13Low();
138  return delay_sig;
139  }
140 
141 
142  /** Input a value to the delay but don't change the delay time or retrieve the output signal.
143  @param input the signal input.
144  */
145  inline
146  void write(int8_t input)
147  {
148  ++write_pos &= (NUM_BUFFER_SAMPLES - 1);
149  delay_array[write_pos] = input;
150  }
151 
152 
153  /** Input a value to the delay but don't advance the write position, change the delay time or retrieve the output signal.
154  This can be useful for manually adding feedback to the delay line, "behind" the advancing write head.
155  @param input the signal input.
156  */
157  inline
158  void writeFeedback(int8_t input)
159  {
160  delay_array[write_pos] = input;
161  }
162 
163 
164  /** Input a value to the delay at an offset from the current write position. Don't advance the main
165  write position or change the stored delay time or retrieve the output signal.
166  @param input the signal input.
167  @param offset the number of cells behind the ordinary write position where the input will be written.
168  */
169  inline
170  void write(int8_t input, uint16_t offset)
171  {
172  uint16_t _pos = (write_pos + offset) & (NUM_BUFFER_SAMPLES - 1);
173  delay_array[_pos] = input;
174  }
175 
176 
177  /** Retrieve the signal in the delay line at the interpolated fractional position delaytime_cells.
178  It doesn't change the stored internal value of _delaytime_cells or feedback the output to the input.
179  @param delaytime_cells indicates the delay time in terms of cells in the delay buffer.
180  */
181  inline
183  {
184  return read(delaytime_cells, Int2Type<INTERP_TYPE>());
185  }
186 
187 
188  /** Retrieve the signal in the delay line at the current stored delaytime_cells.
189  It doesn't change the stored internal value of _delaytime_cells or feedback the output to the input.
190  */
191  inline
193  {
194  return read(Int2Type<INTERP_TYPE>());
195  }
196 
197 
198  /** Set delay time expressed in samples.
199  @param delaytime_cells delay time expressed in cells, with each cell played per tick of MOZZI_AUDIO_RATE.
200  For example, 128 cells delay at MOZZI_AUDIO_RATE would produce a time delay of 128/16384 = 0.0078125 s = 7.8 ms
201  Put another way, num_cells = delay_seconds * MOZZI_AUDIO_RATE.
202  */
203  inline
204  void setDelayTimeCells(uint16_t delaytime_cells)
205  {
206  _delaytime_cells = (uint16_t) delaytime_cells;
207  }
208 
209 
210  /** Set delay time expressed in samples, fractional Q16n16 for an interpolating delay.
211  @param delaytime_cells delay time expressed in cells, with each cell played per tick of MOZZI_AUDIO_RATE.
212  For example, 128 cells delay at MOZZI_AUDIO_RATE would produce a time delay of 128/16384 = 0.0078125 s = 7.8 ms
213  Put another way, num_cells = delay_seconds * MOZZI_AUDIO_RATE.
214  */
215  inline
216  void setDelayTimeCells(Q16n16 delaytime_cells)
217  {
218  return setDelayTimeCells(delaytime_cells, Int2Type<INTERP_TYPE>());
219  }
220 
221 
222  /** Set delay time expressed in samples, fractional float for an interpolating delay.
223  @param delaytime_cells delay time expressed in cells, with each cell played per tick of MOZZI_AUDIO_RATE.
224  For example, 128 cells delay at MOZZI_AUDIO_RATE would produce a time delay of 128/16384 = 0.0078125 s = 7.8 ms
225  Put another way, num_cells = delay_seconds * MOZZI_AUDIO_RATE.
226  */
227  inline
228  void setDelayTimeCells(float delaytime_cells)
229  {
230  return setDelayTimeCells(delaytime_cells, Int2Type<INTERP_TYPE>());
231  }
232 
233 
234  /** Set the feedback gain.
235  @param feedback_level is the feedback level from -128 to 127 (representing -1 to 1).
236  */
237  inline
238  void setFeedbackLevel(int8_t feedback_level)
239  {
240  _feedback_level = feedback_level;
241  }
242 
243 
244 
245 private:
247  uint16_t write_pos;
248  int8_t _feedback_level;
249  uint16_t _delaytime_cells;
250  Q15n16 _coeff; // for allpass interpolation
251 
252 
253 
254  /** Input a value to the delay and retrieve the signal in the delay line at the position delaytime_cells.
255  @param in_value the signal input.
256  */
257  inline
258  int16_t next(int8_t in_value, Int2Type<LINEAR>)
259  {
260  ++write_pos &= (NUM_BUFFER_SAMPLES - 1);
261  uint16_t read_pos = (write_pos - _delaytime_cells) & (NUM_BUFFER_SAMPLES - 1);
262 
263  int16_t delay_sig = delay_array[read_pos]; // read the delay buffer
264  int8_t feedback_sig = (int8_t) min(max(((delay_sig * _feedback_level)/128),-128),127); // feedback clipped
265  delay_array[write_pos] = (int16_t) in_value + feedback_sig; // write to buffer
266 
267  return delay_sig;
268  }
269 
270 
271 
272  /** The delaytime_cells has to be set seperately, because it's slowish
273  and in this implementation the allpass interpolation mode doesn't slide
274  nicely from one delay time to another.
275  @param input an audio signal in
276  @return the delayed signal, including feedback
277  @note Timing: 10us
278  */
279  inline
280  int16_t next(int8_t input, Int2Type<ALLPASS>)
281  {
282  /*
283  http://www.scandalis.com/Jarrah/Documents/DelayLine.pdf
284  also https://ccrma.stanford.edu/~jos/Interpolation/Interpolation_4up.pdf
285  for desired fractional delay of d samples,
286  coeff = (1-d)/(1+d)
287  or
288  coeff = ((d-1)>1) + (((d-1)*(d-1))>>2) - (((d-1)*(d-1)*(d-1))>>3)
289  out = coeff * in + last_in - coeff * last_out
290  = coeff * (in-last_out) + last_in
291  */
292  //setPin13High();
293  static int8_t last_in;
294  static int16_t last_out;
295 
296  ++write_pos &= (NUM_BUFFER_SAMPLES - 1);
297 
298  uint16_t read_pos1 = (write_pos - _delaytime_cells) & (NUM_BUFFER_SAMPLES - 1);
299  int16_t delay_sig = delay_array[read_pos1]; // read the delay buffer
300 
301  int16_t interp = (int16_t)(_coeff * ((int16_t)input - last_out)>>16) + last_in; // Q15n16*Q15n0 + Q15n0 = Q15n16 + Q15n0 = Q15n16
302  delay_sig += interp;
303 
304  int8_t feedback_sig = (int8_t) min(max(((delay_sig * _feedback_level)>>7),-128),127); // feedback clipped
305  delay_array[write_pos] = (int16_t) input + feedback_sig; // write to buffer
306 
307  last_in = input;
308  last_out = delay_sig;
309  //setPin13Low();
310  return delay_sig;
311  }
312 
313 
314 
315  // 20-25us
316  inline
317  void setDelayTimeCells(Q16n16 delaytime_cells, Int2Type<ALLPASS>)
318  {
319  /*
320  integer optimisation/approximation from
321  Van Duyne, Jaffe, Scandalis, Stilson 1997
322  http://www.scandalis.com/Jarrah/Documents/DelayLine.pdf
323  //coeff = -((d-1)>1) + (((d-1)*(d-1))>>2) - (((d-1)*(d-1)*(d-1))>>3) , d is fractional part
324  */
325  _delaytime_cells = delaytime_cells>>16; // whole integer part
326  Q15n16 dminus1 = - Q15n16_FIX1 + (uint16_t) delaytime_cells;
327  Q15n16 dminus1squared = (dminus1)*(dminus1)>>16;
328  _coeff = -(dminus1>>1) + (dminus1squared>>2) - (((dminus1squared*dminus1)>>16)>>3);
329  }
330 
331 
332  // 100us
333  inline
334  void setDelayTimeCells(float delaytime_cells, Int2Type<ALLPASS>)
335  {
336  //coeff = (1-d)/(1+d)
337  _delaytime_cells = (uint16_t) delaytime_cells;
338 
339  float fraction = delaytime_cells - _delaytime_cells;
340 
341  // modified from stk DelayA.cpp
342  float alpha_ = 1.0f + fraction; // fractional part
343  if ( alpha_ < 0.5f ) {
344  // (stk): The optimal range for alpha is about 0.5 - 1.5 in order to
345  // achieve the flattest phase delay response.
346 
347  // something's not right about how I use _delaytime_cells and
348  // NUM_BUFFER_SAMPLES etc. in my ringbuffer compared to stk
349  _delaytime_cells += 1;
350  if ( _delaytime_cells >= NUM_BUFFER_SAMPLES ) _delaytime_cells -= NUM_BUFFER_SAMPLES;
351  alpha_ += 1.0f;
352  }
353  // otherwise this would use fraction instead of alpha
354  _coeff = float_to_Q15n16((1.f-alpha_)/(1.f+alpha_));
355  }
356 
357  // Retrieve the signal in the delay line at the position delaytime_cells.
358  // It doesn't change the stored internal value of _delaytime_cells or feedback the output to the input.
359  // param delaytime_cells indicates the delay time in terms of cells in the delay buffer.
360  //
361  // inline
362  // int16_t read(uint16_t delaytime_cells, Int2Type<LINEAR>)
363  // {
364  // uint16_t read_pos = (write_pos - delaytime_cells) & (NUM_BUFFER_SAMPLES - 1);
365  // int16_t delay_sig = delay_array[read_pos]; // read the delay buffer
366  //
367  // return delay_sig;
368  // }
369 
370  /** Retrieve the signal in the delay line at the interpolated fractional position delaytime_cells.
371  It doesn't change the stored internal value of _delaytime_cells or feedback the output to the input.
372  @param delaytime_cells indicates the delay time in terms of cells in the delay buffer.
373  */
374  inline
375  int16_t read(Q16n16 delaytime_cells, Int2Type<LINEAR>)
376  {
377  uint16_t index = (Q16n16)delaytime_cells >> 16;
378  uint16_t fraction = (uint16_t) delaytime_cells; // keeps low word
379 
380  uint16_t read_pos1 = (write_pos - index) & (NUM_BUFFER_SAMPLES - 1);
381  int16_t delay_sig1 = delay_array[read_pos1]; // read the delay buffer
382 
383  uint16_t read_pos2 = (write_pos - (index+1)) & (NUM_BUFFER_SAMPLES - 1);
384  int16_t delay_sig2 = delay_array[read_pos2]; // read the delay buffer
385 
386  /*
387  int16_t difference = delay_sig2 - delay_sig1;
388  int16_t delay_sig_fraction = ((int32_t) fraction * difference) >> 16;
389 
390  int16_t delay_sig = delay_sig1+delay_sig_fraction;
391  */
392  int16_t delay_sig = delay_sig1 + ((int32_t)delay_sig2*fraction)>>16;
393 
394  return delay_sig;
395  }
396 
397 
398 };
399 
400 /**
401 @example 09.Delays/AudioDelayFeedback/AudioDelayFeedback.ino
402 This is an example of how to use the AudioDelayFeedback class.
403 */
404 
405 #endif // #ifndef AUDIODELAY_FEEDBACK_H_