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.
This commit is contained in:
Frank 2023-09-09 21:07:20 +02:00
parent da0cf01eee
commit e9723de6c3
2 changed files with 57 additions and 38 deletions

View File

@ -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 // Comment/Uncomment to toggle usb serial debugging
// #define MIC_LOGGER // MIC sampling & sound input debugging (serial plotter) // #define MIC_LOGGER // MIC sampling & sound input debugging (serial plotter)
// #define FFT_SAMPLING_LOG // FFT result debugging // #define FFT_SAMPLING_LOG // FFT result debugging
@ -104,7 +110,7 @@ static float sampleAgc = 0.0f; // Smoothed AGC sample
// peak detection // 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 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 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 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. static unsigned long timeOfPeak = 0; // time of last sample peak detection.
@ -173,13 +179,18 @@ static float windowWeighingFactors[samplesFFT] = {0.0f};
// Create FFT object // Create FFT object
#ifdef UM_AUDIOREACTIVE_USE_NEW_FFT #ifdef UM_AUDIOREACTIVE_USE_NEW_FFT
// lib_deps += https://github.com/kosme/arduinoFFT#develop @ 1.9.2 // 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 // these options actually cause slow-downs on all esp32 processors, don't use them.
#define FFT_SQRT_APPROXIMATION // enables "quake3" style inverse sqrt // #define FFT_SPEED_OVER_PRECISION // enables use of reciprocals (1/x etc) - not faster on ESP32
#define sqrt(x) sqrtf(x) // little hack that reduces FFT time by 50% on ESP32 (as alternative to FFT_SQRT_APPROXIMATION) // #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 #else
// lib_deps += https://github.com/blazoncek/arduinoFFT.git // around 40% slower on -S2
// lib_deps += https://github.com/blazoncek/arduinoFFT.git
#endif #endif
#include <arduinoFFT.h> #include <arduinoFFT.h>
#ifdef UM_AUDIOREACTIVE_USE_NEW_FFT #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.8285f; // 18Khz
constexpr float beta1 = 0.85f; // 20Khz 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 last_vals[2] = { 0.0f }; // FIR high freq cutoff filter
static float lowfilt = 0.0f; // IIR low frequency 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) { switch (FFTScalingMode) {
case 1: case 1:
// Logarithmic scaling // Logarithmic scaling
currentResult *= 0.42; // 42 is the answer ;-) currentResult *= 0.42f; // 42 is the answer ;-)
currentResult -= 8.0; // this skips the lowest row, giving some room for peaks currentResult -= 8.0f; // 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 if (currentResult > 1.0f) 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 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 *= 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] currentResult = mapf(currentResult, 0, LOG_256, 0, 255); // map [log(1) ... log(255)] to [0 ... 255]
break; break;
case 2: case 2:
// Linear scaling // Linear scaling
currentResult *= 0.30f; // needs a bit more damping, get stay below 255 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; if (currentResult < 1.0f) currentResult = 0.0f;
currentResult *= 0.85f + (float(i)/1.8f); // extra up-scaling for high frequencies currentResult *= 0.85f + (float(i)/1.8f); // extra up-scaling for high frequencies
break; break;
@ -482,8 +493,8 @@ static void postProcessFFTResults(bool noiseGateOpen, int numberOfChannels) // p
// square root scaling // square root scaling
currentResult *= 0.38f; currentResult *= 0.38f;
currentResult -= 6.0f; currentResult -= 6.0f;
if (currentResult > 1.0) currentResult = sqrtf(currentResult); if (currentResult > 1.0f) currentResult = sqrtf(currentResult);
else currentResult = 0.0; // special handling, because sqrt(0) = undefined else currentResult = 0.0f; // special handling, because sqrt(0) = undefined
currentResult *= 0.85f + (float(i)/4.5f); // extra up-scaling for high frequencies 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] currentResult = mapf(currentResult, 0.0, 16.0, 0.0, 255.0); // map [sqrt(1) ... sqrt(256)] to [0 ... 255]
break; 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 // peak detection is called from FFT task when vReal[] contains valid FFT results
static void detectSamplePeak(void) { static void detectSamplePeak(void) {
bool havePeak = false; 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. // Poor man's beat detection by seeing if sample > Average + some value.
// This goes through ALL of the 255 bins - but ignores stupid settings // 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. // 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; havePeak = true;
} }
@ -758,7 +769,7 @@ class AudioReactive : public Usermod {
if (time_now - last_time > 2) { if (time_now - last_time > 2) {
last_time = time_now; 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 // MIC signal is "squelched" - deliver silence
tmpAgc = 0; tmpAgc = 0;
// we need to "spin down" the intgrated error buffer // 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 // keep "peak" sample, but decay value if current sample is below peak
if ((sampleMax < sampleReal) && (sampleReal > 0.5f)) { if ((sampleMax < sampleReal) && (sampleReal > 0.5f)) {
sampleMax = sampleMax + 0.5f * (sampleReal - sampleMax); // new peak - with some filtering sampleMax = sampleMax + 0.5f * (sampleReal - sampleMax); // new peak - with some filtering
// another simple way to detect samplePeak // another simple way to detect samplePeak - cannot detect beats, but reacts on peak volume
if ((binNum < 10) && (millis() - timeOfPeak > 80) && (sampleAvg > 1)) { if (((binNum < 12) || ((maxVol < 1))) && (millis() - timeOfPeak > 80) && (sampleAvg > 1)) {
samplePeak = true; samplePeak = true;
timeOfPeak = millis(); timeOfPeak = millis();
udpSamplePeak = true; udpSamplePeak = true;
@ -964,9 +975,10 @@ class AudioReactive : public Usermod {
transmitData.FFT_Magnitude = my_magnitude; transmitData.FFT_Magnitude = my_magnitude;
transmitData.FFT_MajorPeak = FFT_MajorPeak; transmitData.FFT_MajorPeak = FFT_MajorPeak;
fftUdp.beginMulticastPacket(); if (fftUdp.beginMulticastPacket() != 0) { // beginMulticastPacket returns 0 in case of error
fftUdp.write(reinterpret_cast<uint8_t *>(&transmitData), sizeof(transmitData)); fftUdp.write(reinterpret_cast<uint8_t *>(&transmitData), sizeof(transmitData));
fftUdp.endPacket(); fftUdp.endPacket();
}
return; return;
} // transmitAudioData() } // transmitAudioData()
@ -1272,9 +1284,10 @@ class AudioReactive : public Usermod {
#ifdef WLED_DEBUG #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. // 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)) { // softhack007 disabled temporarily - avoid serial console spam with MANY leds and low FPS
DEBUG_PRINTF("[AR userLoop] hickup detected -> was inactive for last %d millis!\n", userloopDelay); //if ((userloopDelay > 65) && !disableSoundProcessing && (audioSyncEnabled == 0)) {
} //DEBUG_PRINTF("[AR userLoop] hickup detected -> was inactive for last %d millis!\n", userloopDelay);
//}
#endif #endif
// run filters, and repeat in case of loop delays (hick-up compensation) // run filters, and repeat in case of loop delays (hick-up compensation)
@ -1311,6 +1324,9 @@ class AudioReactive : public Usermod {
if (millis() - lastTime > delayMs) { if (millis() - lastTime > delayMs) {
have_new_sample = receiveAudioData(); have_new_sample = receiveAudioData();
if (have_new_sample) last_UDPTime = millis(); 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(); lastTime = millis();
} }
if (have_new_sample) syncVolumeSmth = volumeSmth; // remember received sample 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 // Info Page: keep max sample from last 5 seconds
if ((millis() - sampleMaxTimer) > CYCLE_SAMPLEMAX) { if ((millis() - sampleMaxTimer) > CYCLE_SAMPLEMAX) {
sampleMaxTimer = millis(); 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 if (sampleAvg < 1) maxSample5sec = 0; // noise gate
} else { } else {
if ((sampleAvg >= 1)) maxSample5sec = fmaxf(maxSample5sec, (soundAgc) ? rawSampleAgc : sampleRaw); // follow maximum volume 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(fftAvg, 0, sizeof(fftAvg));
memset(fftResult, 0, sizeof(fftResult)); memset(fftResult, 0, sizeof(fftResult));
for(int i=(init?0:1); i<NUM_GEQ_CHANNELS; i+=2) fftResult[i] = 16; // make a tiny pattern for(int i=(init?0:1); i<NUM_GEQ_CHANNELS; i+=2) fftResult[i] = 16; // make a tiny pattern
inputLevel = 128; // resset level slider to default inputLevel = 128; // reset level slider to default
autoResetPeak(); autoResetPeak();
if (init && FFT_Task) { if (init && FFT_Task) {
delay(25); // give some time for I2S driver to finish sampling before we suspend it
vTaskSuspend(FFT_Task); // update is about to begin, disable task to prevent crash vTaskSuspend(FFT_Task); // update is about to begin, disable task to prevent crash
if (udpSyncConnected) { // close UDP sync connection (if open) if (udpSyncConnected) { // close UDP sync connection (if open)
udpSyncConnected = false; udpSyncConnected = false;
@ -1388,15 +1405,14 @@ class AudioReactive : public Usermod {
vTaskResume(FFT_Task); vTaskResume(FFT_Task);
connected(); // resume UDP connected(); // resume UDP
} else } else
// xTaskCreatePinnedToCore( xTaskCreateUniversal( // xTaskCreateUniversal also works on -S2 and -C3 with single core
xTaskCreate( // no need to "pin" this task to core #0
FFTcode, // Function to implement the task FFTcode, // Function to implement the task
"FFT", // Name of the task "FFT", // Name of the task
5000, // Stack size in words 5000, // Stack size in words
NULL, // Task input parameter NULL, // Task input parameter
1, // Priority of the task FFTTASK_PRIORITY, // Priority of the task
&FFT_Task // Task handle &FFT_Task // Task handle
// , 0 // Core where the task should run , 0 // Core where the task should run
); );
} }
micDataReal = 0.0f; // just to be sure micDataReal = 0.0f; // just to be sure
@ -1493,7 +1509,7 @@ class AudioReactive : public Usermod {
infoArr.add(F("I2S digital")); infoArr.add(F("I2S digital"));
} }
// input level or "silence" // input level or "silence"
if (maxSample5sec > 1.0) { if (maxSample5sec > 1.0f) {
float my_usage = 100.0f * (maxSample5sec / 255.0f); float my_usage = 100.0f * (maxSample5sec / 255.0f);
snprintf_P(myStringBuffer, 15, PSTR(" - peak %3d%%"), int(my_usage)); snprintf_P(myStringBuffer, 15, PSTR(" - peak %3d%%"), int(my_usage));
infoArr.add(myStringBuffer); infoArr.add(myStringBuffer);
@ -1503,7 +1519,7 @@ class AudioReactive : public Usermod {
} else { } else {
// error during audio source setup // error during audio source setup
infoArr.add(F("not initialized")); 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::_name[] PROGMEM = "AudioReactive";
const char AudioReactive::_enabled[] PROGMEM = "enabled"; const char AudioReactive::_enabled[] PROGMEM = "enabled";
const char AudioReactive::_inputLvl[] PROGMEM = "inputLevel"; 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"; const char AudioReactive::_analogmic[] PROGMEM = "analogmic";
#endif
const char AudioReactive::_digitalmic[] PROGMEM = "digitalmic"; 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[] 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 const char AudioReactive::UDP_SYNC_HEADER_v1[] PROGMEM = "00001"; // old sync header version - need to add backwards-compatibility feature

View File

@ -1,5 +1,5 @@
#pragma once #pragma once
#ifdef ARDUINO_ARCH_ESP32
#include "wled.h" #include "wled.h"
#include <driver/i2s.h> #include <driver/i2s.h>
#include <driver/adc.h> #include <driver/adc.h>
@ -22,14 +22,14 @@
// see https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/hw-reference/chip-series-comparison.html#related-documents // 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 // 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: // 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) // * 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 // * single core, so FFT task might slow down other things like LED updates
#if !defined(SOC_I2S_NUM) || (SOC_I2S_NUM < 1) #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 #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
#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 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 // 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/9635 I2S mic not working since 4.4 (IDFGH-8138)
// https://github.com/espressif/esp-idf/issues/8538 I2S channel selection issue? (IDFGH-6918) // https://github.com/espressif/esp-idf/issues/8538 I2S channel selection issue? (IDFGH-6918)
@ -766,3 +766,4 @@ class SPH0654 : public I2SSource {
#endif #endif
} }
}; };
#endif