Skip to content

Commit e032cf7

Browse files
committed
code cleanup and minor speed improvement
- moved scaling of FFT values into fftAddAvg() to use ferwer operations - added "using" for math types, removing some ifdefs and duplications
1 parent 3915b1b commit e032cf7

File tree

2 files changed

+39
-71
lines changed

2 files changed

+39
-71
lines changed

usermods/audioreactive/audio_reactive.cpp

Lines changed: 31 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ static uint8_t binNum = 8; // Used to select the bin for FFT based bea
133133

134134
#ifdef UM_AUDIOREACTIVE_USE_ARDUINO_FFT
135135
#include <arduinoFFT.h> // ArduinoFFT library for FFT and window functions
136+
#undef UM_AUDIOREACTIVE_USE_INTEGER_FFT // arduinoFFT has not integer support
136137
#else
137138
#include "dsps_fft2r.h" // ESP-IDF DSP library for FFT and window functions
138139
#ifdef FFT_PREFER_EXACT_PEAKS
@@ -145,22 +146,23 @@ static uint8_t binNum = 8; // Used to select the bin for FFT based bea
145146
#endif
146147
#endif
147148

148-
// These are the input and output vectors. Input vectors receive computed results from FFT.
149149
#if !defined(UM_AUDIOREACTIVE_USE_INTEGER_FFT)
150-
static float* valFFT = nullptr;
150+
using FFTsampleType = float;
151+
using FFTmathType = float;
152+
#define FFTabs fabsf
151153
#else
152-
static int16_t* valFFT = nullptr;
154+
using FFTsampleType = int16_t;
155+
using FFTmathType = int32_t;
156+
#define FFTabs abs
153157
#endif
158+
// These are the input and output vectors. Input vectors receive computed results from FFT.
159+
static FFTsampleType* valFFT = nullptr;
154160
#ifdef UM_AUDIOREACTIVE_USE_ARDUINO_FFT
155161
static float* vImag = nullptr; // imaginary part of FFT results
156162
#endif
157163

158164
// pre-computed window function
159-
#if !defined(UM_AUDIOREACTIVE_USE_INTEGER_FFT)
160-
__attribute__((aligned(16))) float* windowFFT;
161-
#else
162-
__attribute__((aligned(16))) int16_t* windowFFT;
163-
#endif
165+
FFTsampleType* windowFFT;
164166

165167
// use audio source class (ESP32 specific)
166168
#include "audio_source.h"
@@ -211,11 +213,7 @@ static bool useMicFilter = false; // if true, enables a
211213
// some prototypes, to ensure consistent interfaces
212214
static float fftAddAvg(int from, int to); // average of several FFT result bins
213215
void FFTcode(void * parameter); // audio processing task: read samples, run FFT, fill GEQ channels from FFT results
214-
#if !defined(UM_AUDIOREACTIVE_USE_INTEGER_FFT)
215-
static void runMicFilter(uint16_t numSamples, float *sampleBuffer); // pre-filtering of raw samples (band-pass)
216-
#else
217-
static void runMicFilter(uint16_t numSamples, int16_t *sampleBuffer);
218-
#endif
216+
static void runMicFilter(uint16_t numSamples, FFTsampleType *sampleBuffer);
219217
static void postProcessFFTResults(bool noiseGateOpen, int numberOfChannels); // post-processing and post-amp of GEQ channels
220218

221219
static TaskHandle_t FFT_Task = nullptr;
@@ -270,18 +268,15 @@ constexpr uint16_t samplesFFT_2 = 256; // meaningfull part of FFT resul
270268

271269
// compute average of several FFT result bins
272270
static float fftAddAvg(int from, int to) {
273-
#if !defined(UM_AUDIOREACTIVE_USE_INTEGER_FFT)
274-
float result = 0.0f;
271+
FFTmathType result = 0;
275272
for (int i = from; i <= to; i++) {
276273
result += valFFT[i];
277274
}
278-
#else
279-
int32_t result = 0;
280-
for (int i = from; i <= to; i++) {
281-
result += valFFT[i];
282-
}
283-
result *= 32; // scale result to match float values. note: scaling value between float and int is 512, float version is scaled down by 16
284-
#endif
275+
#if !defined(UM_AUDIOREACTIVE_USE_INTEGER_FFT)
276+
result = result * 0.0625; // divide by 16 to reduce magnitude. Want end result to be scaled linear and ~4096 max.
277+
#else
278+
result *= 32; // scale result to match float values. note: raw scaling value between float and int is 512, float version is scaled down by 16
279+
#endif
285280
return float(result) / float(to - from + 1); // return average as float
286281
}
287282

