arduino-audio-tools
All Classes Namespaces Files Functions Variables Typedefs Enumerations Friends Macros Modules Pages
AudioEffect.h
1#pragma once
2#include <math.h>
3#include <stdint.h>
4
5#include "AudioLogger.h"
6#include "AudioParameters.h"
7#include "AudioTools/CoreAudio/AudioOutput.h"
8#include "AudioTools/CoreAudio/AudioTypes.h"
9#include "PitchShift.h"
10
11namespace audio_tools {
12
13// we use int16_t for our effects
14typedef int16_t effect_t;
15
24 public:
25 AudioEffect() = default;
26 virtual ~AudioEffect() = default;
27
29 virtual effect_t process(effect_t in) = 0;
30
32 virtual void setActive(bool value) { active_flag = value; }
33
35 virtual bool active() { return active_flag; }
36
37 virtual AudioEffect* clone() = 0;
38
40 int id() { return id_value; }
41
43 void setId(int id) { this->id_value = id; }
44
45 protected:
46 bool active_flag = true;
47 int id_value = -1;
48
49 void copyParent(AudioEffect* copy) {
50 id_value = copy->id_value;
51 active_flag = copy->active_flag;
52 }
53
55 int16_t clip(int32_t in, int16_t clipLimit = 32767,
56 int16_t resultLimit = 32767) {
57 int32_t result = in;
58 if (result > clipLimit) {
59 result = resultLimit;
60 }
61 if (result < -clipLimit) {
62 result = -resultLimit;
63 }
64 return result;
65 }
66};
67
76class Boost : public AudioEffect, public VolumeSupport {
77 public:
80 Boost(float volume = 1.0) { setVolume(volume); }
81
82 Boost(const Boost& copy) = default;
83
84 effect_t process(effect_t input) {
85 if (!active()) return input;
86 int32_t result = volume() * input;
87 // clip to int16_t
88 return clip(result);
89 }
90
91 Boost* clone() { return new Boost(*this); }
92};
93
101class 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()) return input;
121 // the input signal is 16bits (values from -32768 to +32768
122 // the value of input is clipped to the distortion_threshold value
123 return clip(input, p_clip_threashold, max_input);
124 }
125
126 Distortion* clone() { return new Distortion(*this); }
127
128 protected:
129 int16_t p_clip_threashold;
130 int16_t max_input;
131};
132
140class Fuzz : public AudioEffect {
141 public:
143 Fuzz(float fuzzEffectValue = 6.5, uint16_t maxOut = 300) {
144 p_effect_value = fuzzEffectValue;
145 max_out = maxOut;
146 }
147
148 Fuzz(const Fuzz& copy) = default;
149
150 void setFuzzEffectValue(float v) { p_effect_value = v; }
151
152 float fuzzEffectValue() { return p_effect_value; }
153
154 void setMaxOut(uint16_t v) { max_out = v; }
155
156 uint16_t maxOut() { return max_out; }
157
158 effect_t process(effect_t input) {
159 if (!active()) return input;
160 float v = p_effect_value;
161 int32_t result = clip(v * input);
162 return map(result * v, -32768, +32767, -max_out, max_out);
163 }
164
165 Fuzz* clone() { return new Fuzz(*this); }
166
167 protected:
168 float p_effect_value;
169 uint16_t max_out;
170};
171
179class Tremolo : public AudioEffect {
180 public:
183 Tremolo(int16_t duration_ms = 2000, uint8_t depthPercent = 50,
184 uint32_t sampleRate = 44100) {
185 this->duration_ms = duration_ms;
186 this->sampleRate = sampleRate;
187 this->p_percent = depthPercent;
188 int32_t rate_count = sampleRate * duration_ms / 1000;
189 rate_count_half = rate_count / 2;
190 }
191
192 Tremolo(const Tremolo& copy) = default;
193
194 void setDuration(int16_t ms) {
195 this->duration_ms = ms;
196 int32_t rate_count = sampleRate * ms / 1000;
197 rate_count_half = rate_count / 2;
198 }
199
200 int16_t duration() { return duration_ms; }
201
202 void setDepth(uint8_t percent) { p_percent = percent; }
203
204 uint8_t depth() { return p_percent; }
205
206 effect_t process(effect_t input) {
207 if (!active()) return input;
208
209 // limit value to max 100% and calculate factors
210 float tremolo_depth = p_percent > 100 ? 1.0 : 0.01 * p_percent;
211 float signal_depth = (100.0 - p_percent) / 100.0;
212
213 float tremolo_factor = tremolo_depth / rate_count_half;
214 int32_t out = (signal_depth * input) + (tremolo_factor * count * input);
215
216 // saw tooth shaped counter
217 count += inc;
218 if (count >= rate_count_half) {
219 inc = -1;
220 } else if (count <= 0) {
221 inc = +1;
222 }
223
224 return clip(out);
225 }
226
227 Tremolo* clone() { return new Tremolo(*this); }
228
229 protected:
230 int16_t duration_ms;
231 uint32_t sampleRate;
232 int32_t count = 0;
233 int16_t inc = 1;
234 int32_t rate_count_half; // number of samples for on raise and fall
235 uint8_t p_percent;
236};
237
246class Delay : public AudioEffect {
247 public:
249 Delay(uint16_t duration_ms = 1000, float depth = 0.5,
250 float feedbackAmount = 1.0, uint32_t sampleRate = 44100) {
251 setSampleRate(sampleRate);
252 setFeedback(feedbackAmount);
253 setDepth(depth);
254 setDuration(duration_ms);
255 }
256
257 Delay(const Delay& copy) {
258 setSampleRate(copy.sampleRate);
259 setFeedback(copy.feedback);
260 setDepth(copy.depth);
261 setDuration(copy.duration);
262 };
263
264 void setDuration(int16_t dur) {
265 duration = dur;
266 updateBufferSize();
267 }
268
269 int16_t getDuration() { return duration; }
270
271 void setDepth(float value) {
272 depth = value;
273 if (depth > 1.0f) depth = 1.0f;
274 if (depth < 0.0f) depth = 0.0f;
275 }
276
277 float getDepth() { return depth; }
278
279 void setFeedback(float feed) {
280 feedback = feed;
281 if (feedback > 1.0f) feedback = 1.0f;
282 if (feedback < 0.0f) feedback = 0.0f;
283 }
284
285 float getFeedback() { return feedback; }
286
287 void setSampleRate(int32_t sample) {
288 sampleRate = sample;
289 updateBufferSize();
290 }
291
292 float getSampleRate() { return sampleRate; }
293
294 effect_t process(effect_t input) {
295 if (!active()) return input;
296
297 // Read last audio sample in each delay line
298 int32_t delayed_value = buffer[delay_line_index];
299
300 // Mix the above with current audio and write the results back to output
301 int32_t out = ((1.0f - depth) * input) + (depth * delayed_value);
302
303 // Update each delay line
304 // write = input + feedback * delayed_value (typical delay feedback)
305 float write_val = (float)input + feedback * (float)delayed_value;
306 // round to nearest integer to avoid truncation bias
307 int32_t write_int = (int32_t)roundf(write_val);
308 // clip to allowed range and store
309 buffer[delay_line_index] = clip(write_int);
310
311 // Finally, update the delay line index
312 if (delay_line_index++ >= delay_len_samples) {
313 delay_line_index = 0;
314 }
315 return clip(out);
316 }
317
318 Delay* clone() { return new Delay(*this); }
319
320 protected:
321 Vector<effect_t> buffer{0};
322 float feedback = 0.0f, duration = 0.0f, sampleRate = 0.0f, depth = 0.0f;
323 size_t delay_len_samples = 0;
324 size_t delay_line_index = 0;
325
326 void updateBufferSize() {
327 if (sampleRate > 0 && duration > 0) {
328 size_t newSampleCount = sampleRate * duration / 1000;
329 if (newSampleCount != delay_len_samples) {
330 delay_len_samples = newSampleCount;
331 buffer.resize(delay_len_samples);
332 memset(buffer.data(), 0, delay_len_samples * sizeof(effect_t));
333 LOGD("sample_count: %u", (unsigned)delay_len_samples);
334 }
335 }
336 }
337};
338
354class ADSRGain : public AudioEffect {
355 public:
356 ADSRGain(float attack = 0.001, float decay = 0.001, float sustainLevel = 0.5,
357 float release = 0.005, float boostFactor = 1.0) {
358 this->factor = boostFactor;
359 adsr = new ADSR(attack, decay, sustainLevel, release);
360 }
361
362 ADSRGain(const ADSRGain& ref) {
363 adsr = new ADSR(*(ref.adsr));
364 factor = ref.factor;
365 copyParent((AudioEffect*)&ref);
366 };
367
368 virtual ~ADSRGain() { delete adsr; }
369
370 void setAttackRate(float a) { adsr->setAttackRate(a); }
371
372 float attackRate() { return adsr->attackRate(); }
373
374 void setDecayRate(float d) { adsr->setDecayRate(d); }
375
376 float decayRate() { return adsr->decayRate(); }
377
378 void setSustainLevel(float s) { adsr->setSustainLevel(s); }
379
380 float sustainLevel() { return adsr->sustainLevel(); }
381
382 void setReleaseRate(float r) { adsr->setReleaseRate(r); }
383
384 float releaseRate() { return adsr->releaseRate(); }
385
386 void keyOn(float tgt = 0) { adsr->keyOn(tgt); }
387
388 void keyOff() { adsr->keyOff(); }
389
390 effect_t process(effect_t input) {
391 if (!active()) return input;
392 effect_t result = factor * adsr->tick() * input;
393 return result;
394 }
395
396 bool isActive() { return adsr->isActive(); }
397
398 ADSRGain* clone() { return new ADSRGain(*this); }
399
400 protected:
401 ADSR* adsr;
402 float factor;
403};
404
411class PitchShift : public AudioEffect {
412 public:
415 PitchShift(float shift_value = 1.0, int buffer_size = 1000) {
416 effect_value = shift_value;
417 size = buffer_size;
418 buffer.resize(buffer_size);
419 buffer.setIncrement(shift_value);
420 }
421
422 PitchShift(const PitchShift& ref) {
423 size = ref.size;
424 effect_value = ref.effect_value;
425 buffer.resize(size);
426 buffer.setIncrement(effect_value);
427 };
428
429 float value() { return effect_value; }
430
431 void setValue(float value) {
432 effect_value = value;
433 buffer.setIncrement(value);
434 }
435
436 effect_t process(effect_t input) {
437 if (!active()) return input;
438 buffer.write(input);
439 effect_t result;
440 buffer.read(result);
441 return result;
442 }
443
444 PitchShift* clone() { return new PitchShift(*this); }
445
446 protected:
447 VariableSpeedRingBuffer<int16_t> buffer;
448 float effect_value;
449 int size;
450};
451
460class Compressor : public AudioEffect {
461 public:
463 Compressor(const Compressor& copy) = default;
464
466 Compressor(uint32_t sampleRate = 44100, uint16_t attackMs = 30,
467 uint16_t releaseMs = 20, uint16_t holdMs = 10,
468 uint8_t thresholdPercent = 10, float compressionRatio = 0.5) {
469 // assuming 1 sample = 1/96kHz = ~10us
470 // Attack -> 30 ms -> 3000
471 // Release -> 20 ms -> 2000
472 // Hold -> 10ms -> 1000
473 sample_rate = sampleRate;
474 attack_count = sample_rate * attackMs / 1000;
475 release_count = sample_rate * releaseMs / 1000;
476 hold_count = sample_rate * holdMs / 1000;
477
478 // threshold -20dB below limit -> 0.1 * 2^31
479 threshold =
480 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 =
509 0.01f * thresholdPercent * NumberConverter::maxValueT<effect_t>();
510 }
511
513 void setCompressionRatio(float compressionRatio) {
514 if (compressionRatio < 1.0f) {
515 gainreduce = compressionRatio;
516 }
517 recalculate();
518 }
519
521 effect_t process(effect_t input) {
522 if (!active()) 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 } else if (state == S_Release) {
548 state = S_Attack;
549 timeout = attack_count;
550 }
551 }
552 if (state == S_GainReduction) timeout = hold_count;
553 }
554
555 if (fabs(inSampleF) < threshold && gain <= 1.0f) {
556 if (timeout == 0 && state == S_GainReduction) {
557 state = S_Release;
558 timeout = release_count;
559 }
560 }
561
562 switch (state) {
563 case S_Attack:
564 if (timeout > 0 && gain > gainreduce) {
565 gain -= gain_step_attack;
566 timeout--;
567 } else {
568 state = S_GainReduction;
569 timeout = hold_count;
570 }
571 break;
572
573 case S_GainReduction:
574 if (timeout > 0)
575 timeout--;
576 else {
577 state = S_Release;
578 timeout = release_count;
579 }
580 break;
581
582 case S_Release:
583 if (timeout > 0 && gain < 1.0f) {
584 timeout--;
585 gain += gain_step_release;
586 } else {
587 state = S_NoOperation;
588 }
589 break;
590
591 case S_NoOperation:
592 if (gain < 1.0f) gain = 1.0f;
593 break;
594
595 default:
596 break;
597 }
598
599 float outSampleF = gain * inSampleF;
600 return outSampleF;
601 }
602};
603
604} // namespace audio_tools
Real-time pitch shifting audio effect implementation.
ADSR Envelope: Attack, Decay, Sustain and Release. Attack is the time taken for initial run-up oeffec...
Definition AudioEffect.h:354
effect_t process(effect_t input)
calculates the effect output from the input
Definition AudioEffect.h:390
Generates ADSR values between 0.0 and 1.0.
Definition AudioParameters.h:51
Abstract Base class for Sound Effects.
Definition AudioEffect.h:23
virtual bool active()
determines if the effect is active
Definition AudioEffect.h:35
virtual void setActive(bool value)
sets the effect active/inactive
Definition AudioEffect.h:32
void setId(int id)
Allows to identify an effect.
Definition AudioEffect.h:43
int id()
Allows to identify an effect.
Definition AudioEffect.h:40
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:55
Boost AudioEffect.
Definition AudioEffect.h:76
effect_t process(effect_t input)
calculates the effect output from the input
Definition AudioEffect.h:84
Boost(float volume=1.0)
Definition AudioEffect.h:80
Compressor inspired by https://github.com/YetAnotherElectronicsChannel/STM32_DSP_COMPRESSOR/blob/mast...
Definition AudioEffect.h:460
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:521
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:466
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:513
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:246
effect_t process(effect_t input)
calculates the effect output from the input
Definition AudioEffect.h:294
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:249
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:140
effect_t process(effect_t input)
calculates the effect output from the input
Definition AudioEffect.h:158
Fuzz(float fuzzEffectValue=6.5, uint16_t maxOut=300)
Fuzz Constructor: use e.g. effectValue=6.5; maxOut = 300.
Definition AudioEffect.h:143
Shifts the pitch by the indicated step size: e.g. 2 doubles the pitch.
Definition AudioEffect.h:411
effect_t process(effect_t input)
calculates the effect output from the input
Definition AudioEffect.h:436
PitchShift(float shift_value=1.0, int buffer_size=1000)
Definition AudioEffect.h:415
Tremolo AudioEffect.
Definition AudioEffect.h:179
effect_t process(effect_t input)
calculates the effect output from the input
Definition AudioEffect.h:206
Tremolo(int16_t duration_ms=2000, uint8_t depthPercent=50, uint32_t sampleRate=44100)
Definition AudioEffect.h:183
void setIncrement(float increment)
Set the reading speed increment for pitch shifting.
Definition PitchShift.h:391
bool resize(int size)
Resize buffer and set initial read position to prevent immediate overrun.
Definition PitchShift.h:398
bool write(T sample)
write add an entry to the buffer
Definition PitchShift.h:425
bool read(T &result)
reads a single value
Definition PitchShift.h:405
Supports the setting and getting of the volume.
Definition AudioTypes.h:191
virtual float volume()
provides the actual volume in the range of 0.0f to 1.0f
Definition AudioTypes.h:194
virtual bool setVolume(float volume)
define the actual volume in the range of 0.0f to 1.0f
Definition AudioTypes.h:196
Generic Implementation of sound input and output for desktop environments using portaudio.
Definition AudioCodecsBase.h:10
long map(long x, long in_min, long in_max, long out_min, long out_max)
Maps input to output values.
Definition NoArduino.h:189