arduino-audio-tools
AudioEffect.h
1 #pragma once
2 #include "AudioParameters.h"
3 #include "PitchShift.h"
4 #include "AudioLogger.h"
5 #include "AudioTools/CoreAudio/AudioTypes.h"
6 #include "AudioTools/CoreAudio/AudioOutput.h"
7 #include <stdint.h>
8 
9 namespace audio_tools {
10 
11 // we use int16_t for our effects
12 typedef int16_t effect_t;
13 
21 class AudioEffect {
22 public:
23  AudioEffect() = default;
24  virtual ~AudioEffect() = default;
25 
27  virtual effect_t process(effect_t in) = 0;
28 
30  virtual void setActive(bool value) { active_flag = value; }
31 
33  virtual bool active() { return active_flag; }
34 
35  virtual AudioEffect *clone() = 0;
36 
38  int id() { return id_value; }
39 
41  void setId(int id) { this->id_value = id; }
42 
43 protected:
44  bool active_flag = true;
45  int id_value = -1;
46 
47  void copyParent(AudioEffect *copy) {
48  id_value = copy->id_value;
49  active_flag = copy->active_flag;
50  }
51 
53  int16_t clip(int32_t in, int16_t clipLimit = 32767,
54  int16_t resultLimit = 32767) {
55  int32_t result = in;
56  if (result > clipLimit) {
57  result = resultLimit;
58  }
59  if (result < -clipLimit) {
60  result = -resultLimit;
61  }
62  return result;
63  }
64 };
65 
74 class Boost : public AudioEffect, public VolumeSupport {
75 public:
78  Boost(float volume = 1.0) { setVolume(volume); }
79 
80  Boost(const Boost &copy) = default;
81 
82  effect_t process(effect_t input) {
83  if (!active())
84  return input;
85  int32_t result = volume() * input;
86  // clip to int16_t
87  return clip(result);
88  }
89 
90  Boost *clone() { return new Boost(*this); }
91 
92 };
93 
101 class Distortion : public AudioEffect {
102 public:
104  Distortion(int16_t clipThreashold = 4990, int16_t maxInput = 6500) {
105  p_clip_threashold = clipThreashold;
106  max_input = maxInput;
107  }
108 
109  Distortion(const Distortion &copy) = default;
110 
111  void setClipThreashold(int16_t th) { p_clip_threashold = th; }
112 
113  int16_t clipThreashold() { return p_clip_threashold; }
114 
115  void setMaxInput(int16_t maxInput) { max_input = maxInput; }
116 
117  int16_t maxInput() { return max_input; }
118 
119  effect_t process(effect_t input) {
120  if (!active())
121  return input;
122  // the input signal is 16bits (values from -32768 to +32768
123  // the value of input is clipped to the distortion_threshold value
124  return clip(input, p_clip_threashold, max_input);
125  }
126 
127  Distortion *clone() { return new Distortion(*this); }
128 
129 protected:
130  int16_t p_clip_threashold;
131  int16_t max_input;
132 };
133 
141 class Fuzz : public AudioEffect {
142 public:
144  Fuzz(float fuzzEffectValue = 6.5, uint16_t maxOut = 300) {
145  p_effect_value = fuzzEffectValue;
146  max_out = maxOut;
147  }
148 
149  Fuzz(const Fuzz &copy) = default;
150 
151  void setFuzzEffectValue(float v) { p_effect_value = v; }
152 
153  float fuzzEffectValue() { return p_effect_value; }
154 
155  void setMaxOut(uint16_t v) { max_out = v; }
156 
157  uint16_t maxOut() { return max_out; }
158 
159  effect_t process(effect_t input) {
160  if (!active())
161  return input;
162  float v = p_effect_value;
163  int32_t result = clip(v * input);
164  return map(result * v, -32768, +32767, -max_out, max_out);
165  }
166 
167  Fuzz *clone() { return new Fuzz(*this); }
168 
169 protected:
170  float p_effect_value;
171  uint16_t max_out;
172 };
173 
181 class Tremolo : public AudioEffect {
182 public:
185  Tremolo(int16_t duration_ms = 2000, uint8_t depthPercent = 50,
186  uint32_t sampleRate = 44100) {
187  this->duration_ms = duration_ms;
188  this->sampleRate = sampleRate;
189  this->p_percent = depthPercent;
190  int32_t rate_count = sampleRate * duration_ms / 1000;
191  rate_count_half = rate_count / 2;
192  }
193 
194  Tremolo(const Tremolo &copy) = default;
195 
196  void setDuration(int16_t ms) {
197  this->duration_ms = ms;
198  int32_t rate_count = sampleRate * ms / 1000;
199  rate_count_half = rate_count / 2;
200  }
201 
202  int16_t duration() { return duration_ms; }
203 
204  void setDepth(uint8_t percent) { p_percent = percent; }
205 
206  uint8_t depth() { return p_percent; }
207 
208  effect_t process(effect_t input) {
209  if (!active())
210  return input;
211 
212  // limit value to max 100% and calculate factors
213  float tremolo_depth = p_percent > 100 ? 1.0 : 0.01 * p_percent;
214  float signal_depth = (100.0 - p_percent) / 100.0;
215 
216  float tremolo_factor = tremolo_depth / rate_count_half;
217  int32_t out = (signal_depth * input) + (tremolo_factor * count * input);
218 
219  // saw tooth shaped counter
220  count += inc;
221  if (count >= rate_count_half) {
222  inc = -1;
223  } else if (count <= 0) {
224  inc = +1;
225  }
226 
227  return clip(out);
228  }
229 
230  Tremolo *clone() { return new Tremolo(*this); }
231 
232 protected:
233  int16_t duration_ms;
234  uint32_t sampleRate;
235  int32_t count = 0;
236  int16_t inc = 1;
237  int32_t rate_count_half; // number of samples for on raise and fall
238  uint8_t p_percent;
239 };
240 
249 class Delay : public AudioEffect {
250 public:
252  Delay(uint16_t duration_ms = 1000, float depth = 0.5,
253  float feedbackAmount = 1.0, uint32_t sampleRate = 44100) {
254  setSampleRate(sampleRate);
255  setFeedback(feedbackAmount);
256  setDepth(depth);
257  setDuration(duration_ms);
258  }
259 
260  Delay(const Delay &copy) {
261  setSampleRate(copy.sampleRate);
262  setFeedback(copy.feedback);
263  setDepth(copy.depth);
264  setDuration(copy.duration);
265  };
266 
267  void setDuration(int16_t dur) {
268  duration = dur;
269  updateBufferSize();
270  }
271 
272  int16_t getDuration() { return duration; }
273 
274  void setDepth(float value) {
275  depth = value;
276  if (depth > 1.0f)
277  depth = 1.0f;
278  if (depth < 0.0f)
279  depth = 0.0f;
280  }
281 
282  float getDepth() { return depth; }
283 
284  void setFeedback(float feed) {
285  feedback = feed;
286  if (feedback > 1.0f)
287  feedback = 1.0f;
288  if (feedback < 0.0f)
289  feedback = 0.0f;
290  }
291 
292  float getFeedback() { return feedback; }
293 
294  void setSampleRate(int32_t sample) {
295  sampleRate = sample;
296  updateBufferSize();
297  }
298 
299  float getSampleRate() { return sampleRate; }
300 
301  effect_t process(effect_t input) {
302  if (!active())
303  return input;
304 
305  // Read last audio sample in each delay line
306  int32_t delayed_value = buffer[delay_line_index];
307 
308  // Mix the above with current audio and write the results back to output
309  int32_t out = ((1.0f - depth) * input) + (depth * delayed_value);
310 
311  // Update each delay line
312  buffer[delay_line_index] = clip(feedback * (delayed_value + input));
313 
314  // Finally, update the delay line index
315  if (delay_line_index++ >= delay_len_samples) {
316  delay_line_index = 0;
317  }
318  return clip(out);
319  }
320 
321  Delay *clone() { return new Delay(*this); }
322 
323 protected:
324  Vector<effect_t> buffer{0};
325  float feedback = 0.0f, duration = 0.0f, sampleRate = 0.0f, depth = 0.0f;
326  size_t delay_len_samples = 0;
327  size_t delay_line_index = 0;
328 
329  void updateBufferSize() {
330  if (sampleRate > 0 && duration > 0) {
331  size_t newSampleCount = sampleRate * duration / 1000;
332  if (newSampleCount != delay_len_samples) {
333  delay_len_samples = newSampleCount;
334  buffer.resize(delay_len_samples);
335  memset(buffer.data(),0,delay_len_samples*sizeof(effect_t));
336  LOGD("sample_count: %u", (unsigned)delay_len_samples);
337  }
338  }
339  }
340 };
341 
357 class ADSRGain : public AudioEffect {
358 public:
359  ADSRGain(float attack = 0.001, float decay = 0.001, float sustainLevel = 0.5,
360  float release = 0.005, float boostFactor = 1.0) {
361  this->factor = boostFactor;
362  adsr = new ADSR(attack, decay, sustainLevel, release);
363  }
364 
365  ADSRGain(const ADSRGain &ref) {
366  adsr = new ADSR(*(ref.adsr));
367  factor = ref.factor;
368  copyParent((AudioEffect *)&ref);
369  };
370 
371  virtual ~ADSRGain() { delete adsr; }
372 
373  void setAttackRate(float a) { adsr->setAttackRate(a); }
374 
375  float attackRate() { return adsr->attackRate(); }
376 
377  void setDecayRate(float d) { adsr->setDecayRate(d); }
378 
379  float decayRate() { return adsr->decayRate(); }
380 
381  void setSustainLevel(float s) { adsr->setSustainLevel(s); }
382 
383  float sustainLevel() { return adsr->sustainLevel(); }
384 
385  void setReleaseRate(float r) { adsr->setReleaseRate(r); }
386 
387  float releaseRate() { return adsr->releaseRate(); }
388 
389  void keyOn(float tgt = 0) { adsr->keyOn(tgt); }
390 
391  void keyOff() { adsr->keyOff(); }
392 
393  effect_t process(effect_t input) {
394  if (!active())
395  return input;
396  effect_t result = factor * adsr->tick() * input;
397  return result;
398  }
399 
400  bool isActive() { return adsr->isActive(); }
401 
402  ADSRGain *clone() { return new ADSRGain(*this); }
403 
404 protected:
405  ADSR *adsr;
406  float factor;
407 };
408 
415 class PitchShift : public AudioEffect {
416 public:
419  PitchShift(float shift_value = 1.0, int buffer_size = 1000) {
420  effect_value = shift_value;
421  size = buffer_size;
422  buffer.resize(buffer_size);
423  buffer.setIncrement(shift_value);
424  }
425 
426  PitchShift(const PitchShift &ref) {
427  size = ref.size;
428  effect_value = ref.effect_value;
429  buffer.resize(size);
430  buffer.setIncrement(effect_value);
431  };
432 
433  float value() { return effect_value; }
434 
435  void setValue(float value) {
436  effect_value = value;
437  buffer.setIncrement(value);
438  }
439 
440  effect_t process(effect_t input) {
441  if (!active())
442  return input;
443  buffer.write(input);
444  return buffer.read();
445  }
446 
447  PitchShift *clone() { return new PitchShift(*this); }
448 
449 protected:
450  VariableSpeedRingBuffer<int16_t> buffer;
451  float effect_value;
452  int size;
453 };
454 
455 
463 class Compressor : public AudioEffect {
464 public:
466  Compressor(const Compressor &copy) = default;
467 
469  Compressor(uint32_t sampleRate = 44100, uint16_t attackMs=30, uint16_t releaseMs=20, uint16_t holdMs=10, uint8_t thresholdPercent=10, float compressionRatio=0.5){
470  //assuming 1 sample = 1/96kHz = ~10us
471  //Attack -> 30 ms -> 3000
472  //Release -> 20 ms -> 2000
473  //Hold -> 10ms -> 1000
474  sample_rate = sample_rate * attackMs / 1000;
475  attack_count = sample_rate * attackMs / 1000;
476  release_count = sample_rate * releaseMs / 1000;
477  hold_count = sample_rate * holdMs / 1000;
478 
479  //threshold -20dB below limit -> 0.1 * 2^31
480  threshold = 0.01f * thresholdPercent * NumberConverter::maxValueT<effect_t>();
481  //compression ratio: 6:1 -> -6dB = 0.5
482  gainreduce = compressionRatio;
483  //initial gain = 1.0 -> no compression
484  gain = 1.0f;
485  recalculate();
486  }
487 
489  void setAttack(uint16_t attackMs){
490  attack_count = sample_rate * attackMs / 1000;
491  recalculate();
492  }
493 
495  void setRelease(uint16_t releaseMs){
496  release_count = sample_rate * releaseMs / 1000;
497  recalculate();
498  }
499 
501  void setHold(uint16_t holdMs){
502  hold_count = sample_rate * holdMs / 1000;
503  recalculate();
504  }
505 
507  void setThresholdPercent(uint8_t thresholdPercent){
508  threshold = 0.01f * thresholdPercent * NumberConverter::maxValueT<effect_t>();
509  }
510 
512  void setCompressionRatio(float compressionRatio){
513  if (compressionRatio < 1.0f){
514  gainreduce = compressionRatio;
515  }
516  recalculate();
517  }
518 
520  effect_t process(effect_t input) {
521  if (!active())
522  return input;
523  return compress(input);
524  }
525 
526  Compressor *clone() { return new Compressor(*this); }
527 
528 protected:
529  enum CompStates {S_NoOperation, S_Attack, S_GainReduction, S_Release };
530  enum CompStates state = S_NoOperation;
531 
532  int32_t attack_count, release_count, hold_count, timeout;
533  float gainreduce, gain_step_attack, gain_step_release, gain, threshold;
534  uint32_t sample_rate;
535 
536  void recalculate() {
537  gain_step_attack = (1.0f - gainreduce) / attack_count;
538  gain_step_release = (1.0f - gainreduce) / release_count;
539  }
540 
541  float compress(float inSampleF){
542  if (fabs(inSampleF) > threshold) {
543  if (gain >= gainreduce) {
544  if (state==S_NoOperation) {
545  state=S_Attack;
546  timeout = attack_count;
547  }
548  else if (state==S_Release) {
549  state=S_Attack;
550  timeout = attack_count;
551  }
552  }
553  if (state==S_GainReduction) timeout = hold_count;
554 
555  }
556 
557  if (fabs(inSampleF) < threshold && gain <= 1.0f) {
558  if ( timeout==0 && state==S_GainReduction) {
559  state=S_Release;
560  timeout = release_count;
561  }
562  }
563 
564  switch (state) {
565  case S_Attack:
566  if ( timeout>0 && gain > gainreduce) {
567  gain -= gain_step_attack;
568  timeout--;
569  }
570  else {
571  state=S_GainReduction;
572  timeout = hold_count;
573  }
574  break;
575 
576 
577  case S_GainReduction:
578  if ( timeout>0) timeout--;
579  else {
580  state=S_Release;
581  timeout = release_count;
582  }
583  break;
584 
585 
586  case S_Release:
587  if ( timeout>0 && gain<1.0f) {
588  timeout--;
589  gain += gain_step_release;
590  }
591  else {
592  state=S_NoOperation;
593  }
594  break;
595 
596  case S_NoOperation:
597  if (gain < 1.0f) gain = 1.0f;
598  break;
599 
600  default:
601  break;
602 
603  }
604 
605  float outSampleF = gain * inSampleF;
606  return outSampleF;
607  }
608 
609 };
610 
611 
612 } // namespace audio_tools
ADSR Envelope: Attack, Decay, Sustain and Release. Attack is the time taken for initial run-up oeffec...
Definition: AudioEffect.h:357
effect_t process(effect_t input)
calculates the effect output from the input
Definition: AudioEffect.h:393
Generates ADSR values between 0.0 and 1.0.
Definition: AudioParameters.h:51
Abstract Base class for Sound Effects.
Definition: AudioEffect.h:21
virtual bool active()
determines if the effect is active
Definition: AudioEffect.h:33
virtual void setActive(bool value)
sets the effect active/inactive
Definition: AudioEffect.h:30
void setId(int id)
Allows to identify an effect.
Definition: AudioEffect.h:41
int id()
Allows to identify an effect.
Definition: AudioEffect.h:38
virtual effect_t process(effect_t in)=0
calculates the effect output from the input
int16_t clip(int32_t in, int16_t clipLimit=32767, int16_t resultLimit=32767)
generic clipping method
Definition: AudioEffect.h:53
Boost AudioEffect.
Definition: AudioEffect.h:74
effect_t process(effect_t input)
calculates the effect output from the input
Definition: AudioEffect.h:82
Boost(float volume=1.0)
Definition: AudioEffect.h:78
Compressor inspired by https://github.com/YetAnotherElectronicsChannel/STM32_DSP_COMPRESSOR/blob/mast...
Definition: AudioEffect.h:463
void setAttack(uint16_t attackMs)
Defines the attack duration in ms.
Definition: AudioEffect.h:489
effect_t process(effect_t input)
Processes the sample.
Definition: AudioEffect.h:520
void setRelease(uint16_t releaseMs)
Defines the release duration in ms.
Definition: AudioEffect.h:495
Compressor(uint32_t sampleRate=44100, uint16_t attackMs=30, uint16_t releaseMs=20, uint16_t holdMs=10, uint8_t thresholdPercent=10, float compressionRatio=0.5)
Default Constructor.
Definition: AudioEffect.h:469
void setHold(uint16_t holdMs)
Defines the hold duration in ms.
Definition: AudioEffect.h:501
Compressor(const Compressor &copy)=default
Copy Constructor.
void setCompressionRatio(float compressionRatio)
Defines the compression ratio from 0 to 1.
Definition: AudioEffect.h:512
void setThresholdPercent(uint8_t thresholdPercent)
Defines the threshod in %.
Definition: AudioEffect.h:507
Delay/Echo AudioEffect. See https://wiki.analog.com/resources/tools-software/sharc-audio-module/barem...
Definition: AudioEffect.h:249
effect_t process(effect_t input)
calculates the effect output from the input
Definition: AudioEffect.h:301
Delay(uint16_t duration_ms=1000, float depth=0.5, float feedbackAmount=1.0, uint32_t sampleRate=44100)
e.g. depth=0.5, ms=1000, sampleRate=44100
Definition: AudioEffect.h:252
Distortion AudioEffect.
Definition: AudioEffect.h:101
Distortion(int16_t clipThreashold=4990, int16_t maxInput=6500)
Distortion Constructor: e.g. use clipThreashold 4990 and maxInput=6500.
Definition: AudioEffect.h:104
effect_t process(effect_t input)
calculates the effect output from the input
Definition: AudioEffect.h:119
Fuzz AudioEffect.
Definition: AudioEffect.h:141
effect_t process(effect_t input)
calculates the effect output from the input
Definition: AudioEffect.h:159
Fuzz(float fuzzEffectValue=6.5, uint16_t maxOut=300)
Fuzz Constructor: use e.g. effectValue=6.5; maxOut = 300.
Definition: AudioEffect.h:144
Shifts the pitch by the indicated step size: e.g. 2 doubles the pitch.
Definition: AudioEffect.h:415
effect_t process(effect_t input)
calculates the effect output from the input
Definition: AudioEffect.h:440
PitchShift(float shift_value=1.0, int buffer_size=1000)
Definition: AudioEffect.h:419
Tremolo AudioEffect.
Definition: AudioEffect.h:181
effect_t process(effect_t input)
calculates the effect output from the input
Definition: AudioEffect.h:208
Tremolo(int16_t duration_ms=2000, uint8_t depthPercent=50, uint32_t sampleRate=44100)
Definition: AudioEffect.h:185
T read()
reads a single value
Definition: PitchShift.h:240
bool write(T sample)
write add an entry to the buffer
Definition: PitchShift.h:257
Supports the setting and getting of the volume.
Definition: AudioTypes.h:207
virtual float volume()
provides the actual volume in the range of 0.0f to 1.0f
Definition: AudioTypes.h:210
virtual bool setVolume(float volume)
define the actual volume in the range of 0.0f to 1.0f
Definition: AudioTypes.h:212
Generic Implementation of sound input and output for desktop environments using portaudio.
Definition: AudioConfig.h:868
long map(long x, long in_min, long in_max, long out_min, long out_max)
Maps input to output values.
Definition: NoArduino.h:162