@@ -383,21 +378,12 @@ void FFTcode(void * parameter)
383378
// downside: frequencies below 100Hz will be ignored
384379
if (useMicFilter) runMicFilter(samplesFFT, valFFT);
385380
// find highest sample in the batch
386-
#if !defined(UM_AUDIOREACTIVE_USE_INTEGER_FFT)
387-
float maxSample = 0.0f; // max sample from FFT batch
388-
for (int i=0; i < samplesFFT; i++) {
389-
// pick our our current mic sample - we take the max value from all samples that go into FFT
390-
if ((valFFT[i] <= (INT16_MAX - 1024)) && (valFFT[i] >= (INT16_MIN + 1024))) //skip extreme values - normally these are artefacts
391-
if (fabsf((float)valFFT[i]) > maxSample) maxSample = fabsf((float)valFFT[i]);
392-
}
393-
#else
394-
int32_t maxSample = 0; // max sample from FFT batch
381+
FFTsampleType maxSample = 0; // max sample from FFT batch
395382
for (int i=0; i < samplesFFT; i++) {
396383
// pick our our current mic sample - we take the max value from all samples that go into FFT
397384
if ((valFFT[i] <= (INT16_MAX - 1024)) && (valFFT[i] >= (INT16_MIN + 1024))) //skip extreme values - normally these are artefacts
398-
if (abs(valFFT[i]) > maxSample) maxSample = abs(valFFT[i]);
385+
if (FFTabs(valFFT[i]) > maxSample) maxSample = FFTabs(valFFT[i]);
399386
}
400-
#endif
401387
// release highest sample to volume reactive effects early - not strictly necessary here - could also be done at the end of the function
402388
// early release allows the filters (getSample() and agcAvg()) to work with fresh values - we will have matching gain and noise gate values when we want to process the FFT results.
403389
micDataReal = maxSample;
@@ -421,16 +407,15 @@ void FFTcode(void * parameter)
421407
FFT.complexToMagnitude(); // Compute magnitudes
422408
valFFT[0] = 0; // The remaining DC offset on the signal produces a strong spike on position 0 that should be eliminated to avoid issues.
423409
FFT.majorPeak(&FFT_MajorPeak, &FFT_Magnitude); // let the effects know which freq was most dominant
424-
for (int i = 1; i < samplesFFT_2; i++) { // skip [0] as it is DC offset
425-
valFFT[i] = valFFT[i] / 16.0f; // Reduce magnitude. Want end result to be scaled linear and ~4096 max.
426-
}
427-
#elif !defined(UM_AUDIOREACTIVE_USE_INTEGER_FFT)
410+
// note: scaling is done in fftAddAvg(), so we don't scale here
411+
#else
428412
// run run float DSP FFT (takes ~x ms on ESP32, ~x ms on ESP32-S2, , ~x ms on ESP32-C3) TODO: test and fill in these values
429413
// remove DC offset
430-
float sum = 0;
414+
FFTmathType sum = 0;
431415
for (int i = 0; i < samplesFFT; i++) sum += valFFT[i];
432-
float mean = sum / samplesFFT;
416+
FFTmathType mean = sum / (FFTmathType)samplesFFT;
433417
for (int i = 0; i < samplesFFT; i++) valFFT[i] -= mean;
418+
#if !defined(UM_AUDIOREACTIVE_USE_INTEGER_FFT)
434419
//apply window function to samples and fill buffer with interleaved complex values [Re,Im,Re,Im,...]
435420
for (int i = samplesFFT - 1; i >= 0 ; i--) {
436421
// fill the buffer back to front to avoid overwriting samples
@@ -458,15 +443,10 @@ void FFTcode(void * parameter)
458443
FFT_Magnitude = valFFT[i];
459444
FFT_MajorPeak = i*(SAMPLE_RATE/samplesFFT);
460445
}
461-
valFFT[i] = valFFT[i] / 16.0f; // Reduce magnitude. Want end result to be scaled linear and ~4096 max.
446+
// note: scaling is done in fftAddAvg(), so we don't scale here
462447
}
463448
#else
464449
// run integer DSP FFT (takes ~x ms on ESP32, ~x ms on ESP32-S2, , ~1.5 ms on ESP32-C3) TODO: test and fill in these values
465-
// remove DC offset
466-
int32_t sum = 0;
467-
for (int i = 0; i < samplesFFT; i++) sum += valFFT[i];
468-
int32_t mean = sum / samplesFFT;
469-
for (int i = 0; i < samplesFFT; i++) valFFT[i] -= mean;
470450
//apply window function to samples and fill buffer with interleaved complex values [Re,Im,Re,Im,...]
471451
for (int i = samplesFFT - 1; i >= 0 ; i--) {
472452
// fill the buffer back to front to avoid overwriting samples
@@ -488,23 +468,18 @@ void FFTcode(void * parameter)
488468
FFT_Magnitude_int = valFFT[i] * 512; // scale to match raw float value
489469
FFT_MajorPeak_int = ((i * SAMPLE_RATE)/samplesFFT);
490470
}
491-
// note: scaling is done when converting to float in fftAddAvg(), so we don't scale here
471+
// note: scaling is done in fftAddAvg(), so we don't scale here
492472
}
493473
FFT_MajorPeak = FFT_MajorPeak_int;
494474
FFT_Magnitude = FFT_Magnitude_int;
495-
475+
#endif
496476
#endif
497477
FFT_MajorPeak = constrain(FFT_MajorPeak, 1.0f, 11025.0f); // restrict value to range expected by effects
498478
#if defined(WLED_DEBUG) || defined(SR_DEBUG)
499479
haveDoneFFT = true;
500480
#endif
501-
} else { // noise gate closed - only clear results as FFT was skipped. MIC samples are still valid when we do this.
502-
// only lower half of buffer contains FFT results, so only clear that part
503-
#if !defined(UM_AUDIOREACTIVE_USE_INTEGER_FFT)
504-
memset(valFFT, 0, samplesFFT * sizeof(float));
505-
#else
506-
memset(valFFT, 0, samplesFFT * sizeof(int16_t));
507-
#endif
481+
} else { // noise gate closed - only clear results as FFT was skipped. MIC samples are still valid when we do this -> set all samples to 0
482+
memset(valFFT, 0, samplesFFT * sizeof(FFTsampleType));
508483
FFT_MajorPeak = 1;
509484
FFT_Magnitude = 0.001;
510485
}
@@ -601,9 +576,9 @@ void FFTcode(void * parameter)
601576
// Pre / Postprocessing //
602577
///////////////////////////
603578

