From e9723de6c330cadbabd3f449400cdd4346f07a75 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Sat, 9 Sep 2023 21:07:20 +0200 Subject: [PATCH] minor updates for audioreactive * remove "not supported" warning on -S2 * minor tweaking of beat detector * optimized parameters for arduinoFFT * fixed a few implicit "double" promotions * minor UDP protocol optimizations * small optimization for fft task create / suspend * completely remove analogmic settings for -S3/-S2/-C3 where analog is not supported changes were already tested extensively in the MM fork. --- usermods/audioreactive/audio_reactive.h | 84 +++++++++++++++---------- usermods/audioreactive/audio_source.h | 11 ++-- 2 files changed, 57 insertions(+), 38 deletions(-) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index 357e612c..8ff26411 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -20,6 +20,12 @@ * .... */ +#if !defined(FFTTASK_PRIORITY) +#define FFTTASK_PRIORITY 1 // standard: looptask prio +//#define FFTTASK_PRIORITY 2 // above looptask, below asyc_tcp +//#define FFTTASK_PRIORITY 4 // above asyc_tcp +#endif + // Comment/Uncomment to toggle usb serial debugging // #define MIC_LOGGER // MIC sampling & sound input debugging (serial plotter) // #define FFT_SAMPLING_LOG // FFT result debugging @@ -104,7 +110,7 @@ static float sampleAgc = 0.0f; // Smoothed AGC sample // peak detection static bool samplePeak = false; // Boolean flag for peak - used in effects. Responding routine may reset this flag. Auto-reset after strip.getMinShowDelay() -static uint8_t maxVol = 10; // Reasonable value for constant volume for 'peak detector', as it won't always trigger (deprecated) +static uint8_t maxVol = 31; // Reasonable value for constant volume for 'peak detector', as it won't always trigger (deprecated) static uint8_t binNum = 8; // Used to select the bin for FFT based beat detection (deprecated) static bool udpSamplePeak = false; // Boolean flag for peak. Set at the same tiem as samplePeak, but reset by transmitAudioData static unsigned long timeOfPeak = 0; // time of last sample peak detection. @@ -173,13 +179,18 @@ static float windowWeighingFactors[samplesFFT] = {0.0f}; // Create FFT object #ifdef UM_AUDIOREACTIVE_USE_NEW_FFT -// lib_deps += https://github.com/kosme/arduinoFFT#develop @ 1.9.2 -#define FFT_SPEED_OVER_PRECISION // enables use of reciprocals (1/x etc), and an a few other speedups -#define FFT_SQRT_APPROXIMATION // enables "quake3" style inverse sqrt -#define sqrt(x) sqrtf(x) // little hack that reduces FFT time by 50% on ESP32 (as alternative to FFT_SQRT_APPROXIMATION) + // lib_deps += https://github.com/kosme/arduinoFFT#develop @ 1.9.2 + // these options actually cause slow-downs on all esp32 processors, don't use them. + // #define FFT_SPEED_OVER_PRECISION // enables use of reciprocals (1/x etc) - not faster on ESP32 + // #define FFT_SQRT_APPROXIMATION // enables "quake3" style inverse sqrt - slower on ESP32 + // Below options are forcing ArduinoFFT to use sqrtf() instead of sqrt() + #define sqrt(x) sqrtf(x) // little hack that reduces FFT time by 10-50% on ESP32 + #define sqrt_internal sqrtf // see https://github.com/kosme/arduinoFFT/pull/83 #else -// lib_deps += https://github.com/blazoncek/arduinoFFT.git + // around 40% slower on -S2 + // lib_deps += https://github.com/blazoncek/arduinoFFT.git #endif + #include #ifdef UM_AUDIOREACTIVE_USE_NEW_FFT @@ -411,7 +422,7 @@ static void runMicFilter(uint16_t numSamples, float *sampleBuffer) // p //constexpr float beta1 = 0.8285f; // 18Khz constexpr float beta1 = 0.85f; // 20Khz - constexpr float beta2 = (1.0f - beta1) / 2.0; + constexpr float beta2 = (1.0f - beta1) / 2.0f; static float last_vals[2] = { 0.0f }; // FIR high freq cutoff filter static float lowfilt = 0.0f; // IIR low frequency cutoff filter @@ -464,17 +475,17 @@ static void postProcessFFTResults(bool noiseGateOpen, int numberOfChannels) // p switch (FFTScalingMode) { case 1: // Logarithmic scaling - currentResult *= 0.42; // 42 is the answer ;-) - currentResult -= 8.0; // this skips the lowest row, giving some room for peaks - if (currentResult > 1.0) currentResult = logf(currentResult); // log to base "e", which is the fastest log() function - else currentResult = 0.0; // special handling, because log(1) = 0; log(0) = undefined + currentResult *= 0.42f; // 42 is the answer ;-) + currentResult -= 8.0f; // this skips the lowest row, giving some room for peaks + if (currentResult > 1.0f) currentResult = logf(currentResult); // log to base "e", which is the fastest log() function + else currentResult = 0.0f; // special handling, because log(1) = 0; log(0) = undefined currentResult *= 0.85f + (float(i)/18.0f); // extra up-scaling for high frequencies currentResult = mapf(currentResult, 0, LOG_256, 0, 255); // map [log(1) ... log(255)] to [0 ... 255] break; case 2: // Linear scaling currentResult *= 0.30f; // needs a bit more damping, get stay below 255 - currentResult -= 4.0; // giving a bit more room for peaks + currentResult -= 4.0f; // giving a bit more room for peaks if (currentResult < 1.0f) currentResult = 0.0f; currentResult *= 0.85f + (float(i)/1.8f); // extra up-scaling for high frequencies break; @@ -482,8 +493,8 @@ static void postProcessFFTResults(bool noiseGateOpen, int numberOfChannels) // p // square root scaling currentResult *= 0.38f; currentResult -= 6.0f; - if (currentResult > 1.0) currentResult = sqrtf(currentResult); - else currentResult = 0.0; // special handling, because sqrt(0) = undefined + if (currentResult > 1.0f) currentResult = sqrtf(currentResult); + else currentResult = 0.0f; // special handling, because sqrt(0) = undefined currentResult *= 0.85f + (float(i)/4.5f); // extra up-scaling for high frequencies currentResult = mapf(currentResult, 0.0, 16.0, 0.0, 255.0); // map [sqrt(1) ... sqrt(256)] to [0 ... 255] break; @@ -511,11 +522,11 @@ static void postProcessFFTResults(bool noiseGateOpen, int numberOfChannels) // p // peak detection is called from FFT task when vReal[] contains valid FFT results static void detectSamplePeak(void) { bool havePeak = false; - + // softhack007: this code continuously triggers while amplitude in the selected bin is above a certain threshold. So it does not detect peaks - it detects high activity in a frequency bin. // Poor man's beat detection by seeing if sample > Average + some value. // This goes through ALL of the 255 bins - but ignores stupid settings // Then we got a peak, else we don't. The peak has to time out on its own in order to support UDP sound sync. - if ((sampleAvg > 1) && (maxVol > 0) && (binNum > 1) && (vReal[binNum] > maxVol) && ((millis() - timeOfPeak) > 100)) { + if ((sampleAvg > 1) && (maxVol > 0) && (binNum > 4) && (vReal[binNum] > maxVol) && ((millis() - timeOfPeak) > 100)) { havePeak = true; } @@ -758,7 +769,7 @@ class AudioReactive : public Usermod { if (time_now - last_time > 2) { last_time = time_now; - if((fabsf(sampleReal) < 2.0f) || (sampleMax < 1.0f)) { + if((fabsf(sampleReal) < 2.0f) || (sampleMax < 1.0)) { // MIC signal is "squelched" - deliver silence tmpAgc = 0; // we need to "spin down" the intgrated error buffer @@ -873,8 +884,8 @@ class AudioReactive : public Usermod { // keep "peak" sample, but decay value if current sample is below peak if ((sampleMax < sampleReal) && (sampleReal > 0.5f)) { sampleMax = sampleMax + 0.5f * (sampleReal - sampleMax); // new peak - with some filtering - // another simple way to detect samplePeak - if ((binNum < 10) && (millis() - timeOfPeak > 80) && (sampleAvg > 1)) { + // another simple way to detect samplePeak - cannot detect beats, but reacts on peak volume + if (((binNum < 12) || ((maxVol < 1))) && (millis() - timeOfPeak > 80) && (sampleAvg > 1)) { samplePeak = true; timeOfPeak = millis(); udpSamplePeak = true; @@ -964,9 +975,10 @@ class AudioReactive : public Usermod { transmitData.FFT_Magnitude = my_magnitude; transmitData.FFT_MajorPeak = FFT_MajorPeak; - fftUdp.beginMulticastPacket(); - fftUdp.write(reinterpret_cast(&transmitData), sizeof(transmitData)); - fftUdp.endPacket(); + if (fftUdp.beginMulticastPacket() != 0) { // beginMulticastPacket returns 0 in case of error + fftUdp.write(reinterpret_cast(&transmitData), sizeof(transmitData)); + fftUdp.endPacket(); + } return; } // transmitAudioData() @@ -1272,9 +1284,10 @@ class AudioReactive : public Usermod { #ifdef WLED_DEBUG // complain when audio userloop has been delayed for long time. Currently we need userloop running between 500 and 1500 times per second. - if ((userloopDelay > 23) && !disableSoundProcessing && (audioSyncEnabled == 0)) { - DEBUG_PRINTF("[AR userLoop] hickup detected -> was inactive for last %d millis!\n", userloopDelay); - } + // softhack007 disabled temporarily - avoid serial console spam with MANY leds and low FPS + //if ((userloopDelay > 65) && !disableSoundProcessing && (audioSyncEnabled == 0)) { + //DEBUG_PRINTF("[AR userLoop] hickup detected -> was inactive for last %d millis!\n", userloopDelay); + //} #endif // run filters, and repeat in case of loop delays (hick-up compensation) @@ -1311,6 +1324,9 @@ class AudioReactive : public Usermod { if (millis() - lastTime > delayMs) { have_new_sample = receiveAudioData(); if (have_new_sample) last_UDPTime = millis(); +#ifdef ARDUINO_ARCH_ESP32 + else fftUdp.flush(); // Flush udp input buffers if we haven't read it - avoids hickups in receive mode. Does not work on 8266. +#endif lastTime = millis(); } if (have_new_sample) syncVolumeSmth = volumeSmth; // remember received sample @@ -1329,7 +1345,7 @@ class AudioReactive : public Usermod { // Info Page: keep max sample from last 5 seconds if ((millis() - sampleMaxTimer) > CYCLE_SAMPLEMAX) { sampleMaxTimer = millis(); - maxSample5sec = (0.15 * maxSample5sec) + 0.85 *((soundAgc) ? sampleAgc : sampleAvg); // reset, and start with some smoothing + maxSample5sec = (0.15f * maxSample5sec) + 0.85f *((soundAgc) ? sampleAgc : sampleAvg); // reset, and start with some smoothing if (sampleAvg < 1) maxSample5sec = 0; // noise gate } else { if ((sampleAvg >= 1)) maxSample5sec = fmaxf(maxSample5sec, (soundAgc) ? rawSampleAgc : sampleRaw); // follow maximum volume @@ -1373,10 +1389,11 @@ class AudioReactive : public Usermod { memset(fftAvg, 0, sizeof(fftAvg)); memset(fftResult, 0, sizeof(fftResult)); for(int i=(init?0:1); i 1.0) { + if (maxSample5sec > 1.0f) { float my_usage = 100.0f * (maxSample5sec / 255.0f); snprintf_P(myStringBuffer, 15, PSTR(" - peak %3d%%"), int(my_usage)); infoArr.add(myStringBuffer); @@ -1503,7 +1519,7 @@ class AudioReactive : public Usermod { } else { // error during audio source setup infoArr.add(F("not initialized")); - infoArr.add(F(" - check GPIO config")); + infoArr.add(F(" - check pin settings")); } } @@ -1807,7 +1823,9 @@ class AudioReactive : public Usermod { const char AudioReactive::_name[] PROGMEM = "AudioReactive"; const char AudioReactive::_enabled[] PROGMEM = "enabled"; const char AudioReactive::_inputLvl[] PROGMEM = "inputLevel"; +#if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) const char AudioReactive::_analogmic[] PROGMEM = "analogmic"; +#endif const char AudioReactive::_digitalmic[] PROGMEM = "digitalmic"; const char AudioReactive::UDP_SYNC_HEADER[] PROGMEM = "00002"; // new sync header version, as format no longer compatible with previous structure const char AudioReactive::UDP_SYNC_HEADER_v1[] PROGMEM = "00001"; // old sync header version - need to add backwards-compatibility feature diff --git a/usermods/audioreactive/audio_source.h b/usermods/audioreactive/audio_source.h index 0ba24e8b..ad466e4f 100644 --- a/usermods/audioreactive/audio_source.h +++ b/usermods/audioreactive/audio_source.h @@ -1,5 +1,5 @@ #pragma once - +#ifdef ARDUINO_ARCH_ESP32 #include "wled.h" #include #include @@ -22,14 +22,14 @@ // see https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/hw-reference/chip-series-comparison.html#related-documents // and https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/api-reference/peripherals/i2s.html#overview-of-all-modes -#if defined(CONFIG_IDF_TARGET_ESP32C2) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C5) || defined(CONFIG_IDF_TARGET_ESP32C6) || defined(CONFIG_IDF_TARGET_ESP32H2) || defined(ESP8266) || defined(ESP8265) +#if defined(CONFIG_IDF_TARGET_ESP32C2) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32C5) || defined(CONFIG_IDF_TARGET_ESP32C6) || defined(CONFIG_IDF_TARGET_ESP32H2) || defined(ESP8266) || defined(ESP8265) // there are two things in these MCUs that could lead to problems with audio processing: // * no floating point hardware (FPU) support - FFT uses float calculations. If done in software, a strong slow-down can be expected (between 8x and 20x) // * single core, so FFT task might slow down other things like LED updates #if !defined(SOC_I2S_NUM) || (SOC_I2S_NUM < 1) - #error This audio reactive usermod does not support ESP32-C2, ESP32-C3 or ESP32-S2. + #error This audio reactive usermod does not support ESP32-C2 or ESP32-C3. #else - #warning This audio reactive usermod does not support ESP32-C2, ESP32-C3 or ESP32-S2. + #warning This audio reactive usermod does not support ESP32-C2 and ESP32-C3. #endif #endif @@ -71,7 +71,7 @@ * if you want to receive two channels, one is the actual data from microphone and another channel is suppose to receive 0, it's different data in two channels, you need to choose I2S_CHANNEL_FMT_RIGHT_LEFT in this case. */ -#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0)) && (ESP_IDF_VERSION <= ESP_IDF_VERSION_VAL(4, 4, 3)) +#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0)) && (ESP_IDF_VERSION <= ESP_IDF_VERSION_VAL(4, 4, 4)) // espressif bug: only_left has no sound, left and right are swapped // https://github.com/espressif/esp-idf/issues/9635 I2S mic not working since 4.4 (IDFGH-8138) // https://github.com/espressif/esp-idf/issues/8538 I2S channel selection issue? (IDFGH-6918) @@ -766,3 +766,4 @@ class SPH0654 : public I2SSource { #endif } }; +#endif \ No newline at end of file