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