604-
#if !defined(UM_AUDIOREACTIVE_USE_INTEGER_FFT)
605-
static void runMicFilter(uint16_t numSamples, float *sampleBuffer) // pre-filtering of raw samples (band-pass)
579+
static void runMicFilter(uint16_t numSamples, FFTsampleType *sampleBuffer) // pre-filtering of raw samples (band-pass)
606580
{
581+
#if !defined(UM_AUDIOREACTIVE_USE_INTEGER_FFT)
607582
// low frequency cutoff parameter - see https://dsp.stackexchange.com/questions/40462/exponential-moving-average-cut-off-frequency (alpha = 2π × fc / fs)
608583
//constexpr float alpha = 0.04f; // 150Hz
609584
//constexpr float alpha = 0.03f; // 110Hz
@@ -633,10 +608,7 @@ static void runMicFilter(uint16_t numSamples, float *sampleBuffer) // p
633608
lowfilt += alpha * (sampleBuffer[i] - lowfilt);
634609
sampleBuffer[i] = sampleBuffer[i] - lowfilt;
635610
}
636-
}
637611
#else
638-
static void runMicFilter(uint16_t numSamples, int16_t *sampleBuffer) // pre-filtering of raw samples (band-pass)
639-
{
640612
// low frequency cutoff parameter 17.15 fixed point format
641613
//constexpr int32_t ALPHA_FP = 1311; // 0.04f * (1<<15) (150Hz)
642614
//constexpr int32_t ALPHA_FP = 983; // 0.03f * (1<<15) (110Hz)

usermods/audioreactive/audio_source.h

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ class AudioSource {
134134
Read num_samples from the microphone, and store them in the provided
135135
buffer
136136
*/
137-
virtual void getSamples(void *buffer, uint16_t num_samples) = 0;
137+
virtual void getSamples(FFTsampleType *buffer, uint16_t num_samples) = 0;
138138

139139
/* check if the audio source driver was initialized successfully */
140140
virtual bool isInitialized(void) {return(_initialized);}
@@ -314,7 +314,7 @@ class I2SSource : public AudioSource {
314314
if (_mclkPin != I2S_PIN_NO_CHANGE) PinManager::deallocatePin(_mclkPin, PinOwner::UM_Audioreactive);
315315
}
316316

317-
virtual void getSamples(void *buffer, uint16_t num_samples) {
317+
virtual void getSamples(FFTsampleType *buffer, uint16_t num_samples) {
318318
if (_initialized) {
319319
esp_err_t err;
320320
size_t bytes_read = 0; /* Counter variable to check if we actually got enough data */
@@ -333,10 +333,7 @@ class I2SSource : public AudioSource {
333333
}
334334

335335
// Store samples in sample buffer
336-
#if !defined(UM_AUDIOREACTIVE_USE_INTEGER_FFT)
337-
float* _buffer = static_cast<float*>(buffer);
338-
#else
339-
int16_t* _buffer = static_cast<int16_t*>(buffer); // use integer samples on ESP32-S2 and ESP32-C3
336+
#if defined(UM_AUDIOREACTIVE_USE_INTEGER_FFT)
340337
//constexpr int32_t FIXEDSHIFT = 8; // shift by 8 bits for fixed point math (no loss at 24bit input sample resolution)
341338
//int32_t intSampleScale = _sampleScale * (1<<FIXEDSHIFT); // _sampleScale <= 1.0f, shift for fixed point math
342339
#endif
@@ -350,8 +347,8 @@ class I2SSource : public AudioSource {
350347
#else
351348
float currSample = (float) newSamples[i]; // 16bit input -> use as-is
352349
#endif
353-
_buffer[i] = currSample;
354-
_buffer[i] *= _sampleScale; // scale samples
350+
buffer[i] = currSample;
351+
buffer[i] *= _sampleScale; // scale samples
355352
#else
356353
#ifdef I2S_SAMPLE_DOWNSCALE_TO_16BIT
357354
// note on sample scaling: scaling is only used for inputs with master clock and those are better suited for ESP32 or S3
@@ -363,7 +360,7 @@ class I2SSource : public AudioSource {
363360
//int32_t currSample = (newSamples[i] * intSampleScale) >> FIXEDSHIFT; // scale samples, shift back down to 16bit
364361
int16_t currSample = newSamples[i]; // 16bit input -> use as-is
365362
#endif
366-
_buffer[i] = (int16_t)currSample;
363+
buffer[i] = (int16_t)currSample;
367364
#endif
368365
}
369366
}
@@ -707,8 +704,7 @@ class I2SAdcSource : public I2SSource {
707704
}
708705

709706

710-
void getSamples(void *buffer, uint16_t num_samples) {
711-
float *_buffer = static_cast<float*>(buffer);
707+
void getSamples(FFTsampleType *buffer, uint16_t num_samples) {
712708
/* Enable ADC. This has to be enabled and disabled directly before and
713709
* after sampling, otherwise Wifi dies
714710
*/
@@ -723,7 +719,7 @@ class I2SAdcSource : public I2SSource {
723719
}
724720
#endif
725721

726-
I2SSource::getSamples(_buffer, num_samples);
722+
I2SSource::getSamples(buffer, num_samples);
727723

728724
#if !defined(I2S_GRAB_ADC1_COMPLETELY)
729725
// old code - works for me without enable/disable, at least on ESP32.

0 commit comments

Comments
 (0)