From 96d497a5cd8bd8739eb5fc405571aa053c173ec6 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Sat, 6 Aug 2022 16:48:26 +0200 Subject: [PATCH 01/19] AR: optimize sound sync, and code improvements UDP audio sync: introduced new header version, because the new struct (without myvals[]) is not compatible with the previous struct. Also optimized structure size. UDP audio sync: sender decides is AGC or non-AGC samples are transmitted. getsamples: move volumeSmth/volumeRaw code out of AGC core function. --- usermods/audioreactive/audio_reactive.h | 131 +++++++++++++++--------- 1 file changed, 83 insertions(+), 48 deletions(-) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index 23737a418..3db5d4f2a 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -58,12 +58,12 @@ static uint8_t audioSyncEnabled = 0; // bit field: bit 0 - send, bit 1 // #define AGC_NUM_PRESETS 3 // AGC presets: normal, vivid, lazy const double agcSampleDecay[AGC_NUM_PRESETS] = { 0.9994f, 0.9985f, 0.9997f}; // decay factor for sampleMax, in case the current sample is below sampleMax -const float agcZoneLow[AGC_NUM_PRESETS] = { 32, 28, 36}; // low volume emergency zone -const float agcZoneHigh[AGC_NUM_PRESETS] = { 240, 240, 248}; // high volume emergency zone -const float agcZoneStop[AGC_NUM_PRESETS] = { 336, 448, 304}; // disable AGC integrator if we get above this level -const float agcTarget0[AGC_NUM_PRESETS] = { 112, 144, 164}; // first AGC setPoint -> between 40% and 65% -const float agcTarget0Up[AGC_NUM_PRESETS] = { 88, 64, 116}; // setpoint switching value (a poor man's bang-bang) -const float agcTarget1[AGC_NUM_PRESETS] = { 220, 224, 216}; // second AGC setPoint -> around 85% +const float agcZoneLow[AGC_NUM_PRESETS] = { 32, 28, 36}; // low volume emergency zone +const float agcZoneHigh[AGC_NUM_PRESETS] = { 240, 240, 248}; // high volume emergency zone +const float agcZoneStop[AGC_NUM_PRESETS] = { 336, 448, 304}; // disable AGC integrator if we get above this level +const float agcTarget0[AGC_NUM_PRESETS] = { 112, 144, 164}; // first AGC setPoint -> between 40% and 65% +const float agcTarget0Up[AGC_NUM_PRESETS] = { 88, 64, 116}; // setpoint switching value (a poor man's bang-bang) +const float agcTarget1[AGC_NUM_PRESETS] = { 220, 224, 216}; // second AGC setPoint -> around 85% const double agcFollowFast[AGC_NUM_PRESETS] = { 1/192.f, 1/128.f, 1/256.f}; // quickly follow setpoint - ~0.15 sec const double agcFollowSlow[AGC_NUM_PRESETS] = {1/6144.f,1/4096.f,1/8192.f}; // slowly follow setpoint - ~2-15 secs const double agcControlKp[AGC_NUM_PRESETS] = { 0.6f, 1.5f, 0.65f}; // AGC - PI control, proportional gain parameter @@ -308,7 +308,7 @@ void FFTcode(void * parameter) fftCalc[ 4] = fftAddAvg(9,12); // 180 - 260 fftCalc[ 5] = fftAddAvg(12,16); // 240 - 340 fftCalc[ 6] = fftAddAvg(16,21); // 320 - 440 - fftCalc[ 7] = fftAddAvg(21,28); // 420 - 600 + fftCalc[ 7] = fftAddAvg(21,29); // 420 - 600 fftCalc[ 8] = fftAddAvg(29,37); // 580 - 760 fftCalc[ 9] = fftAddAvg(37,48); // 740 - 980 fftCalc[10] = fftAddAvg(48,64); // 960 - 1300 @@ -401,15 +401,29 @@ class AudioReactive : public Usermod { int8_t mclkPin = MLCK_PIN; #endif + // new "V2" audiosync struct - 40 Bytes struct audioSyncPacket { - char header[6]; - int sampleAgc; // 04 Bytes - int sampleRaw; // 04 Bytes - float sampleAvg; // 04 Bytes - bool samplePeak; // 01 Bytes + char header[6]; // 06 Bytes + float sampleRaw; // 04 Bytes - either "sampleRaw" or "rawSampleAgc" depending on soundAgc setting + float sampleSmth; // 04 Bytes - either "sampleAvg" or "sampleAgc" depending on soundAgc setting + uint8_t samplePeak; // 01 Bytes - 0 no peak; >=1 peak detected. In future, this will also provide peak Magnitude + uint8_t reserved1; // 01 Bytes - for future extensions - not used yet uint8_t fftResult[16]; // 16 Bytes - double FFT_Magnitude; // 08 Bytes - double FFT_MajorPeak; // 08 Bytes + float FFT_Magnitude; // 04 Bytes + float FFT_MajorPeak; // 04 Bytes + }; + + // old "V1" audiosync struct - 83 Bytes - for backwards compatibility + struct audioSyncPacket_v1 { + char header[6]; // 06 Bytes + uint8_t myVals[32]; // 32 Bytes + int sampleAgc; // 04 Bytes + int sampleRaw; // 04 Bytes + float sampleAvg; // 04 Bytes + bool samplePeak; // 01 Bytes + uint8_t fftResult[16]; // 16 Bytes + double FFT_Magnitude; // 08 Bytes + double FFT_MajorPeak; // 08 Bytes }; WiFiUDP fftUdp; @@ -460,6 +474,7 @@ class AudioReactive : public Usermod { static const char _analogmic[]; static const char _digitalmic[]; static const char UDP_SYNC_HEADER[]; + static const char UDP_SYNC_HEADER_v1[]; float my_magnitude; @@ -642,14 +657,6 @@ class AudioReactive : public Usermod { //if (userVar0 > 255) userVar0 = 255; last_soundAgc = soundAgc; - - volumeSmth = (soundAgc) ? sampleAgc:sampleAvg; - volumeRaw = (soundAgc) ? rawSampleAgc : sampleRaw; - - my_magnitude = FFT_Magnitude; // / 16.0f, 8.0f, 4.0f done in effects - if (soundAgc) my_magnitude *= multAgc; - if (volumeSmth < 1 ) my_magnitude = 0.001f; // noise gate closed - mute - } // agcAvg() @@ -749,17 +756,17 @@ class AudioReactive : public Usermod { audioSyncPacket transmitData; strncpy_P(transmitData.header, PSTR(UDP_SYNC_HEADER), 6); - transmitData.sampleAgc = sampleAgc; - transmitData.sampleRaw = sampleRaw; - transmitData.sampleAvg = sampleAvg; - transmitData.samplePeak = udpSamplePeak; - udpSamplePeak = 0; // Reset udpSamplePeak after we've transmitted it + transmitData.sampleRaw = volumeRaw; + transmitData.sampleSmth = volumeSmth; + transmitData.samplePeak = udpSamplePeak ? 1:0; + udpSamplePeak = false; // Reset udpSamplePeak after we've transmitted it + transmitData.reserved1 = 0; for (int i = 0; i < 16; i++) { transmitData.fftResult[i] = (uint8_t)constrain(fftResult[i], 0, 254); } - transmitData.FFT_Magnitude = FFT_Magnitude; + transmitData.FFT_Magnitude = my_magnitude; transmitData.FFT_MajorPeak = FFT_MajorPeak; fftUdp.beginMulticastPacket(); @@ -780,7 +787,7 @@ class AudioReactive : public Usermod { //DEBUGSR_PRINTLN("Checking for UDP Microphone Packet"); size_t packetSize = fftUdp.parsePacket(); - if (packetSize) { + if (packetSize > 5) { //DEBUGSR_PRINTLN("Received UDP Sync Packet"); uint8_t fftBuff[packetSize]; fftUdp.read(fftBuff, packetSize); @@ -789,19 +796,35 @@ class AudioReactive : public Usermod { if (packetSize == sizeof(audioSyncPacket) && !(isValidUdpSyncVersion((const char *)fftBuff))) { audioSyncPacket *receivedPacket = reinterpret_cast(fftBuff); - sampleAgc = receivedPacket->sampleAgc; - rawSampleAgc = receivedPacket->sampleAgc; - sampleRaw = receivedPacket->sampleRaw; - sampleAvg = receivedPacket->sampleAvg; + volumeSmth = receivedPacket->sampleSmth; + volumeRaw = receivedPacket->sampleRaw; + sampleRaw = volumeRaw; + sampleAvg = volumeSmth; + rawSampleAgc = volumeRaw; + sampleAgc = volumeSmth; + multAgc = 1.0f; + + // auto-reset sample peak. Need to do it here, because getSample() is not running + uint16_t MinShowDelay = strip.getMinShowDelay(); + if (millis() - timeOfPeak > MinShowDelay) { // Auto-reset of samplePeak after a complete frame has passed. + samplePeak = false; + udpSamplePeak = false; + } + //if (userVar1 == 0) samplePeak = 0; // Only change samplePeak IF it's currently false. // If it's true already, then the animation still needs to respond. - if (!samplePeak) samplePeak = receivedPacket->samplePeak; + if (!samplePeak) { + samplePeak = receivedPacket->samplePeak >0 ? true:false; + if (samplePeak) timeOfPeak = millis(); + //userVar1 = samplePeak; + } //These values are only available on the ESP32 for (int i = 0; i < 16; i++) fftResult[i] = receivedPacket->fftResult[i]; - FFT_Magnitude = receivedPacket->FFT_Magnitude; + my_magnitude = receivedPacket->FFT_Magnitude; + FFT_Magnitude = my_magnitude; FFT_MajorPeak = receivedPacket->FFT_MajorPeak; //DEBUGSR_PRINTLN("Finished parsing UDP Sync Packet"); } @@ -938,7 +961,7 @@ class AudioReactive : public Usermod { } // We cannot wait indefinitely before processing audio data //if (!enabled || strip.isUpdating()) return; - if (strip.isUpdating() && (millis() - lastUMRun < 12)) return; // be nice, but not too nice + if (strip.isUpdating() && (millis() - lastUMRun < 2)) return; // be nice, but not too nice // suspend local sound processing when "real time mode" is active (E131, UDP, ADALIGHT, ARTNET) if ( (realtimeOverride == REALTIME_OVERRIDE_NONE) // please odd other orrides here if needed @@ -992,6 +1015,17 @@ class AudioReactive : public Usermod { getSample(); // Sample the microphone agcAvg(); // Calculated the PI adjusted value as sampleAvg + + // update samples for effects (raw, smooth) + volumeSmth = (soundAgc) ? sampleAgc : sampleAvg; + volumeRaw = (soundAgc) ? rawSampleAgc: sampleRaw; + // update FFTMagnitude, taking into account AGC amplification + my_magnitude = FFT_Magnitude; // / 16.0f, 8.0f, 4.0f done in effects + if (soundAgc) my_magnitude *= multAgc; + if (volumeSmth < 1 ) my_magnitude = 0.001f; // noise gate closed - mute + + + // update UI uint8_t knownMode = strip.getFirstSelectedSeg().mode; // 1st selected segment is more appropriate than main segment if (lastMode != knownMode) { // only execute if mode changes @@ -1032,22 +1066,22 @@ class AudioReactive : public Usermod { last_user_inputLevel = new_user_inputLevel; } } - - #if defined(MIC_LOGGER) || defined(MIC_SAMPLING_LOG) || defined(FFT_SAMPLING_LOG) - EVERY_N_MILLIS(20) { - logAudio(); - } - #endif } // Begin UDP Microphone Sync - if ((audioSyncEnabled & 0x02) && millis() - lastTime > delayMs) // Only run the audio listener code if we're in Receive mode + if ((audioSyncEnabled & 0x02) && millis() - lastTime > delayMs) { // Only run the audio listener code if we're in Receive mode receiveAudioData(); + lastTime = millis(); + } - if (millis() - lastTime > 20) { - if (audioSyncEnabled & 0x01) { // Only run the transmit code IF we're in Transmit mode - transmitAudioData(); - } + #if defined(MIC_LOGGER) || defined(MIC_SAMPLING_LOG) || defined(FFT_SAMPLING_LOG) + EVERY_N_MILLIS(20) { + logAudio(); + } + #endif + + if ((audioSyncEnabled & 0x01) && millis() - lastTime > 20) { // Only run the transmit code IF we're in Transmit mode + transmitAudioData(); lastTime = millis(); } } @@ -1350,4 +1384,5 @@ const char AudioReactive::_enabled[] PROGMEM = "enabled"; const char AudioReactive::_inputLvl[] PROGMEM = "inputLevel"; const char AudioReactive::_analogmic[] PROGMEM = "analogmic"; const char AudioReactive::_digitalmic[] PROGMEM = "digitalmic"; -const char AudioReactive::UDP_SYNC_HEADER[] PROGMEM = "00001"; +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 From d0f53cb14a7a3ef90d6616ffbabd8d12ce36ecb1 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Sat, 6 Aug 2022 17:24:39 +0200 Subject: [PATCH 02/19] AR: removing some old debug code Align with SR WLED code: - removed old debug code that did not work any more - removed experimental MAJORPEAK_SUPPRESS_NOISE code --- usermods/audioreactive/audio_reactive.h | 156 ++++-------------------- 1 file changed, 27 insertions(+), 129 deletions(-) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index 3db5d4f2a..98d2c7504 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -21,7 +21,13 @@ */ // Comment/Uncomment to toggle usb serial debugging -// #define SR_DEBUG +// #define MIC_LOGGER // MIC sampling & sound input debugging (serial plotter) +// #define FFT_SAMPLING_LOG // FFT result debugging +// #define SR_DEBUG // generic SR DEBUG messages + +// hackers corner +// nothing atm + #ifdef SR_DEBUG #define DEBUGSR_PRINT(x) Serial.print(x) #define DEBUGSR_PRINTLN(x) Serial.println(x) @@ -31,6 +37,11 @@ #define DEBUGSR_PRINTLN(x) #define DEBUGSR_PRINTF(x...) #endif +// legacy support +#if defined(SR_DEBUG) && !defined(MIC_LOGGER) && !defined(NO_MIC_LOGGER) +#define MIC_LOGGER +#endif + #include "audio_source.h" @@ -39,12 +50,6 @@ constexpr int BLOCK_SIZE = 128; constexpr int SAMPLE_RATE = 20480; // Base sample rate in Hz - 20Khz is experimental //constexpr int SAMPLE_RATE = 10240; // Base sample rate in Hz - standard -// #define MIC_LOGGER -// #define MIC_SAMPLING_LOG -// #define FFT_SAMPLING_LOG - -//#define MAJORPEAK_SUPPRESS_NOISE // define to activate a dirty hack that ignores the lowest + hightest FFT bins - // globals static uint8_t inputLevel = 128; // UI slider value static uint8_t soundSquelch = 10; // squelch value for volume reactive routines (config value) @@ -146,9 +151,6 @@ float fftAddAvg(int from, int to) { void FFTcode(void * parameter) { DEBUGSR_PRINT("FFT running on core: "); DEBUGSR_PRINTLN(xPortGetCoreID()); -#ifdef MAJORPEAK_SUPPRESS_NOISE - static float xtemp[24] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; -#endif for(;;) { delay(1); // DO NOT DELETE THIS LINE! It is needed to give the IDLE(0) task enough time and to keep the watchdog happy. @@ -161,24 +163,18 @@ void FFTcode(void * parameter) } #ifdef WLED_DEBUG -// unsigned long start = millis(); uint64_t start = esp_timer_get_time(); #endif if (audioSource) audioSource->getSamples(vReal, samplesFFT); #ifdef WLED_DEBUG - //sampleTime = ((millis() - start)*3 + sampleTime*7)/10; // smooth if (start < esp_timer_get_time()) { // filter out overflows unsigned long sampleTimeInMillis = (esp_timer_get_time() - start +500ULL) / 1000ULL; // "+500" to ensure proper rounding sampleTime = (sampleTimeInMillis*3 + sampleTime*7)/10; // smooth } #endif - // old code - Last sample in vReal is our current mic sample - //micDataSm = (uint16_t)vReal[samplesFFT - 1]; // will do a this a bit later - //micDataSm = ((micData * 3) + micData)/4; - const int halfSamplesFFT = samplesFFT / 2; // samplesFFT divided by 2 float maxSample1 = 0.0f; // max sample from first half of FFT batch float maxSample2 = 0.0f; // max sample from second half of FFT batch @@ -219,34 +215,6 @@ void FFTcode(void * parameter) // vReal[3 .. 255] contain useful data, each a 20Hz interval (60Hz - 5120Hz). // There could be interesting data at bins 0 to 2, but there are too many artifacts. // -#ifdef MAJORPEAK_SUPPRESS_NOISE - // teporarily reduce signal strength in the highest + lowest bins - xtemp[0] = vReal[0]; vReal[0] *= 0.005f; - xtemp[1] = vReal[1]; vReal[1] *= 0.005f; - xtemp[2] = vReal[2]; vReal[2] *= 0.005f; - xtemp[3] = vReal[3]; vReal[3] *= 0.02f; - xtemp[4] = vReal[4]; vReal[4] *= 0.02f; - xtemp[5] = vReal[5]; vReal[5] *= 0.02f; - xtemp[6] = vReal[6]; vReal[6] *= 0.05f; - xtemp[7] = vReal[7]; vReal[7] *= 0.08f; - xtemp[8] = vReal[8]; vReal[8] *= 0.1f; - xtemp[9] = vReal[9]; vReal[9] *= 0.2f; - xtemp[10] = vReal[10]; vReal[10] *= 0.2f; - xtemp[11] = vReal[11]; vReal[11] *= 0.25f; - xtemp[12] = vReal[12]; vReal[12] *= 0.3f; - xtemp[13] = vReal[13]; vReal[13] *= 0.3f; - xtemp[14] = vReal[14]; vReal[14] *= 0.4f; - xtemp[15] = vReal[15]; vReal[15] *= 0.4f; - xtemp[16] = vReal[16]; vReal[16] *= 0.4f; - xtemp[17] = vReal[17]; vReal[17] *= 0.5f; - xtemp[18] = vReal[18]; vReal[18] *= 0.5f; - xtemp[19] = vReal[19]; vReal[19] *= 0.6f; - xtemp[20] = vReal[20]; vReal[20] *= 0.7f; - xtemp[21] = vReal[21]; vReal[21] *= 0.8f; - - xtemp[22] = vReal[samplesFFT-2]; vReal[samplesFFT-2] = 0.0f; - xtemp[23] = vReal[samplesFFT-1]; vReal[samplesFFT-1] = 0.0f; -#endif #ifdef UM_AUDIOREACTIVE_USE_NEW_FFT FFT.majorPeak(FFT_MajorPeak, FFT_Magnitude); // let the effects know which freq was most dominant @@ -254,36 +222,6 @@ void FFTcode(void * parameter) FFT.MajorPeak(&FFT_MajorPeak, &FFT_Magnitude); // let the effects know which freq was most dominant #endif -#ifdef MAJORPEAK_SUPPRESS_NOISE - // dirty hack: limit suppressed channel intensities to FFT_Magnitude - for (int k=0; k < 24; k++) if(xtemp[k] > FFT_Magnitude) xtemp[k] = FFT_Magnitude; - // restore bins - vReal[0] = xtemp[0]; - vReal[1] = xtemp[1]; - vReal[2] = xtemp[2]; - vReal[3] = xtemp[3]; - vReal[4] = xtemp[4]; - vReal[5] = xtemp[5]; - vReal[6] = xtemp[6]; - vReal[7] = xtemp[7]; - vReal[8] = xtemp[8]; - vReal[9] = xtemp[9]; - vReal[10] = xtemp[10]; - vReal[11] = xtemp[11]; - vReal[12] = xtemp[12]; - vReal[13] = xtemp[13]; - vReal[14] = xtemp[14]; - vReal[15] = xtemp[15]; - vReal[16] = xtemp[16]; - vReal[17] = xtemp[17]; - vReal[18] = xtemp[18]; - vReal[19] = xtemp[19]; - vReal[20] = xtemp[20]; - vReal[21] = xtemp[21]; - vReal[samplesFFT-2] = xtemp[22]; - vReal[samplesFFT-1] = xtemp[23]; -#endif - for (int i = 0; i < samplesFFT; i++) { // Values for bins 0 and 1 are WAY too large. Might as well start at 3. float t = fabs(vReal[i]); // just to be sure - values in fft bins should be positive any way fftBin[i] = t / 16.0f; // Reduce magnitude. Want end result to be linear and ~4096 max. @@ -344,14 +282,6 @@ void FFTcode(void * parameter) } #endif -#ifdef SR_DEBUG - // Looking for fftResultMax for each bin using Pink Noise - for (int i=0; i<16; i++) { - fftResultMax[i] = ((fftResultMax[i] * 63.0f) + fftResult[i]) / 64.0f; - DEBUGSR_PRINT(fftResultMax[i]*fftResultPink[i]); DEBUGSR_PRINT("\t"); - } - DEBUGSR_PRINTLN(); -#endif } // for(;;) } // FFTcode() @@ -482,32 +412,22 @@ class AudioReactive : public Usermod { void logAudio() { #ifdef MIC_LOGGER - //Serial.print("micData:"); Serial.print(micData); Serial.print("\t"); - //Serial.print("micDataSm:"); Serial.print(micDataSm); Serial.print("\t"); - //Serial.print("micIn:"); Serial.print(micIn); Serial.print("\t"); - //Serial.print("micLev:"); Serial.print(micLev); Serial.print("\t"); - //Serial.print("sample:"); Serial.print(sample); Serial.print("\t"); - //Serial.print("sampleAvg:"); Serial.print(sampleAvg); Serial.print("\t"); - Serial.print("sampleReal:"); Serial.print(sampleReal); Serial.print("\t"); - //Serial.print("sampleMax:"); Serial.print(sampleMax); Serial.print("\t"); - Serial.print("multAgc:"); Serial.print(multAgc, 4); Serial.print("\t"); - Serial.print("sampleAgc:"); Serial.print(sampleAgc); Serial.print("\t"); - Serial.println(); - #endif + // Debugging functions for audio input and sound processing. Comment out the values you want to see + Serial.print("micReal:"); Serial.print(micDataReal); Serial.print("\t"); + //Serial.print("micData:"); Serial.print(micData); Serial.print("\t"); + //Serial.print("micDataSm:"); Serial.print(micDataSm); Serial.print("\t"); + //Serial.print("micIn:"); Serial.print(micIn); Serial.print("\t"); + //Serial.print("micLev:"); Serial.print(micLev); Serial.print("\t"); + //Serial.print("sampleReal:"); Serial.print(sampleReal); Serial.print("\t"); + //Serial.print("sample:"); Serial.print(sample); Serial.print("\t"); + //Serial.print("sampleAvg:"); Serial.print(sampleAvg); Serial.print("\t"); + //Serial.print("sampleMax:"); Serial.print(sampleMax); Serial.print("\t"); + //Serial.print("samplePeak:"); Serial.print((samplePeak!=0) ? 128:0); Serial.print("\t"); + //Serial.print("multAgc:"); Serial.print(multAgc, 4); Serial.print("\t"); + Serial.print("sampleAgc:"); Serial.print(sampleAgc); Serial.print("\t"); + //Serial.print("volumeRaw:"); Serial.print(volumeRaw); Serial.print("\t"); + //Serial.print("volumeSmth:"); Serial.print(volumeSmth); Serial.print("\t"); - #ifdef MIC_SAMPLING_LOG - //------------ Oscilloscope output --------------------------- - Serial.print(targetAgc); Serial.print(" "); - Serial.print(multAgc); Serial.print(" "); - Serial.print(sampleAgc); Serial.print(" "); - - Serial.print(sample); Serial.print(" "); - Serial.print(sampleAvg); Serial.print(" "); - Serial.print(micLev); Serial.print(" "); - Serial.print(samplePeak); Serial.print(" "); //samplePeak = 0; - Serial.print(micIn); Serial.print(" "); - Serial.print(100); Serial.print(" "); - Serial.print(0); Serial.print(" "); Serial.println(); #endif @@ -672,18 +592,12 @@ class AudioReactive : public Usermod { micDataReal = micIn; #else micIn = micDataSm; // micDataSm = ((micData * 3) + micData)/4; - //DEBUGSR_PRINT("micIn:\tmicData:\tmicIn>>2:\tmic_In_abs:\tsample:\tsampleAdj:\tsampleAvg:\n"); - //DEBUGSR_PRINT(micIn); DEBUGSR_PRINT("\t"); DEBUGSR_PRINT(micData); #endif - // Note to self: the next line kills 80% of sample - "miclev" filter runs at "full arduino loop" speed, following the signal almost instantly! - //micLev = ((micLev * 31) + micIn) / 32; // Smooth it out over the last 32 samples for automatic centering micLev = ((micLev * 8191.0f) + micDataReal) / 8192.0f; // takes a few seconds to "catch up" with the Mic Input if(micIn < micLev) micLev = ((micLev * 31.0f) + micDataReal) / 32.0f; // align MicLev to lowest input signal micIn -= micLev; // Let's center it to 0 now - DEBUGSR_PRINT("\t\t"); DEBUGSR_PRINT(micIn); - // Using an exponential filter to smooth out the signal. We'll add controls for this in a future release. float micInNoDC = fabs(micDataReal - micLev); expAdjF = (weighting * micInNoDC + (1.0-weighting) * expAdjF); @@ -691,13 +605,9 @@ class AudioReactive : public Usermod { expAdjF = fabsf(expAdjF); // Now (!) take the absolute value tmpSample = expAdjF; - - DEBUGSR_PRINT("\t\t"); DEBUGSR_PRINT(tmpSample); - micIn = abs(micIn); // And get the absolute value of each sample sampleAdj = tmpSample * sampleGain / 40.0f * inputLevel/128.0f + tmpSample / 16.0f; // Adjust the gain. with inputLevel adjustment - //sampleReal = sampleAdj; sampleReal = tmpSample; sampleAdj = fmax(fmin(sampleAdj, 255), 0); // Question: why are we limiting the value to 8 bits ??? @@ -716,9 +626,6 @@ class AudioReactive : public Usermod { sampleAvg = ((sampleAvg * 15.0f) + sampleAdj) / 16.0f; // Smooth it out over the last 16 samples. - DEBUGSR_PRINT("\t"); DEBUGSR_PRINT(sampleRaw); - DEBUGSR_PRINT("\t\t"); DEBUGSR_PRINT(sampleAvg); DEBUGSR_PRINT("\n\n"); - // Fixes private class variable compiler error. Unsure if this is the correct way of fixing the root problem. -THATDONFC uint16_t MinShowDelay = strip.getMinShowDelay(); @@ -729,14 +636,6 @@ class AudioReactive : public Usermod { //if (userVar1 == 0) samplePeak = 0; // Poor man's beat detection by seeing if sample > Average + some value. - // Serial.print(binNum); Serial.print("\t"); - // Serial.print(fftBin[binNum]); - // Serial.print("\t"); - // Serial.print(fftAvg[binNum/16]); - // Serial.print("\t"); - // Serial.print(maxVol); - // Serial.print("\t"); - // Serial.println(samplePeak); if ((fftBin[binNum] > maxVol) && (millis() > (timeOfPeak + 100))) { // This goes through ALL of the 255 bins // if (sample > (sampleAvg + maxVol) && millis() > (timeOfPeak + 200)) { // 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. @@ -960,7 +859,6 @@ class AudioReactive : public Usermod { return; } // We cannot wait indefinitely before processing audio data - //if (!enabled || strip.isUpdating()) return; if (strip.isUpdating() && (millis() - lastUMRun < 2)) return; // be nice, but not too nice // suspend local sound processing when "real time mode" is active (E131, UDP, ADALIGHT, ARTNET) From b46a6ed0948c3a7a5f6bcf562a1573eb32edf022 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Sat, 6 Aug 2022 17:53:35 +0200 Subject: [PATCH 03/19] AR: samples dynamics limiter (experimental) to enable, compile with -D SOUND_DYNAMICS_LIMITER. still missing UI integration, and more testing. --- usermods/audioreactive/audio_reactive.h | 38 ++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index 98d2c7504..90150198e 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -26,7 +26,7 @@ // #define SR_DEBUG // generic SR DEBUG messages // hackers corner -// nothing atm +//#define SOUND_DYNAMICS_LIMITER // experimental: define to enable a dynamics limiter that avoids "sudden flashes" at onsets. Makes some effects look more "smooth and fluent" #ifdef SR_DEBUG #define DEBUGSR_PRINT(x) Serial.print(x) @@ -647,6 +647,39 @@ class AudioReactive : public Usermod { } // getSample() + /* Limits the dynamics of volumeSmth (= sampleAvg or sampleAgc). + * It does not affect FFTResult[] or volumeRaw ( = sample or rawSampleAgc) + */ + // effects: Gravimeter, Gravcenter, Gravcentric, Noisefire, Plasmoid, Freqpixels, Freqwave, Gravfreq, (2D Swirl, 2D Waverly) + // experimental, as it still has side-effects on AGC - AGC detects "silence" to late (due to long decay time) and ditches up the gain multiplier. + // values below will be made user-configurable later + const float attackTime = 200; // attack time -> 0.2sec + const float decayTime = 2800; // decay time -> 2.8sec + + void limitSampleDynamics(void) { + #ifdef SOUND_DYNAMICS_LIMITER + const float bigChange = 196; // just a representative number - a large, expected sample value + static unsigned long last_time = 0; + static float last_volumeSmth = 0.0f; + + if ((attackTime > 0) && (decayTime > 0)) { // only change volume if user has defined attack>0 and decay>0 + long delta_time = millis() - last_time; + delta_time = constrain(delta_time , 1, 1000); // below 1ms -> 1ms; above 1sec -> sily lil hick-up + float maxAttack = bigChange * float(delta_time) / attackTime; + float maxDecay = - bigChange * float(delta_time) / decayTime; + + float deltaSample = volumeSmth - last_volumeSmth; + if (deltaSample > maxAttack) deltaSample = maxAttack; + if (deltaSample < maxDecay) deltaSample = maxDecay; + volumeSmth = last_volumeSmth + deltaSample; + } + + last_volumeSmth = volumeSmth; + last_time = millis(); + #endif + } + + void transmitAudioData() { if (!udpSyncConnected) return; @@ -922,6 +955,7 @@ class AudioReactive : public Usermod { if (soundAgc) my_magnitude *= multAgc; if (volumeSmth < 1 ) my_magnitude = 0.001f; // noise gate closed - mute + limitSampleDynamics(); // optional - makes volumeSmth very smooth and fluent // update UI uint8_t knownMode = strip.getFirstSelectedSeg().mode; // 1st selected segment is more appropriate than main segment @@ -982,6 +1016,8 @@ class AudioReactive : public Usermod { transmitAudioData(); lastTime = millis(); } + + //limitSampleDynamics(); // If done as the last step, it will also affect audio received by UDP sound sync. Problem: effects might see inconsistent intermediate values and start flickering :-( } From 8694e7a6bf7f93d9ab53cf2fc804b437bed62914 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Sat, 6 Aug 2022 18:17:45 +0200 Subject: [PATCH 04/19] AR: loop hickup protection (from SR WLED) same "hickup protection" as implemented in SR WLED. --- usermods/audioreactive/audio_reactive.h | 42 ++++++++++++++----------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index 90150198e..d0486c5fb 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -493,7 +493,7 @@ class AudioReactive : public Usermod { * a) normal zone - very slow adjustment * b) emergency zome (<10% or >90%) - very fast adjustment */ - void agcAvg() + void agcAvg(unsigned long the_time) { const int AGC_preset = (soundAgc > 0)? (soundAgc-1): 0; // make sure the _compiler_ knows this value will not change while we are inside the function @@ -510,6 +510,8 @@ class AudioReactive : public Usermod { // so let's make sure that the control loop is not running at insane speed static unsigned long last_time = 0; unsigned long time_now = millis(); + if ((the_time > 0) && (the_time < time_now)) time_now = the_time; // allow caller to override my clock + if (time_now - last_time > 2) { last_time = time_now; @@ -923,29 +925,31 @@ class AudioReactive : public Usermod { if (audioSyncEnabled & 0x02) disableSoundProcessing = true; // make sure everything is disabled IF in audio Receive mode if (audioSyncEnabled & 0x01) disableSoundProcessing = false; // keep running audio IF we're in audio Transmit mode - if (!(audioSyncEnabled & 0x02) && !disableSoundProcessing) { // Only run the sampling code IF we're not in Receive mode or realtime mode + // Only run the sampling code IF we're not in Receive mode or realtime mode + if (!(audioSyncEnabled & 0x02) && !disableSoundProcessing) { bool agcEffect = false; - if (soundAgc > AGC_NUM_PRESETS) soundAgc = 0; // make sure that AGC preset is valid (to avoid array bounds violation) - int userloopDelay = int(millis() - lastUMRun); // how long since last run? we might need to cat up to compensate lost times - int samplesSkipped = 0; - if (userloopDelay > 12) samplesSkipped = (userloopDelay + 12) / 25; // every 25ms we get a new batch of samples - if (samplesSkipped > 100) samplesSkipped = 100; // don't be silly -#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)) { - // Expect lagging in soundreactive effects if you see the next messages !!! - DEBUG_PRINTF("[AR userLoop] hickup detected -> was inactive for last %d millis!\n", userloopDelay); - if (samplesSkipped > 0) DEBUG_PRINTF("[AR userLoop] lost %d sample(s).\n", samplesSkipped); - } -#endif + unsigned long t_now = millis(); // remember current time + int userloopDelay = int(t_now - lastUMRun); + if (lastUMRun == 0) userloopDelay=0; // startup - don't have valid data from last run. - lastUMRun = millis(); // update time keeping - - getSample(); // Sample the microphone - agcAvg(); // Calculated the PI adjusted value as sampleAvg + #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); + } + #endif + // run filters, and repeat in case of loop delays (hick-up compensation) + if (userloopDelay <2) userloopDelay = 0; // minor glitch, no problem + if (userloopDelay >200) userloopDelay = 200; // limit number of filter re-runs + do { + getSample(); // run microphone sampling filters + agcAvg(t_now - userloopDelay); // Calculated the PI adjusted value as sampleAvg + userloopDelay -= 2; // advance "simulated time" by 2ms + } while (userloopDelay > 0); + lastUMRun = t_now; // update time keeping // update samples for effects (raw, smooth) volumeSmth = (soundAgc) ? sampleAgc : sampleAvg; From 86e8ee334f464e54db7c7c1216feb76bdd7ec774 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Sun, 7 Aug 2022 22:04:26 +0200 Subject: [PATCH 05/19] future support: reading a single sample on 8266 audioreactive will still not work on 8266. This is just experimental code that allows to read a single sample from ADC every 20 millis. --- usermods/audioreactive/audio_reactive.h | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index d0486c5fb..7588af382 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -47,8 +47,9 @@ constexpr i2s_port_t I2S_PORT = I2S_NUM_0; constexpr int BLOCK_SIZE = 128; -constexpr int SAMPLE_RATE = 20480; // Base sample rate in Hz - 20Khz is experimental -//constexpr int SAMPLE_RATE = 10240; // Base sample rate in Hz - standard +//constexpr int SAMPLE_RATE = 22050; // Base sample rate in Hz - 22Khz is a standard rate. Physical sample time -> 23ms +constexpr int SAMPLE_RATE = 20480; // Base sample rate in Hz - 20Khz is experimental. Physical sample time -> 25ms +//constexpr int SAMPLE_RATE = 10240; // Base sample rate in Hz - standard. Physical sample time -> 50ms // globals static uint8_t inputLevel = 128; // UI slider value @@ -593,7 +594,19 @@ class AudioReactive : public Usermod { micIn = inoise8(millis(), millis()); // Simulated analog read micDataReal = micIn; #else + #ifdef ESP32 micIn = micDataSm; // micDataSm = ((micData * 3) + micData)/4; + #else + // this is the minimal code for reading analog mic input on 8266. + // warning!! Absolutely experimental code. Audio on 8266 is still not working. Expects a million follow-on problems. + static unsigned long lastAnalogTime = 0; + if (millis() - lastAnalogTime > 20) { + micDataReal = analogRead(A0); // read one sample with 10bit resolution. This is a dirty hack, supporting volumereactive effects only. + lastAnalogTime = millis(); + } + micDataSm = micDataReal; + micIn = micDataSm; + #endif #endif micLev = ((micLev * 8191.0f) + micDataReal) / 8192.0f; // takes a few seconds to "catch up" with the Mic Input From 58987989da8b52169a782e42fb007427347ee220 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Sun, 7 Aug 2022 22:19:38 +0200 Subject: [PATCH 06/19] experimetal: limit rate at which the FFT task runs this should do the trick. Needs some more testing. --- usermods/audioreactive/audio_reactive.h | 29 ++++++++++++++++--------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index 7588af382..284cdf695 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -51,6 +51,8 @@ constexpr int BLOCK_SIZE = 128; constexpr int SAMPLE_RATE = 20480; // Base sample rate in Hz - 20Khz is experimental. Physical sample time -> 25ms //constexpr int SAMPLE_RATE = 10240; // Base sample rate in Hz - standard. Physical sample time -> 50ms +#define FFT_MIN_CYCLE 22 // minimum time before FFT task is repeated. Must be less than time needed to read 512 samples at SAMPLE_RATE -> not the same as I2S time!! + // globals static uint8_t inputLevel = 128; // UI slider value static uint8_t soundSquelch = 10; // squelch value for volume reactive routines (config value) @@ -151,7 +153,11 @@ float fftAddAvg(int from, int to) { // FFT main code void FFTcode(void * parameter) { - DEBUGSR_PRINT("FFT running on core: "); DEBUGSR_PRINTLN(xPortGetCoreID()); + DEBUGSR_PRINT("FFT started on core: "); DEBUGSR_PRINTLN(xPortGetCoreID()); + + // see https://www.freertos.org/vtaskdelayuntil.html + const TickType_t xFrequency = FFT_MIN_CYCLE * portTICK_PERIOD_MS; + TickType_t xLastWakeTime = xTaskGetTickCount(); for(;;) { delay(1); // DO NOT DELETE THIS LINE! It is needed to give the IDLE(0) task enough time and to keep the watchdog happy. @@ -159,7 +165,8 @@ void FFTcode(void * parameter) // Only run the FFT computing code if we're not in Receive mode and not in realtime mode if (disableSoundProcessing || (audioSyncEnabled & 0x02)) { - delay(7); // release CPU - delay is implemeted using vTaskDelay(). cannot use yield() because we are out of arduino loop context + //delay(7); // release CPU - delay is implemeted using vTaskDelay(). cannot use yield() because we are out of arduino loop context + vTaskDelayUntil( &xLastWakeTime, xFrequency); // release CPU, by doing nothing for FFT_MIN_CYCLE millis continue; } @@ -270,11 +277,6 @@ void FFTcode(void * parameter) fftAvg[i] = (float)fftResult[i]*0.05f + 0.95f*fftAvg[i]; } - // release second sample to volume reactive effects. - // The FFT process currently takes ~20ms, so releasing a second sample now effectively doubles the "sample rate" - micDataSm = (uint16_t)maxSample2; - micDataReal = maxSample2; - #ifdef WLED_DEBUG //fftTime = ((millis() - start)*3 + fftTime*7)/10; if (start < esp_timer_get_time()) { // filter out overflows @@ -283,6 +285,12 @@ void FFTcode(void * parameter) } #endif + vTaskDelayUntil( &xLastWakeTime, xFrequency); // release CPU, by waiting until FFT_MIN_CYCLE is over + // release second sample to volume reactive effects. + // Releasing a second sample now effectively doubles the "sample rate" + micDataSm = (uint16_t)maxSample2; + micDataReal = maxSample2; + } // for(;;) } // FFTcode() @@ -1059,14 +1067,15 @@ class AudioReactive : public Usermod { if (FFT_Task) vTaskResume(FFT_Task); else - xTaskCreatePinnedToCore( +// xTaskCreatePinnedToCore( + xTaskCreate( // no need to "pin" this task to core #0 FFTcode, // Function to implement the task "FFT", // Name of the task 5000, // Stack size in words NULL, // Task input parameter 1, // Priority of the task - &FFT_Task, // Task handle - 0 // Core where the task should run + &FFT_Task // Task handle +// , 0 // Core where the task should run ); } if (enabled) disableSoundProcessing = false; From 3a8c99d43cab347bfb0b3ec39cbc78439f8befd1 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Mon, 8 Aug 2022 10:51:46 +0200 Subject: [PATCH 07/19] AR: removed two unneeded variables some cleanup - no functional impact. --- usermods/audioreactive/audio_reactive.h | 27 ++++++++----------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index 284cdf695..9425294ca 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -82,9 +82,7 @@ const float agcSampleSmooth[AGC_NUM_PRESETS] = { 1/12.f, 1/6.f, 1/16.f}; // static AudioSource *audioSource = nullptr; static volatile bool disableSoundProcessing = false; // if true, sound processing (FFT, filters, AGC) will be suspended. "volatile" as its shared between tasks. -//static uint16_t micData; // Analog input for FFT -static uint16_t micDataSm; // Smoothed mic data, as it's a bit twitchy -static float micDataReal = 0.0f; // future support - this one has the full 24bit MicIn data - lowest 8bit after decimal point +static float micDataReal = 0.0f; // MicIn data with full 24bit resolution - lowest 8bit after decimal point static float multAgc = 1.0f; // sample * multAgc = sampleAgc. Our AGC multiplier //////////////////// @@ -201,7 +199,6 @@ void FFTcode(void * parameter) if (fabsf((float)vReal[i]) > maxSample2) maxSample2 = fabsf((float)vReal[i]); } // release first sample to volume reactive effects - micDataSm = (uint16_t)maxSample1; micDataReal = maxSample1; #ifdef UM_AUDIOREACTIVE_USE_NEW_FFT @@ -288,7 +285,6 @@ void FFTcode(void * parameter) vTaskDelayUntil( &xLastWakeTime, xFrequency); // release CPU, by waiting until FFT_MIN_CYCLE is over // release second sample to volume reactive effects. // Releasing a second sample now effectively doubles the "sample rate" - micDataSm = (uint16_t)maxSample2; micDataReal = maxSample2; } // for(;;) @@ -373,15 +369,13 @@ class AudioReactive : public Usermod { const uint16_t delayMs = 10; // I don't want to sample too often and overload WLED // variables used in effects - uint8_t maxVol = 10; // Reasonable value for constant volume for 'peak detector', as it won't always trigger - uint8_t binNum = 8; // Used to select the bin for FFT based beat detection. + uint8_t maxVol = 10; // Reasonable value for constant volume for 'peak detector', as it won't always trigger (deprecated) + uint8_t binNum = 8; // Used to select the bin for FFT based beat detection (deprecated) bool samplePeak = 0; // Boolean flag for peak. Responding routine must reset this flag float volumeSmth; // either sampleAvg or sampleAgc depending on soundAgc; smoothed sample int16_t volumeRaw; // either sampleRaw or rawSampleAgc depending on soundAgc + float my_magnitude; // FFT_Magnitude, scaled by multAgc - #ifdef MIC_SAMPLING_LOG - uint8_t targetAgc = 60; // This is our setPoint at 20% of max for the adjusted output (used only in logAudio()) - #endif bool udpSamplePeak = 0; // Boolean flag for peak. Set at the same tiem as samplePeak, but reset by transmitAudioData int16_t micIn = 0; // Current sample starts with negative values and large values, which is why it's 16 bit signed int16_t sampleRaw; // Current sample. Must only be updated ONCE!!! (amplified mic value by sampleGain and inputLevel; smoothed over 16 samples) @@ -415,16 +409,12 @@ class AudioReactive : public Usermod { static const char UDP_SYNC_HEADER[]; static const char UDP_SYNC_HEADER_v1[]; - float my_magnitude; - // private methods void logAudio() { #ifdef MIC_LOGGER // Debugging functions for audio input and sound processing. Comment out the values you want to see Serial.print("micReal:"); Serial.print(micDataReal); Serial.print("\t"); - //Serial.print("micData:"); Serial.print(micData); Serial.print("\t"); - //Serial.print("micDataSm:"); Serial.print(micDataSm); Serial.print("\t"); //Serial.print("micIn:"); Serial.print(micIn); Serial.print("\t"); //Serial.print("micLev:"); Serial.print(micLev); Serial.print("\t"); //Serial.print("sampleReal:"); Serial.print(sampleReal); Serial.print("\t"); @@ -474,7 +464,7 @@ class AudioReactive : public Usermod { } for(int i = 0; i < 16; i++) { Serial.print(i); Serial.print(":"); - Serial.printf("%04d ", map(fftResult[i], 0, (scaleValuesFromCurrentMaxVal ? maxVal : defaultScalingFromHighValue), (mapValuesToPlotterSpace*i*scalingToHighValue)+0, (mapValuesToPlotterSpace*i*scalingToHighValue)+scalingToHighValue-1)); + Serial.printf("%04ld ", map(fftResult[i], 0, (scaleValuesFromCurrentMaxVal ? maxVal : defaultScalingFromHighValue), (mapValuesToPlotterSpace*i*scalingToHighValue)+0, (mapValuesToPlotterSpace*i*scalingToHighValue)+scalingToHighValue-1)); } if(printMaxVal) { Serial.printf("maxVal:%04d ", maxVal + (mapValuesToPlotterSpace ? 16*256 : 0)); @@ -603,7 +593,7 @@ class AudioReactive : public Usermod { micDataReal = micIn; #else #ifdef ESP32 - micIn = micDataSm; // micDataSm = ((micData * 3) + micData)/4; + micIn = int(micDataReal); // micDataSm = ((micData * 3) + micData)/4; #else // this is the minimal code for reading analog mic input on 8266. // warning!! Absolutely experimental code. Audio on 8266 is still not working. Expects a million follow-on problems. @@ -612,8 +602,7 @@ class AudioReactive : public Usermod { micDataReal = analogRead(A0); // read one sample with 10bit resolution. This is a dirty hack, supporting volumereactive effects only. lastAnalogTime = millis(); } - micDataSm = micDataReal; - micIn = micDataSm; + micIn = int(micDataReal); #endif #endif @@ -659,8 +648,8 @@ class AudioReactive : public Usermod { //if (userVar1 == 0) samplePeak = 0; // Poor man's beat detection by seeing if sample > Average + some value. - if ((fftBin[binNum] > maxVol) && (millis() > (timeOfPeak + 100))) { // This goes through ALL of the 255 bins // if (sample > (sampleAvg + maxVol) && millis() > (timeOfPeak + 200)) { + if ((fftBin[binNum] > maxVol) && (millis() > (timeOfPeak + 100))) { // This goes through ALL of the 255 bins // 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. samplePeak = true; timeOfPeak = millis(); From 924073424f72da6b7fd9d426f896c124118b30d0 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Mon, 8 Aug 2022 13:53:46 +0200 Subject: [PATCH 08/19] AR FFT task optimization - wait so I2S can fill its buffers It seems that waiting first (before reading I2S) is much better than waiting after FFT is completed. --- usermods/audioreactive/audio_reactive.h | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index 9425294ca..35606324e 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -92,7 +92,7 @@ static float multAgc = 1.0f; // sample * multAgc = sampleAgc. // 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) +#define sqrt(x) sqrtf(x) // little hack that reduces FFT time by 50% on ESP32 (as alternative to FFT_SQRT_APPROXIMATION) #endif #include "arduinoFFT.h" @@ -155,9 +155,10 @@ void FFTcode(void * parameter) // see https://www.freertos.org/vtaskdelayuntil.html const TickType_t xFrequency = FFT_MIN_CYCLE * portTICK_PERIOD_MS; - TickType_t xLastWakeTime = xTaskGetTickCount(); + //const TickType_t xFrequency_2 = (FFT_MIN_CYCLE * portTICK_PERIOD_MS) / 2; for(;;) { + TickType_t xLastWakeTime = xTaskGetTickCount(); delay(1); // DO NOT DELETE THIS LINE! It is needed to give the IDLE(0) task enough time and to keep the watchdog happy. // taskYIELD(), yield(), vTaskDelay() and esp_task_wdt_feed() didn't seem to work. @@ -168,6 +169,9 @@ void FFTcode(void * parameter) continue; } + vTaskDelayUntil( &xLastWakeTime, xFrequency); // release CPU, and let I2S fill its buffers + //vTaskDelayUntil( &xLastWakeTime, xFrequency_2); // release CPU, and let I2S fill its buffers + #ifdef WLED_DEBUG uint64_t start = esp_timer_get_time(); #endif @@ -275,14 +279,13 @@ void FFTcode(void * parameter) } #ifdef WLED_DEBUG - //fftTime = ((millis() - start)*3 + fftTime*7)/10; if (start < esp_timer_get_time()) { // filter out overflows unsigned long fftTimeInMillis = ((esp_timer_get_time() - start) +500ULL) / 1000ULL; // "+500" to ensure proper rounding fftTime = (fftTimeInMillis*3 + fftTime*7)/10; // smooth } #endif - vTaskDelayUntil( &xLastWakeTime, xFrequency); // release CPU, by waiting until FFT_MIN_CYCLE is over + //vTaskDelayUntil( &xLastWakeTime, xFrequency_2); // release CPU, by waiting until FFT_MIN_CYCLE is over // release second sample to volume reactive effects. // Releasing a second sample now effectively doubles the "sample rate" micDataReal = maxSample2; @@ -645,8 +648,8 @@ class AudioReactive : public Usermod { samplePeak = false; udpSamplePeak = false; } - //if (userVar1 == 0) samplePeak = 0; + // Poor man's beat detection by seeing if sample > Average + some value. // if (sample > (sampleAvg + maxVol) && millis() > (timeOfPeak + 200)) { if ((fftBin[binNum] > maxVol) && (millis() > (timeOfPeak + 100))) { // This goes through ALL of the 255 bins From 5e6532959b2bf990f71b0ea3f762c01265569919 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Wed, 10 Aug 2022 17:18:43 +0200 Subject: [PATCH 09/19] AudioSource improvements (work in progress) -new methods: getType(), isInitailized(), postProcessSample() - allow users to compile for RIGHT audio channel (-D I2S_USE_RIGHT_CHANNEL) - better handling in case audio input driver failed to initialize - removed some unneeded code and unneeded parameters --- usermods/audioreactive/audio_reactive.h | 50 +++++++++------- usermods/audioreactive/audio_source.h | 76 +++++++++++++++---------- 2 files changed, 74 insertions(+), 52 deletions(-) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index 35606324e..fb8cafb3f 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -728,11 +728,11 @@ class AudioReactive : public Usermod { } - void receiveAudioData() + bool receiveAudioData() // check & process new data. return TRUE in case that new audio data was received. { - if (!udpSyncConnected) return; + if (!udpSyncConnected) return false; //DEBUGSR_PRINTLN("Checking for UDP Microphone Packet"); - + bool haveFreshData = false; size_t packetSize = fftUdp.parsePacket(); if (packetSize > 5) { //DEBUGSR_PRINTLN("Received UDP Sync Packet"); @@ -774,8 +774,10 @@ class AudioReactive : public Usermod { FFT_Magnitude = my_magnitude; FFT_MajorPeak = receivedPacket->FFT_MajorPeak; //DEBUGSR_PRINTLN("Finished parsing UDP Sync Packet"); + haveFreshData = true; } } + return haveFreshData; } @@ -822,50 +824,54 @@ class AudioReactive : public Usermod { delay(100); // Give that poor microphone some time to setup. switch (dmType) { case 1: - DEBUGSR_PRINTLN(F("AS: Generic I2S Microphone.")); - audioSource = new I2SSource(SAMPLE_RATE, BLOCK_SIZE, 0, 0xFFFFFFFF); + DEBUGSR_PRINT(F("AR: Generic I2S Microphone - ")); DEBUGSR_PRINTLN(F(I2S_MIC_CHANNEL_TEXT)); + audioSource = new I2SSource(SAMPLE_RATE, BLOCK_SIZE); delay(100); if (audioSource) audioSource->initialize(i2swsPin, i2ssdPin, i2sckPin); break; case 2: - DEBUGSR_PRINTLN(F("AS: ES7243 Microphone.")); - audioSource = new ES7243(SAMPLE_RATE, BLOCK_SIZE, 0, 0xFFFFFFFF); + DEBUGSR_PRINTLN(F("AR: ES7243 Microphone (right channel only).")); + audioSource = new ES7243(SAMPLE_RATE, BLOCK_SIZE); delay(100); if (audioSource) audioSource->initialize(sdaPin, sclPin, i2swsPin, i2ssdPin, i2sckPin, mclkPin); break; case 3: - DEBUGSR_PRINTLN(F("AS: SPH0645 Microphone")); - audioSource = new SPH0654(SAMPLE_RATE, BLOCK_SIZE, 0, 0xFFFFFFFF); + DEBUGSR_PRINT(F("AR: SPH0645 Microphone - ")); DEBUGSR_PRINTLN(F(I2S_MIC_CHANNEL_TEXT)); + audioSource = new SPH0654(SAMPLE_RATE, BLOCK_SIZE); delay(100); audioSource->initialize(i2swsPin, i2ssdPin, i2sckPin); break; case 4: - DEBUGSR_PRINTLN(F("AS: Generic I2S Microphone with Master Clock")); - audioSource = new I2SSource(SAMPLE_RATE, BLOCK_SIZE, 0, 0xFFFFFFFF); + DEBUGSR_PRINT(F("AR: Generic I2S Microphone with Master Clock - ")); DEBUGSR_PRINTLN(F(I2S_MIC_CHANNEL_TEXT)); + audioSource = new I2SSource(SAMPLE_RATE, BLOCK_SIZE); delay(100); if (audioSource) audioSource->initialize(i2swsPin, i2ssdPin, i2sckPin, mclkPin); break; case 5: - DEBUGSR_PRINTLN(F("AS: I2S PDM Microphone")); - audioSource = new I2SSource(SAMPLE_RATE, BLOCK_SIZE, 0, 0xFFFFFFFF); + DEBUGSR_PRINT(F("AR: I2S PDM Microphone - ")); DEBUGSR_PRINTLN(F(I2S_MIC_CHANNEL_TEXT)); + audioSource = new I2SSource(SAMPLE_RATE, BLOCK_SIZE); delay(100); if (audioSource) audioSource->initialize(i2swsPin, i2ssdPin); break; case 0: default: - DEBUGSR_PRINTLN(F("AS: Analog Microphone.")); - // we don't do the down-shift by 16bit any more - //audioSource = new I2SAdcSource(SAMPLE_RATE, BLOCK_SIZE, -4, 0x0FFF); // request upscaling to 16bit - still produces too much noise - audioSource = new I2SAdcSource(SAMPLE_RATE, BLOCK_SIZE, 0, 0x0FFF); // keep at 12bit - less noise + DEBUGSR_PRINTLN(F("AR: Analog Microphone (left channel only).")); + audioSource = new I2SAdcSource(SAMPLE_RATE, BLOCK_SIZE); delay(100); if (audioSource) audioSource->initialize(audioPin); break; } - delay(250); // give mictophone enough time to initialise + delay(250); // give microphone enough time to initialise - if (!audioSource) enabled = false; // audio failed to initialise - if (enabled) onUpdateBegin(false); // create FFT task - if (enabled) disableSoundProcessing = false; + if (!audioSource) enabled = false; // audio failed to initialise + if (enabled) onUpdateBegin(false); // create FFT task + if (FFT_Task == nullptr) enabled = false; // FFT task creation failed + if (enabled) disableSoundProcessing = false; // all good - enable audio processing + + if((!audioSource) || (!audioSource->isInitialized())) { // audio source failed to initialize. Still stay "enabled", as there might be input arriving via UDP Sound Sync + DEBUGSR_PRINTLN(F("AR: Failed to initialize sound input driver. Please check input PIN settings.")); + disableSoundProcessing = true; + } initDone = true; } @@ -1019,7 +1025,7 @@ class AudioReactive : public Usermod { // Begin UDP Microphone Sync if ((audioSyncEnabled & 0x02) && millis() - lastTime > delayMs) { // Only run the audio listener code if we're in Receive mode - receiveAudioData(); + (void) receiveAudioData(); // ToDo: use return value for something meaningfull lastTime = millis(); } diff --git a/usermods/audioreactive/audio_source.h b/usermods/audioreactive/audio_source.h index 168b3aca3..dc46f15fa 100644 --- a/usermods/audioreactive/audio_source.h +++ b/usermods/audioreactive/audio_source.h @@ -18,19 +18,36 @@ Until this configuration is moved to the webinterface */ +// if you have problems to get your microphone work on the left channel, uncomment the following line +//#define I2S_USE_RIGHT_CHANNEL // (experimental) define this to use right channel (digital mics only) +#ifdef I2S_USE_RIGHT_CHANNEL +#define I2S_MIC_CHANNEL I2S_CHANNEL_FMT_ONLY_RIGHT +#define I2S_MIC_CHANNEL_TEXT "right channel only." +#else +#define I2S_MIC_CHANNEL I2S_CHANNEL_FMT_ONLY_LEFT +#define I2S_MIC_CHANNEL_TEXT "left channel only." +#endif + +// Uncomment the line below to utilize ADC1 _exclusively_ for I2S sound input. +// benefit: analog mic inputs will be sampled contiously -> better response times and less "glitches" +// WARNING: this option WILL lock-up your device in case that any other analogRead() operation is performed; +// for example if you want to read "analog buttons" +//#define I2S_GRAB_ADC1_COMPLETELY // (experimental) continously sample analog ADC microphone. WARNING will cause analogRead() lock-up + // data type requested from the I2S driver - currently we always use 32bit //#define I2S_USE_16BIT_SAMPLES // (experimental) define this to request 16bit - more efficient but possibly less compatible #ifdef I2S_USE_16BIT_SAMPLES #define I2S_SAMPLE_RESOLUTION I2S_BITS_PER_SAMPLE_16BIT #define I2S_datatype int16_t +#define I2S_unsigned_datatype uint16_t #undef I2S_SAMPLE_DOWNSCALE_TO_16BIT #else #define I2S_SAMPLE_RESOLUTION I2S_BITS_PER_SAMPLE_32BIT #define I2S_datatype int32_t +#define I2S_unsigned_datatype uint32_t #define I2S_SAMPLE_DOWNSCALE_TO_16BIT #endif - /* Interface class AudioSource serves as base class for all microphone types This enables accessing all microphones with one single interface @@ -65,15 +82,23 @@ class AudioSource { /* Get an up-to-date sample without DC offset */ virtual int getSampleWithoutDCOffset() { return _sampleNoDCOffset; }; + /* check if the audio source driver was initialized successfully */ + virtual bool isInitialized(void) {return(_initialized);} + + /* identify Audiosource type - I2S-ADC or I2S-digital */ + typedef enum{Type_unknown=0, Type_I2SAdc=1, Type_I2SDigital=2} AudioSourceType; + virtual AudioSourceType getType(void) {return(Type_I2SDigital);} // default is "I2S digital source" - ADC type overrides this method + protected: + /* Post-process audio sample - currently on needed for I2SAdcSource*/ + virtual I2S_datatype postProcessSample(I2S_datatype sample_in) {return(sample_in);} // default method can be overriden by instances (ADC) that need sample postprocessing + // Private constructor, to make sure it is not callable except from derived classes - AudioSource(int sampleRate, int blockSize, int16_t lshift, uint32_t mask) : + AudioSource(int sampleRate, int blockSize) : _sampleRate(sampleRate), _blockSize(blockSize), _sampleNoDCOffset(0), _dcOffset(0.0f), - _shift(lshift), - _mask(mask), _initialized(false) {}; @@ -81,8 +106,6 @@ class AudioSource { int _blockSize; // I2S block size volatile int _sampleNoDCOffset; // Up-to-date sample without DCOffset float _dcOffset; // Rolling average DC offset - int16_t _shift; // Shift obtained samples to the right (positive) or left(negative) by this amount - uint32_t _mask; // Bitmask for sample data after shifting. Bitmask 0X0FFF means that we need to convert 12bit ADC samples from unsigned to signed bool _initialized; // Gets set to true if initialization is successful }; @@ -91,13 +114,13 @@ class AudioSource { */ class I2SSource : public AudioSource { public: - I2SSource(int sampleRate, int blockSize, int16_t lshift, uint32_t mask) : - AudioSource(sampleRate, blockSize, lshift, mask) { + I2SSource(int sampleRate, int blockSize) : + AudioSource(sampleRate, blockSize) { _config = { .mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX), .sample_rate = _sampleRate, .bits_per_sample = I2S_SAMPLE_RESOLUTION, - .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, + .channel_format = I2S_MIC_CHANNEL, #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 2, 0) .communication_format = i2s_comm_format_t(I2S_COMM_FORMAT_STAND_I2S), #else @@ -193,26 +216,16 @@ class I2SSource : public AudioSource { // Store samples in sample buffer and update DC offset for (int i = 0; i < num_samples; i++) { - // pre-shift samples down to 16bit -#ifdef I2S_SAMPLE_DOWNSCALE_TO_16BIT - if (_shift != 0) - newSamples[i] >>= 16; -#endif + newSamples[i] = postProcessSample(newSamples[i]); // perform postprocessing (needed for ADC samples) + float currSample = 0.0f; - if(_shift > 0) - currSample = (float) (newSamples[i] >> _shift); - else { - if(_shift < 0) - currSample = (float) (newSamples[i] << (- _shift)); // need to "pump up" 12bit ADC to full 16bit as delivered by other digital mics - else #ifdef I2S_SAMPLE_DOWNSCALE_TO_16BIT - currSample = (float) newSamples[i] / 65536.0f; // _shift == 0 -> use the chance to keep lower 16bits + currSample = (float) newSamples[i] / 65536.0f; // 32bit input -> 16bit; keeping lower 16bits as decimal places #else - currSample = (float) newSamples[i]; + currSample = (float) newSamples[i]; // 16bit input -> use as-is #endif - } buffer[i] = currSample; - _dcOffset = ((_dcOffset * 31) + currSample) / 32; + _dcOffset = ((_dcOffset * 31.0f) + currSample) / 32.0f; } // Update no-DC sample @@ -275,8 +288,8 @@ class ES7243 : public I2SSource { } public: - ES7243(int sampleRate, int blockSize, int16_t lshift, uint32_t mask) : - I2SSource(sampleRate, blockSize, lshift, mask) { + ES7243(int sampleRate, int blockSize) : + I2SSource(sampleRate, blockSize) { _config.channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT; }; @@ -314,8 +327,8 @@ public: */ class I2SAdcSource : public I2SSource { public: - I2SAdcSource(int sampleRate, int blockSize, int16_t lshift, uint32_t mask) : - I2SSource(sampleRate, blockSize, lshift, mask) { + I2SAdcSource(int sampleRate, int blockSize) : + I2SSource(sampleRate, blockSize) { _config = { .mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_ADC_BUILT_IN), .sample_rate = _sampleRate, @@ -332,6 +345,9 @@ class I2SAdcSource : public I2SSource { }; } + /* identify Audiosource type - I2S-ADC*/ + AudioSourceType getType(void) {return(Type_I2SAdc);} + void initialize(int8_t audioPin, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE) { if(!pinManager.allocatePin(audioPin, false, PinOwner::UM_Audioreactive)) { return; @@ -432,8 +448,8 @@ class I2SAdcSource : public I2SSource { */ class SPH0654 : public I2SSource { public: - SPH0654(int sampleRate, int blockSize, int16_t lshift, uint32_t mask) : - I2SSource(sampleRate, blockSize, lshift, mask) + SPH0654(int sampleRate, int blockSize) : + I2SSource(sampleRate, blockSize) {} void initialize(uint8_t i2swsPin, uint8_t i2ssdPin, uint8_t i2sckPin, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE) { From ecce3243debb9cc32e7cce73bff611ac7b689f31 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Wed, 10 Aug 2022 18:14:28 +0200 Subject: [PATCH 10/19] save 1KB of RAM save one KB (4*256 bytes) by not storing the "upper half" of FFT results. Only the lower half has interesting results. --- usermods/audioreactive/audio_reactive.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index fb8cafb3f..21153abdc 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -98,6 +98,7 @@ static float multAgc = 1.0f; // sample * multAgc = sampleAgc. // FFT Variables constexpr uint16_t samplesFFT = 512; // Samples in an FFT batch - This value MUST ALWAYS be a power of 2 +constexpr uint16_t samplesFFT_2 = 256; // meaningfull part of FFT results - nly the "lower half" contains usefull information. static float FFT_MajorPeak = 0.0f; static float FFT_Magnitude = 0.0f; @@ -105,7 +106,7 @@ static float FFT_Magnitude = 0.0f; // These are the input and output vectors. Input vectors receive computed results from FFT. static float vReal[samplesFFT]; static float vImag[samplesFFT]; -static float fftBin[samplesFFT]; +static float fftBin[samplesFFT_2]; #ifdef UM_AUDIOREACTIVE_USE_NEW_FFT static float windowWeighingFactors[samplesFFT]; @@ -231,7 +232,7 @@ void FFTcode(void * parameter) FFT.MajorPeak(&FFT_MajorPeak, &FFT_Magnitude); // let the effects know which freq was most dominant #endif - for (int i = 0; i < samplesFFT; i++) { // Values for bins 0 and 1 are WAY too large. Might as well start at 3. + for (int i = 0; i < samplesFFT_2; i++) { // Values for bins 0 and 1 are WAY too large. Might as well start at 3. float t = fabs(vReal[i]); // just to be sure - values in fft bins should be positive any way fftBin[i] = t / 16.0f; // Reduce magnitude. Want end result to be linear and ~4096 max. } // for() @@ -652,7 +653,7 @@ class AudioReactive : public Usermod { // Poor man's beat detection by seeing if sample > Average + some value. // if (sample > (sampleAvg + maxVol) && millis() > (timeOfPeak + 200)) { - if ((fftBin[binNum] > maxVol) && (millis() > (timeOfPeak + 100))) { // This goes through ALL of the 255 bins + if ((maxVol > 0) && (binNum > 1) && (fftBin[binNum] > maxVol) && (millis() > (timeOfPeak + 100))) { // 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. samplePeak = true; timeOfPeak = millis(); From 968721a515cc475bd89ff09e410d88c7309b41b9 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 14 Aug 2022 13:58:07 +0200 Subject: [PATCH 11/19] some audio processing improvements and bugfixes from SR WLED - smoothing FFTResult (don't have a matrix to test) - UDP sound sync improvements - some bugfixes from SR WLED - button.cpp: avoid starvation: strip.isUpdating() can be true for a long time. work in progress - still needs testing!! --- usermods/audioreactive/audio_reactive.h | 72 ++++++++++++++++--------- usermods/audioreactive/audio_source.h | 12 +++-- wled00/FX.cpp | 3 +- wled00/button.cpp | 5 +- 4 files changed, 62 insertions(+), 30 deletions(-) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index 41947ca88..c48501090 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -23,10 +23,13 @@ // Comment/Uncomment to toggle usb serial debugging // #define MIC_LOGGER // MIC sampling & sound input debugging (serial plotter) // #define FFT_SAMPLING_LOG // FFT result debugging -// #define SR_DEBUG // generic SR DEBUG messages +// #define SR_DEBUG // generic SR DEBUG messages (including MIC_LOGGER) +// #define NO_MIC_LOGGER // exclude MIC_LOGGER from SR_DEBUG // hackers corner -//#define SOUND_DYNAMICS_LIMITER // experimental: define to enable a dynamics limiter that avoids "sudden flashes" at onsets. Makes some effects look more "smooth and fluent" +#if !defined(SOUND_DYNAMICS_LIMITER) && !defined(NO_SOUND_DYNAMICS_LIMITER) +#define SOUND_DYNAMICS_LIMITER // experimental: define to enable a dynamics limiter that avoids "sudden flashes" at onsets. Makes some effects look more "smooth and fluent" +#endif #ifdef SR_DEBUG #define DEBUGSR_PRINT(x) Serial.print(x) @@ -60,6 +63,10 @@ static uint8_t sampleGain = 60; // sample gain (config value) static uint8_t soundAgc = 0; // Automagic gain control: 0 - none, 1 - normal, 2 - vivid, 3 - lazy (config value) static uint8_t audioSyncEnabled = 0; // bit field: bit 0 - send, bit 1 - receive (config value) +// user settable parameters for limitSoundDynamics() +static int attackTime = 80; // int: attack time in milliseconds. Default 0.1sec +static int decayTime = 1400; // int: decay time in milliseconds. Default 1.4sec + // // AGC presets // Note: in C++, "const" implies "static" - no need to explicitly declare everything as "static const" @@ -98,7 +105,7 @@ static float multAgc = 1.0f; // sample * multAgc = sampleAgc. // FFT Variables constexpr uint16_t samplesFFT = 512; // Samples in an FFT batch - This value MUST ALWAYS be a power of 2 -constexpr uint16_t samplesFFT_2 = 256; // meaningfull part of FFT results - nly the "lower half" contains usefull information. +constexpr uint16_t samplesFFT_2 = 256; // meaningfull part of FFT results - only the "lower half" contains useful information. static float FFT_MajorPeak = 0.0f; static float FFT_Magnitude = 0.0f; @@ -274,9 +281,12 @@ void FFTcode(void * parameter) // Manual linear adjustment of gain using sampleGain adjustment for different input types. fftCalc[i] *= soundAgc ? multAgc : ((float)sampleGain/40.0f * (float)inputLevel/128.0f + 1.0f/16.0f); //with inputLevel adjustment + // smooth results + //fftAvg[i] = fftCalc[i]*0.05f + 0.95f*fftAvg[i]; // will need approx 10 cycles (250ms) for converging against fftCalc[i] + fftAvg[i] = fftCalc[i] *0.1f + 0.9f*fftAvg[i]; // will need approx 5 cycles (125ms) for converging against fftCalc[i] // Now, let's dump it all into fftResult. Need to do this, otherwise other routines might grab fftResult values prematurely. - fftResult[i] = constrain((int)fftCalc[i], 0, 254); - fftAvg[i] = (float)fftResult[i]*0.05f + 0.95f*fftAvg[i]; + //fftResult[i] = constrain((int)fftCalc[i], 0, 254); + fftResult[i] = constrain((int)fftAvg[i], 0, 254); } #ifdef WLED_DEBUG @@ -602,10 +612,13 @@ class AudioReactive : public Usermod { // this is the minimal code for reading analog mic input on 8266. // warning!! Absolutely experimental code. Audio on 8266 is still not working. Expects a million follow-on problems. static unsigned long lastAnalogTime = 0; + static float lastAnalogValue = 0.0f; if (millis() - lastAnalogTime > 20) { micDataReal = analogRead(A0); // read one sample with 10bit resolution. This is a dirty hack, supporting volumereactive effects only. lastAnalogTime = millis(); - } + lastAnalogValue = micDataReal; + yield(); + } else micDataReal = lastAnalogValue; micIn = int(micDataReal); #endif #endif @@ -618,6 +631,7 @@ class AudioReactive : public Usermod { float micInNoDC = fabs(micDataReal - micLev); expAdjF = (weighting * micInNoDC + (1.0-weighting) * expAdjF); expAdjF = (expAdjF <= soundSquelch) ? 0: expAdjF; // simple noise gate + if ((soundSquelch == 0) && (expAdjF < 0.25f)) expAdjF = 0; // do something meaningfull when "squelch = 0" expAdjF = fabsf(expAdjF); // Now (!) take the absolute value tmpSample = expAdjF; @@ -664,14 +678,9 @@ class AudioReactive : public Usermod { /* Limits the dynamics of volumeSmth (= sampleAvg or sampleAgc). - * It does not affect FFTResult[] or volumeRaw ( = sample or rawSampleAgc) + * does not affect FFTResult[] or volumeRaw ( = sample or rawSampleAgc) */ // effects: Gravimeter, Gravcenter, Gravcentric, Noisefire, Plasmoid, Freqpixels, Freqwave, Gravfreq, (2D Swirl, 2D Waverly) - // experimental, as it still has side-effects on AGC - AGC detects "silence" to late (due to long decay time) and ditches up the gain multiplier. - // values below will be made user-configurable later - const float attackTime = 200; // attack time -> 0.2sec - const float decayTime = 2800; // decay time -> 2.8sec - void limitSampleDynamics(void) { #ifdef SOUND_DYNAMICS_LIMITER const float bigChange = 196; // just a representative number - a large, expected sample value @@ -681,8 +690,8 @@ class AudioReactive : public Usermod { if ((attackTime > 0) && (decayTime > 0)) { // only change volume if user has defined attack>0 and decay>0 long delta_time = millis() - last_time; delta_time = constrain(delta_time , 1, 1000); // below 1ms -> 1ms; above 1sec -> sily lil hick-up - float maxAttack = bigChange * float(delta_time) / attackTime; - float maxDecay = - bigChange * float(delta_time) / decayTime; + float maxAttack = bigChange * float(delta_time) / float(attackTime); + float maxDecay = - bigChange * float(delta_time) / float(decayTime); float deltaSample = volumeSmth - last_volumeSmth; if (deltaSample > maxAttack) deltaSample = maxAttack; @@ -704,8 +713,11 @@ class AudioReactive : public Usermod { audioSyncPacket transmitData; strncpy_P(transmitData.header, PSTR(UDP_SYNC_HEADER), 6); - transmitData.sampleRaw = volumeRaw; - transmitData.sampleSmth = volumeSmth; + //transmitData.sampleRaw = volumeRaw; + //transmitData.sampleSmth = volumeSmth; + // transmit samples that were not modified by limitSampleDynamics() + transmitData.sampleRaw = (soundAgc) ? rawSampleAgc: sampleRaw; + transmitData.sampleSmth = (soundAgc) ? sampleAgc : sampleAvg; transmitData.samplePeak = udpSamplePeak ? 1:0; udpSamplePeak = false; // Reset udpSamplePeak after we've transmitted it transmitData.reserved1 = 0; @@ -744,9 +756,11 @@ class AudioReactive : public Usermod { if (packetSize == sizeof(audioSyncPacket) && !(isValidUdpSyncVersion((const char *)fftBuff))) { audioSyncPacket *receivedPacket = reinterpret_cast(fftBuff); + // update samples for effects volumeSmth = receivedPacket->sampleSmth; volumeRaw = receivedPacket->sampleRaw; + // update internal samples sampleRaw = volumeRaw; sampleAvg = volumeSmth; rawSampleAgc = volumeRaw; @@ -945,6 +959,7 @@ class AudioReactive : public Usermod { if (audioSyncEnabled & 0x02) disableSoundProcessing = true; // make sure everything is disabled IF in audio Receive mode if (audioSyncEnabled & 0x01) disableSoundProcessing = false; // keep running audio IF we're in audio Transmit mode + // Only run the sampling code IF we're not in Receive mode or realtime mode if (!(audioSyncEnabled & 0x02) && !disableSoundProcessing) { bool agcEffect = false; @@ -981,9 +996,8 @@ class AudioReactive : public Usermod { limitSampleDynamics(); // optional - makes volumeSmth very smooth and fluent - // update UI + // update WebServer UI uint8_t knownMode = strip.getFirstSelectedSeg().mode; // 1st selected segment is more appropriate than main segment - if (lastMode != knownMode) { // only execute if mode changes char lineBuffer[4]; extractModeName(knownMode, JSON_mode_names, lineBuffer, 3); // use of JSON_mode_names is deprecated, use nullptr @@ -1024,10 +1038,19 @@ class AudioReactive : public Usermod { } } - // Begin UDP Microphone Sync - if ((audioSyncEnabled & 0x02) && millis() - lastTime > delayMs) { // Only run the audio listener code if we're in Receive mode - (void) receiveAudioData(); // ToDo: use return value for something meaningfull - lastTime = millis(); + + // UDP Microphone Sync - receive mode + if ((audioSyncEnabled & 0x02) && udpSyncConnected) { + // Only run the audio listener code if we're in Receive mode + static float syncVolumeSmth = 0; + bool have_new_sample = false; + if (millis() - lastTime > delayMs) { + have_new_sample = receiveAudioData(); + lastTime = millis(); + } + if (have_new_sample) syncVolumeSmth = volumeSmth; // remember received sample + else volumeSmth = syncVolumeSmth; // restore originally received sample for next run of dynamics limiter + limitSampleDynamics(); // run dynamics limiter on received volumeSmth, to hide jumps and hickups } #if defined(MIC_LOGGER) || defined(MIC_SAMPLING_LOG) || defined(FFT_SAMPLING_LOG) @@ -1036,12 +1059,13 @@ class AudioReactive : public Usermod { } #endif - if ((audioSyncEnabled & 0x01) && millis() - lastTime > 20) { // Only run the transmit code IF we're in Transmit mode + //UDP Microphone Sync - transmit mode + if ((audioSyncEnabled & 0x01) && millis() - lastTime > 20) { + // Only run the transmit code IF we're in Transmit mode transmitAudioData(); lastTime = millis(); } - //limitSampleDynamics(); // If done as the last step, it will also affect audio received by UDP sound sync. Problem: effects might see inconsistent intermediate values and start flickering :-( } diff --git a/usermods/audioreactive/audio_source.h b/usermods/audioreactive/audio_source.h index dc46f15fa..5b3b01ea5 100644 --- a/usermods/audioreactive/audio_source.h +++ b/usermods/audioreactive/audio_source.h @@ -339,9 +339,12 @@ class I2SAdcSource : public I2SSource { #else .communication_format = i2s_comm_format_t(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB), #endif - .intr_alloc_flags = ESP_INTR_FLAG_LEVEL2, + .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, .dma_buf_count = 8, - .dma_buf_len = _blockSize + .dma_buf_len = _blockSize, + .use_apll = false, + .tx_desc_auto_clear = false, + .fixed_mclk = 0 }; } @@ -350,6 +353,7 @@ class I2SAdcSource : public I2SSource { void initialize(int8_t audioPin, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE) { if(!pinManager.allocatePin(audioPin, false, PinOwner::UM_Audioreactive)) { + DEBUGSR_PRINTF("failed to allocate GPIO for audio analog input: %d\n", audioPin); return; } _audioPin = audioPin; @@ -376,7 +380,7 @@ class I2SAdcSource : public I2SSource { DEBUGSR_PRINTF("Failed to set i2s adc mode: %d\n", err); return; } - + // adc1_config_channel_atten(adc1_channel_t(channel), ADC_ATTEN_DB_11)); //see https://github.com/espressif/arduino-esp32/blob/master/libraries/ESP32/examples/I2S/HiFreq_ADC/HiFreq_ADC.ino #if defined(ARDUINO_ARCH_ESP32) // according to docs from espressif, the ADC needs to be started explicitly // fingers crossed @@ -408,7 +412,7 @@ class I2SAdcSource : public I2SSource { #if !defined(ARDUINO_ARCH_ESP32) // old code - works for me without enable/disable, at least on ESP32. - err = i2s_adc_disable(I2S_NUM_0); + err = i2s_adc_disable(I2S_NUM_0); //i2s_adc_disable() may cause crash with IDF 4.4 (https://github.com/espressif/arduino-esp32/issues/6832) //err = i2s_stop(I2S_NUM_0); if (err != ESP_OK) { DEBUGSR_PRINTF("Failed to disable i2s adc: %d\n", err); diff --git a/wled00/FX.cpp b/wled00/FX.cpp index c58dcdec3..942f93905 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -6711,7 +6711,8 @@ uint16_t mode_freqmap(void) { // Map FFT_MajorPeak to SEGLEN. SEGMENT.fade_out(SEGMENT.speed); - uint16_t locn = (log10f((float)FFT_MajorPeak) - 1.78f) * (float)SEGLEN/(3.71f-1.78f); // log10 frequency range is from 1.78 to 3.71. Let's scale to SEGLEN. + int locn = (log10f((float)FFT_MajorPeak) - 1.78f) * (float)SEGLEN/(3.71f-1.78f); // log10 frequency range is from 1.78 to 3.71. Let's scale to SEGLEN. + if (locn < 1) locn = 0; // avoid underflow if (locn >=SEGLEN) locn = SEGLEN-1; uint16_t pixCol = (log10f(FFT_MajorPeak) - 1.78f) * 255.0f/(3.71f-1.78f); // Scale log10 of frequency values to the 255 colour index. diff --git a/wled00/button.cpp b/wled00/button.cpp index 2bd093ceb..b472f9275 100644 --- a/wled00/button.cpp +++ b/wled00/button.cpp @@ -216,10 +216,13 @@ void handleAnalog(uint8_t b) void handleButton() { static unsigned long lastRead = 0UL; + static unsigned long lastRun = 0UL; bool analog = false; unsigned long now = millis(); - if (strip.isUpdating()) return; // don't interfere with strip updates. Our button will still be there in 1ms (next cycle) + //if (strip.isUpdating()) return; // don't interfere with strip updates. Our button will still be there in 1ms (next cycle) + if (strip.isUpdating() && (millis() - lastRun < 400)) return; // be niced, but avoid button starvation + lastRun = millis(); for (uint8_t b=0; b Date: Sun, 14 Aug 2022 14:38:27 +0200 Subject: [PATCH 12/19] small improvement for limitSampleDynamics support the case when only attackTime XOR decayTime is defined --- usermods/audioreactive/audio_reactive.h | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index c48501090..4cb0f6923 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -683,21 +683,24 @@ class AudioReactive : public Usermod { // effects: Gravimeter, Gravcenter, Gravcentric, Noisefire, Plasmoid, Freqpixels, Freqwave, Gravfreq, (2D Swirl, 2D Waverly) void limitSampleDynamics(void) { #ifdef SOUND_DYNAMICS_LIMITER - const float bigChange = 196; // just a representative number - a large, expected sample value + const float bigChange = 196; // just a representative number - a large, expected sample value static unsigned long last_time = 0; static float last_volumeSmth = 0.0f; - if ((attackTime > 0) && (decayTime > 0)) { // only change volume if user has defined attack>0 and decay>0 - long delta_time = millis() - last_time; - delta_time = constrain(delta_time , 1, 1000); // below 1ms -> 1ms; above 1sec -> sily lil hick-up - float maxAttack = bigChange * float(delta_time) / float(attackTime); - float maxDecay = - bigChange * float(delta_time) / float(decayTime); + long delta_time = millis() - last_time; + delta_time = constrain(delta_time , 1, 1000); // below 1ms -> 1ms; above 1sec -> sily lil hick-up + float deltaSample = volumeSmth - last_volumeSmth; - float deltaSample = volumeSmth - last_volumeSmth; + if (attackTime > 0) { // user has defined attack time > 0 + float maxAttack = bigChange * float(delta_time) / float(attackTime); if (deltaSample > maxAttack) deltaSample = maxAttack; - if (deltaSample < maxDecay) deltaSample = maxDecay; - volumeSmth = last_volumeSmth + deltaSample; } + if (decayTime > 0) { // user has defined decay time > 0 + float maxDecay = - bigChange * float(delta_time) / float(decayTime); + if (deltaSample < maxDecay) deltaSample = maxDecay; + } + + volumeSmth = last_volumeSmth + deltaSample; last_volumeSmth = volumeSmth; last_time = millis(); From c6691564a51a7ce457660f66d2573d8561b71f0b Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Sun, 14 Aug 2022 14:47:03 +0200 Subject: [PATCH 13/19] removing dead code from getSamples() --- usermods/audioreactive/audio_source.h | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/usermods/audioreactive/audio_source.h b/usermods/audioreactive/audio_source.h index 5b3b01ea5..276bfd44b 100644 --- a/usermods/audioreactive/audio_source.h +++ b/usermods/audioreactive/audio_source.h @@ -79,9 +79,6 @@ class AudioSource { */ virtual void getSamples(float *buffer, uint16_t num_samples) = 0; - /* Get an up-to-date sample without DC offset */ - virtual int getSampleWithoutDCOffset() { return _sampleNoDCOffset; }; - /* check if the audio source driver was initialized successfully */ virtual bool isInitialized(void) {return(_initialized);} @@ -97,15 +94,11 @@ class AudioSource { AudioSource(int sampleRate, int blockSize) : _sampleRate(sampleRate), _blockSize(blockSize), - _sampleNoDCOffset(0), - _dcOffset(0.0f), _initialized(false) {}; int _sampleRate; // Microphone sampling rate int _blockSize; // I2S block size - volatile int _sampleNoDCOffset; // Up-to-date sample without DCOffset - float _dcOffset; // Rolling average DC offset bool _initialized; // Gets set to true if initialization is successful }; @@ -199,9 +192,6 @@ class I2SSource : public AudioSource { size_t bytes_read = 0; /* Counter variable to check if we actually got enough data */ I2S_datatype newSamples[num_samples]; /* Intermediary sample storage */ - // Reset dc offset - _dcOffset = 0.0f; - err = i2s_read(I2S_NUM_0, (void *)newSamples, sizeof(newSamples), &bytes_read, portMAX_DELAY); if (err != ESP_OK) { DEBUGSR_PRINTF("Failed to get samples: %d\n", err); @@ -216,8 +206,9 @@ class I2SSource : public AudioSource { // Store samples in sample buffer and update DC offset for (int i = 0; i < num_samples; i++) { - newSamples[i] = postProcessSample(newSamples[i]); // perform postprocessing (needed for ADC samples) + newSamples[i] = postProcessSample(newSamples[i]); // perform postprocessing (needed for ADC samples) + float currSample = 0.0f; #ifdef I2S_SAMPLE_DOWNSCALE_TO_16BIT currSample = (float) newSamples[i] / 65536.0f; // 32bit input -> 16bit; keeping lower 16bits as decimal places @@ -225,11 +216,7 @@ class I2SSource : public AudioSource { currSample = (float) newSamples[i]; // 16bit input -> use as-is #endif buffer[i] = currSample; - _dcOffset = ((_dcOffset * 31.0f) + currSample) / 32.0f; } - - // Update no-DC sample - _sampleNoDCOffset = buffer[num_samples - 1] - _dcOffset; } } From 5a4713950cf9884756f318fbad1a85da47f15f63 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Sun, 14 Aug 2022 16:17:34 +0200 Subject: [PATCH 14/19] improved ADCsample processing (from SR WLED) improved ADCsample processing, including replacement of "rogue" samples from other channels (this happens at least once in 5 seconds !!). It compiles, don't ship it yet - needs more testing. --- usermods/audioreactive/audio_source.h | 125 +++++++++++++++++++------- 1 file changed, 91 insertions(+), 34 deletions(-) diff --git a/usermods/audioreactive/audio_source.h b/usermods/audioreactive/audio_source.h index 276bfd44b..090b46e6a 100644 --- a/usermods/audioreactive/audio_source.h +++ b/usermods/audioreactive/audio_source.h @@ -339,6 +339,7 @@ class I2SAdcSource : public I2SSource { AudioSourceType getType(void) {return(Type_I2SAdc);} void initialize(int8_t audioPin, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE) { + _myADCchannel = 0x0F; if(!pinManager.allocatePin(audioPin, false, PinOwner::UM_Audioreactive)) { DEBUGSR_PRINTF("failed to allocate GPIO for audio analog input: %d\n", audioPin); return; @@ -352,6 +353,7 @@ class I2SAdcSource : public I2SSource { return; } else { adc_gpio_init(ADC_UNIT_1, adc_channel_t(channel)); + _myADCchannel = channel; } // Install Driver @@ -368,60 +370,114 @@ class I2SAdcSource : public I2SSource { return; } // adc1_config_channel_atten(adc1_channel_t(channel), ADC_ATTEN_DB_11)); //see https://github.com/espressif/arduino-esp32/blob/master/libraries/ESP32/examples/I2S/HiFreq_ADC/HiFreq_ADC.ino -#if defined(ARDUINO_ARCH_ESP32) + + #if defined(I2S_GRAB_ADC1_COMPLETELY) // according to docs from espressif, the ADC needs to be started explicitly // fingers crossed - err = i2s_adc_enable(I2S_NUM_0); - if (err != ESP_OK) { - DEBUGSR_PRINTF("Failed to enable i2s adc: %d\n", err); - //return; - } -#endif + err = i2s_adc_enable(I2S_NUM_0); + if (err != ESP_OK) { + DEBUGSR_PRINTF("Failed to enable i2s adc: %d\n", err); + //return; + } + #else + err = i2s_adc_disable(I2S_NUM_0); + //err = i2s_stop(I2S_NUM_0); + if (err != ESP_OK) { + DEBUGSR_PRINTF("Failed to initially disable i2s adc: %d\n", err); + } + #endif _initialized = true; } + + I2S_datatype postProcessSample(I2S_datatype sample_in) { + static I2S_datatype lastADCsample = 0; // last good sample + static unsigned int broken_samples_counter = 0; // number of consecutive broken (and fixed) ADC samples + I2S_datatype sample_out = 0; + + // bring sample down down to 16bit unsigned + I2S_unsigned_datatype rawData = * reinterpret_cast (&sample_in); // C++ acrobatics to get sample as "unsigned" + #ifndef I2S_USE_16BIT_SAMPLES + rawData = (rawData >> 16) & 0xFFFF; // scale input down from 32bit -> 16bit + I2S_datatype lastGoodSample = lastADCsample / 16384 ; // prepare "last good sample" accordingly (26bit-> 12bit with correct sign handling) + #else + rawData = rawData & 0xFFFF; // input is already in 16bit, just mask off possible junk + I2S_datatype lastGoodSample = lastADCsample * 4; // prepare "last good sample" accordingly (10bit-> 12bit) + #endif + + // decode ADC sample data fields + uint16_t the_channel = (rawData >> 12) & 0x000F; // upper 4 bit = ADC channel + uint16_t the_sample = rawData & 0x0FFF; // lower 12bit -> ADC sample (unsigned) + I2S_datatype finalSample = (int(the_sample) - 2048); // convert unsigned sample to signed (centered at 0); + + if ((the_channel != _myADCchannel) && (_myADCchannel != 0x0F)) { // 0x0F means "don't know what my channel is" + // fix bad sample + finalSample = lastGoodSample; // replace with last good ADC sample + broken_samples_counter ++; + if (broken_samples_counter > 256) _myADCchannel = 0x0F; // too many bad samples in a row -> disable sample corrections + //Serial.print("\n!ADC rogue sample 0x"); Serial.print(rawData, HEX); Serial.print("\tchannel:");Serial.println(the_channel); + } else broken_samples_counter = 0; // good sample - reset counter + + // back to original resolution + #ifndef I2S_USE_16BIT_SAMPLES + finalSample = finalSample << 16; // scale up from 16bit -> 32bit; + #endif + + finalSample = finalSample / 4; // mimic old analog driver behaviour (12bit -> 10bit) + sample_out = (3 * finalSample + lastADCsample) / 4; // apply low-pass filter (2-tap FIR) + //sample_out = (finalSample + lastADCsample) / 2; // apply stronger low-pass filter (2-tap FIR) + + lastADCsample = sample_out; // update ADC last sample + return(sample_out); + } + + void getSamples(float *buffer, uint16_t num_samples) { /* Enable ADC. This has to be enabled and disabled directly before and * after sampling, otherwise Wifi dies */ if (_initialized) { -#if !defined(ARDUINO_ARCH_ESP32) - // old code - works for me without enable/disable, at least on ESP32. - esp_err_t err = i2s_adc_enable(I2S_NUM_0); - //esp_err_t err = i2s_start(I2S_NUM_0); - if (err != ESP_OK) { - DEBUGSR_PRINTF("Failed to enable i2s adc: %d\n", err); - return; - } -#endif + #if !defined(I2S_GRAB_ADC1_COMPLETELY) + // old code - works for me without enable/disable, at least on ESP32. + //esp_err_t err = i2s_start(I2S_NUM_0); + esp_err_t err = i2s_adc_enable(I2S_NUM_0); + if (err != ESP_OK) { + DEBUGSR_PRINTF("Failed to enable i2s adc: %d\n", err); + return; + } + #endif + I2SSource::getSamples(buffer, num_samples); -#if !defined(ARDUINO_ARCH_ESP32) - // old code - works for me without enable/disable, at least on ESP32. - err = i2s_adc_disable(I2S_NUM_0); //i2s_adc_disable() may cause crash with IDF 4.4 (https://github.com/espressif/arduino-esp32/issues/6832) - //err = i2s_stop(I2S_NUM_0); - if (err != ESP_OK) { - DEBUGSR_PRINTF("Failed to disable i2s adc: %d\n", err); - return; - } -#endif + #if !defined(I2S_GRAB_ADC1_COMPLETELY) + // old code - works for me without enable/disable, at least on ESP32. + err = i2s_adc_disable(I2S_NUM_0); //i2s_adc_disable() may cause crash with IDF 4.4 (https://github.com/espressif/arduino-esp32/issues/6832) + //err = i2s_stop(I2S_NUM_0); + if (err != ESP_OK) { + DEBUGSR_PRINTF("Failed to disable i2s adc: %d\n", err); + return; + } + #endif } } void deinitialize() { pinManager.deallocatePin(_audioPin, PinOwner::UM_Audioreactive); _initialized = false; + _myADCchannel = 0x0F; + esp_err_t err; -#if defined(ARDUINO_ARCH_ESP32) - // according to docs from espressif, the ADC needs to be stopped explicitly - // fingers crossed - err = i2s_adc_disable(I2S_NUM_0); - if (err != ESP_OK) { - DEBUGSR_PRINTF("Failed to disable i2s adc: %d\n", err); - //return; - } -#endif + #if defined(I2S_GRAB_ADC1_COMPLETELY) + // according to docs from espressif, the ADC needs to be stopped explicitly + // fingers crossed + err = i2s_adc_disable(I2S_NUM_0); + if (err != ESP_OK) { + DEBUGSR_PRINTF("Failed to disable i2s adc: %d\n", err); + } + #endif + + i2s_stop(I2S_NUM_0); err = i2s_driver_uninstall(I2S_NUM_0); if (err != ESP_OK) { DEBUGSR_PRINTF("Failed to uninstall i2s driver: %d\n", err); @@ -431,6 +487,7 @@ class I2SAdcSource : public I2SSource { private: int8_t _audioPin; + int8_t _myADCchannel = 0x0F; // current ADC channel for analog input. 0x0F means "undefined" }; /* SPH0645 Microphone From 873e41dcfb397714d1ae337b2ddb359cc33b91b2 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Mon, 15 Aug 2022 14:28:51 +0200 Subject: [PATCH 15/19] AR: change smoothing of FFTResult FFTResult smoothing changed; rising edges will be very quick, falling down is slower. --- usermods/audioreactive/audio_reactive.h | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index 4cb0f6923..7147d1f46 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -41,9 +41,9 @@ #define DEBUGSR_PRINTF(x...) #endif // legacy support -#if defined(SR_DEBUG) && !defined(MIC_LOGGER) && !defined(NO_MIC_LOGGER) -#define MIC_LOGGER -#endif +// #if defined(SR_DEBUG) && !defined(MIC_LOGGER) && !defined(NO_MIC_LOGGER) +// #define MIC_LOGGER +// #endif #include "audio_source.h" @@ -281,12 +281,19 @@ void FFTcode(void * parameter) // Manual linear adjustment of gain using sampleGain adjustment for different input types. fftCalc[i] *= soundAgc ? multAgc : ((float)sampleGain/40.0f * (float)inputLevel/128.0f + 1.0f/16.0f); //with inputLevel adjustment - // smooth results - //fftAvg[i] = fftCalc[i]*0.05f + 0.95f*fftAvg[i]; // will need approx 10 cycles (250ms) for converging against fftCalc[i] - fftAvg[i] = fftCalc[i] *0.1f + 0.9f*fftAvg[i]; // will need approx 5 cycles (125ms) for converging against fftCalc[i] + // smooth results - rise fast, fall slower + if(fftCalc[i] > fftAvg[i]) // rise fast + fftAvg[i] = fftCalc[i] *0.75f + 0.25f*fftAvg[i]; // will need approx 2 cycles (50ms) for converging against fftCalc[i] + else // fall slow + fftAvg[i] = fftCalc[i]*0.1f + 0.9f*fftAvg[i]; // will need approx 5 cycles (150ms) for converging against fftCalc[i] + //fftAvg[i] = fftCalc[i]*0.05f + 0.95f*fftAvg[i]; // will need approx 10 cycles (250ms) for converging against fftCalc[i] + // Now, let's dump it all into fftResult. Need to do this, otherwise other routines might grab fftResult values prematurely. - //fftResult[i] = constrain((int)fftCalc[i], 0, 254); +#if !defined(SOUND_DYNAMICS_LIMITER) + fftResult[i] = constrain((int)fftCalc[i], 0, 254); +#else fftResult[i] = constrain((int)fftAvg[i], 0, 254); +#endif } #ifdef WLED_DEBUG From 1a2701561b53210e0556c03f73472a326a0fd647 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Tue, 16 Aug 2022 12:02:22 +0200 Subject: [PATCH 16/19] AR: bugfix for audio sync receive, and a few robustness improvements * Header checking for sound sync receiver: removed wrong "!" * make sure all member vars have initial values * some robustness improvements in case of receiving bad UDP data. --- usermods/audioreactive/audio_reactive.h | 40 ++++++++++++------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index 7147d1f46..e31456f4c 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -111,22 +111,22 @@ static float FFT_MajorPeak = 0.0f; static float FFT_Magnitude = 0.0f; // These are the input and output vectors. Input vectors receive computed results from FFT. -static float vReal[samplesFFT]; -static float vImag[samplesFFT]; -static float fftBin[samplesFFT_2]; +static float vReal[samplesFFT] = {0.0f}; +static float vImag[samplesFFT] = {0.0f}; +static float fftBin[samplesFFT_2] = {0.0f}; #ifdef UM_AUDIOREACTIVE_USE_NEW_FFT -static float windowWeighingFactors[samplesFFT]; +static float windowWeighingFactors[samplesFFT] = {0.0f}; #endif // Try and normalize fftBin values to a max of 4096, so that 4096/16 = 256. // Oh, and bins 0,1,2 are no good, so we'll zero them out. -static float fftCalc[16]; -static uint8_t fftResult[16]; // Our calculated result table, which we feed to the animations. +static float fftCalc[16] = {0.0f}; +static uint8_t fftResult[16] = {0}; // Our calculated result table, which we feed to the animations. #ifdef SR_DEBUG -static float fftResultMax[16]; // A table used for testing to determine how our post-processing is working. +static float fftResultMax[16] = {0.0f}; // A table used for testing to determine how our post-processing is working. #endif -static float fftAvg[16]; +static float fftAvg[16] = {0.0f}; #ifdef WLED_DEBUG static unsigned long fftTime = 0; @@ -148,7 +148,7 @@ static arduinoFFT FFT = arduinoFFT(vReal, vImag, samplesFFT, SAMPLE_RATE); static TaskHandle_t FFT_Task = nullptr; -float fftAddAvg(int from, int to) { +static float fftAddAvg(int from, int to) { float result = 0.0f; for (int i = from; i <= to; i++) { result += fftBin[i]; @@ -393,13 +393,13 @@ class AudioReactive : public Usermod { uint8_t maxVol = 10; // Reasonable value for constant volume for 'peak detector', as it won't always trigger (deprecated) uint8_t binNum = 8; // Used to select the bin for FFT based beat detection (deprecated) bool samplePeak = 0; // Boolean flag for peak. Responding routine must reset this flag - float volumeSmth; // either sampleAvg or sampleAgc depending on soundAgc; smoothed sample - int16_t volumeRaw; // either sampleRaw or rawSampleAgc depending on soundAgc - float my_magnitude; // FFT_Magnitude, scaled by multAgc + float volumeSmth = 0.0f; // either sampleAvg or sampleAgc depending on soundAgc; smoothed sample + int16_t volumeRaw = 0; // either sampleRaw or rawSampleAgc depending on soundAgc + float my_magnitude =0.0f; // FFT_Magnitude, scaled by multAgc bool udpSamplePeak = 0; // Boolean flag for peak. Set at the same tiem as samplePeak, but reset by transmitAudioData int16_t micIn = 0; // Current sample starts with negative values and large values, which is why it's 16 bit signed - int16_t sampleRaw; // Current sample. Must only be updated ONCE!!! (amplified mic value by sampleGain and inputLevel; smoothed over 16 samples) + int16_t sampleRaw = 0; // Current sample. Must only be updated ONCE!!! (amplified mic value by sampleGain and inputLevel; smoothed over 16 samples) double sampleMax = 0.0; // Max sample over a few seconds. Needed for AGC controler. float sampleReal = 0.0f; // "sampleRaw" as float, to provide bits that are lost otherwise (before amplification by sampleGain or inputLevel). Needed for AGC. float sampleAvg = 0.0f; // Smoothed Average sampleRaw @@ -746,7 +746,7 @@ class AudioReactive : public Usermod { } // transmitAudioData() - bool isValidUdpSyncVersion(const char *header) { + static bool isValidUdpSyncVersion(const char *header) { return strncmp_P(header, PSTR(UDP_SYNC_HEADER), 6) == 0; } @@ -763,12 +763,12 @@ class AudioReactive : public Usermod { fftUdp.read(fftBuff, packetSize); // VERIFY THAT THIS IS A COMPATIBLE PACKET - if (packetSize == sizeof(audioSyncPacket) && !(isValidUdpSyncVersion((const char *)fftBuff))) { + if (packetSize == sizeof(audioSyncPacket) && (isValidUdpSyncVersion((const char *)fftBuff))) { audioSyncPacket *receivedPacket = reinterpret_cast(fftBuff); // update samples for effects - volumeSmth = receivedPacket->sampleSmth; - volumeRaw = receivedPacket->sampleRaw; + volumeSmth = fmaxf(receivedPacket->sampleSmth, 0.0f); + volumeRaw = fmaxf(receivedPacket->sampleRaw, 0.0f); // update internal samples sampleRaw = volumeRaw; @@ -795,9 +795,9 @@ class AudioReactive : public Usermod { //These values are only available on the ESP32 for (int i = 0; i < 16; i++) fftResult[i] = receivedPacket->fftResult[i]; - my_magnitude = receivedPacket->FFT_Magnitude; + my_magnitude = fmaxf(receivedPacket->FFT_Magnitude, 0.0f); FFT_Magnitude = my_magnitude; - FFT_MajorPeak = receivedPacket->FFT_MajorPeak; + FFT_MajorPeak = fmaxf(receivedPacket->FFT_MajorPeak, 0.0f); //DEBUGSR_PRINTLN("Finished parsing UDP Sync Packet"); haveFreshData = true; } @@ -1070,7 +1070,7 @@ class AudioReactive : public Usermod { #endif //UDP Microphone Sync - transmit mode - if ((audioSyncEnabled & 0x01) && millis() - lastTime > 20) { + if ((audioSyncEnabled & 0x01) && (millis() - lastTime > 20)) { // Only run the transmit code IF we're in Transmit mode transmitAudioData(); lastTime = millis(); From 1336de12a095b896964d07bf9aa5dcf9a2d00399 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Wed, 17 Aug 2022 00:15:06 +0200 Subject: [PATCH 17/19] Info Page: added status info for audioreactive - Current sound source - including "failed to initialize" - Current AGC or Manual Gain - Sound Sync Status --- usermods/audioreactive/audio_reactive.h | 97 +++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index e31456f4c..67f0d8629 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -421,6 +421,12 @@ class AudioReactive : public Usermod { unsigned long last_kick_time = 0; uint8_t last_user_inputLevel = 0; + // used to feed "Info" Page + unsigned long last_UDPTime = 0; // time of last valid UDP sound sync datapacket + float maxSample5sec = 0.0f; // max sample (after AGC) in last 5 seconds + unsigned long sampleMaxTimer = 0; // last time maxSample5sec was reset + #define CYCLE_SAMPLEMAX 3500 // time window for merasuring + // strings to reduce flash memory usage (used more than twice) static const char _name[]; static const char _enabled[]; @@ -968,6 +974,7 @@ class AudioReactive : public Usermod { if (audioSyncEnabled & 0x02) disableSoundProcessing = true; // make sure everything is disabled IF in audio Receive mode if (audioSyncEnabled & 0x01) disableSoundProcessing = false; // keep running audio IF we're in audio Transmit mode + if(!audioSource->isInitialized()) disableSoundProcessing = true; // no audio source // Only run the sampling code IF we're not in Receive mode or realtime mode @@ -1056,6 +1063,7 @@ class AudioReactive : public Usermod { bool have_new_sample = false; if (millis() - lastTime > delayMs) { have_new_sample = receiveAudioData(); + if (have_new_sample) last_UDPTime = millis(); lastTime = millis(); } if (have_new_sample) syncVolumeSmth = volumeSmth; // remember received sample @@ -1069,6 +1077,14 @@ class AudioReactive : public Usermod { } #endif + // peak sample from last 5 seconds + if ((millis() - sampleMaxTimer) > CYCLE_SAMPLEMAX) { + sampleMaxTimer = millis(); + maxSample5sec = (0.25 * maxSample5sec) + 0.75 *((soundAgc) ? sampleAgc : sampleAvg); // reset, with some smoothing + if (sampleAvg < 1) maxSample5sec = 0; // noise gate + } else { + maxSample5sec = fmaxf(maxSample5sec, (soundAgc) ? sampleAgc : sampleAvg); // follow maximum + } //UDP Microphone Sync - transmit mode if ((audioSyncEnabled & 0x01) && (millis() - lastTime > 20)) { // Only run the transmit code IF we're in Transmit mode @@ -1092,7 +1108,17 @@ class AudioReactive : public Usermod { #ifdef WLED_DEBUG fftTime = sampleTime = 0; #endif + // gracefully suspend FFT task (if running) disableSoundProcessing = true; + + // reset sound data + micDataReal = 0.0f; + volumeRaw = 0; volumeSmth = 0; + sampleAgc = 0; sampleAvg = 0; + sampleRaw = 0; rawSampleAgc = 0; + my_magnitude = 0; FFT_Magnitude = 0; FFT_MajorPeak = 0; + multAgc = 1; + if (init && FFT_Task) { vTaskSuspend(FFT_Task); // update is about to begin, disable task to prevent crash } else { @@ -1111,6 +1137,7 @@ class AudioReactive : public Usermod { // , 0 // Core where the task should run ); } + micDataReal = 0.0f; // just to ber sure if (enabled) disableSoundProcessing = false; } @@ -1140,6 +1167,7 @@ class AudioReactive : public Usermod { */ void addToJsonInfo(JsonObject& root) { + char myStringBuffer[16]; // buffer for snprintf() JsonObject user = root["u"]; if (user.isNull()) user = root.createNestedObject("u"); @@ -1167,6 +1195,75 @@ class AudioReactive : public Usermod { uiDomString += F(" />
"); // infoArr.add(uiDomString); + // current Audio input + infoArr = user.createNestedArray(F("Audio Source")); + if (audioSyncEnabled & 0x02) { + // UDP sound sync - receive mode + infoArr.add("UDP sound sync"); + if (udpSyncConnected) { + if (millis() - last_UDPTime < 2500) + infoArr.add(" - receiving"); + else + infoArr.add(" - idle"); + } else { + infoArr.add(" - no network"); + } + } else { + // Analog or I2S digital input + if (audioSource && (audioSource->isInitialized())) { + // audio source sucessfully configured + if(audioSource->getType() == AudioSource::Type_I2SAdc) { + infoArr.add("ADC analog"); + } else { + infoArr.add("I2S digital"); + } + // input level or "silence" + if (maxSample5sec > 1.0) { + float my_usage = 100.0f * (maxSample5sec / 255.0f); + snprintf(myStringBuffer, 15, " - peak %3d%%", int(my_usage)); + infoArr.add(myStringBuffer); + } else { + infoArr.add(" - quiet"); + } + } else { + // error during audio source setup + infoArr.add("not initialized"); + infoArr.add(" - check GPIO config"); + } + } + + // Sound processing (FFT and input filters) + infoArr = user.createNestedArray(F("Sound Processing")); + if (audioSource && (disableSoundProcessing == false)) { + infoArr.add("running"); + } else { + infoArr.add("suspended"); + } + + // AGC or manual Gain + if((soundAgc==0) && (disableSoundProcessing == false) && !(audioSyncEnabled & 0x02)) { + infoArr = user.createNestedArray(F("Manual Gain")); + float myGain = ((float)sampleGain/40.0f * (float)inputLevel/128.0f) + 1.0f/16.0f; // non-AGC gain from presets + infoArr.add(roundf(myGain*100.0f) / 100.0f); + infoArr.add("x"); + } + if(soundAgc && (disableSoundProcessing == false) && !(audioSyncEnabled & 0x02)) { + infoArr = user.createNestedArray(F("AGC Gain")); + infoArr.add(roundf(multAgc*100.0f) / 100.0f); + infoArr.add("x"); + } + + // UDP Sound Sync status + infoArr = user.createNestedArray(F("UDP Sound Sync")); + if (audioSyncEnabled) { + if (audioSyncEnabled & 0x01) { + infoArr.add("send mode"); + } else if (audioSyncEnabled & 0x02) { + infoArr.add("receive mode"); + } + } else + infoArr.add("off"); + #ifdef WLED_DEBUG infoArr = user.createNestedArray(F("Sampling time")); infoArr.add(sampleTime); From 991fad02d7e1ce26a1b90f9904cd44ab77b691ce Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Wed, 17 Aug 2022 13:17:00 +0200 Subject: [PATCH 18/19] fixed look of some 1D effects - fade_out() appears to finally do something meaning. Old fade_out values were too high. Adjusted so effects in 1D look similar "classic" SR WLED - frequency reactive effects: max FFT frequency of 5120 Hz is hard-coded in most effects. Updated ranges to 10240 Hz --- wled00/FX.cpp | 39 +++++++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 3f6daacc5..b41049370 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -6157,7 +6157,8 @@ uint16_t mode_gravcenter(void) { // Gravcenter. By Andrew Tuline. } float volumeSmth = *(float*) um_data->u_data[0]; - SEGMENT.fade_out(240); + //SEGMENT.fade_out(240); + SEGMENT.fade_out(251); // 30% float segmentSampleAvg = volumeSmth * (float)SEGMENT.intensity / 255.0f; segmentSampleAvg *= 0.125; // divide by 8, to compensate for later "sensitivty" upscaling @@ -6206,8 +6207,9 @@ uint16_t mode_gravcentric(void) { // Gravcentric. By Andrew // printUmData(); - SEGMENT.fade_out(240); - SEGMENT.fade_out(240); // twice? really? + //SEGMENT.fade_out(240); + //SEGMENT.fade_out(240); // twice? really? + SEGMENT.fade_out(253); // 50% float segmentSampleAvg = volumeSmth * (float)SEGMENT.intensity / 255.0; segmentSampleAvg *= 0.125f; // divide by 8, to compensate for later "sensitivty" upscaling @@ -6254,7 +6256,8 @@ uint16_t mode_gravimeter(void) { // Gravmeter. By Andrew Tuline. } float volumeSmth = *(float*) um_data->u_data[0]; - SEGMENT.fade_out(240); + //SEGMENT.fade_out(240); + SEGMENT.fade_out(249); // 25% float segmentSampleAvg = volumeSmth * (float)SEGMENT.intensity / 255.0; segmentSampleAvg *= 0.25; // divide by 4, to compensate for later "sensitivty" upscaling @@ -6294,7 +6297,7 @@ uint16_t mode_juggles(void) { // Juggles. By Andrew Tuline. } float volumeSmth = *(float*) um_data->u_data[0]; - SEGMENT.fade_out(224); + SEGMENT.fade_out(224); // 6.25% uint16_t my_sampleAgc = fmax(fmin(volumeSmth, 255.0), 0); for (size_t i=0; iu_data[0]; int16_t volumeRaw = *(int16_t*)um_data->u_data[1]; - uint8_t fadeRate = map(SEGMENT.speed,0,255,224,255); + //uint8_t fadeRate = map(SEGMENT.speed,0,255,224,255); + uint8_t fadeRate = map(SEGMENT.speed,0,255,200,254); SEGMENT.fade_out(fadeRate); float tmpSound2 = volumeRaw * 2.0 * (float)SEGMENT.intensity / 255.0; int maxLen = mapf(tmpSound2, 0, 255, 0, SEGLEN); // map to pixels availeable in current segment // Still a bit too sensitive. + if (maxLen <0) maxLen = 0; if (maxLen >SEGLEN) maxLen = SEGLEN; for (int i=0; i=SEGLEN) locn = SEGLEN-1; - uint16_t pixCol = (log10f(FFT_MajorPeak) - 1.78f) * 255.0f/(3.71f-1.78f); // Scale log10 of frequency values to the 255 colour index. + //uint16_t pixCol = (log10f(FFT_MajorPeak) - 1.78f) * 255.0f/(3.71f-1.78f); // Scale log10 of frequency values to the 255 colour index. + uint16_t pixCol = (log10f(FFT_MajorPeak) - 1.78f) * 255.0f/(4.0102f-1.78f); // Scale log10 of frequency values to the 255 colour index. uint16_t bright = (int)my_magnitude; SEGMENT.setPixelColor(locn, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(SEGMENT.intensity+pixCol, false, PALETTE_SOLID_WRAP, 0), bright)); @@ -6757,7 +6764,8 @@ uint16_t mode_freqmatrix(void) { // Freqmatrix. By Andreas Plesch CRGB color = CRGB::Black; - if (FFT_MajorPeak > 5120) FFT_MajorPeak = 0; + //if (FFT_MajorPeak > 5120) FFT_MajorPeak = 0; + if (FFT_MajorPeak > 10240) FFT_MajorPeak = 0; // MajorPeak holds the freq. value which is most abundant in the last sample. // With our sampling rate of 10240Hz we have a usable freq range from roughtly 80Hz to 10240/2 Hz // we will treat everything with less than 65Hz as 0 @@ -6804,7 +6812,8 @@ uint16_t mode_freqpixels(void) { // Freqpixel. By Andrew Tuline. for (int i=0; i < SEGMENT.intensity/32+1; i++) { uint16_t locn = random16(0,SEGLEN); - uint8_t pixCol = (log10f(FFT_MajorPeak) - 1.78) * 255.0/(3.71-1.78); // Scale log10 of frequency values to the 255 colour index. + //uint8_t pixCol = (log10f(FFT_MajorPeak) - 1.78) * 255.0/(3.71-1.78); // Scale log10 of frequency values to the 255 colour index. + uint8_t pixCol = (log10f(FFT_MajorPeak) - 1.78f) * 255.0f/(4.0102f-1.78f); // Scale log10 of frequency values to the 255 colour index. SEGMENT.setPixelColor(locn, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(SEGMENT.intensity+pixCol, false, PALETTE_SOLID_WRAP, 0), (int)my_magnitude)); } @@ -6854,7 +6863,8 @@ uint16_t mode_freqwave(void) { // Freqwave. By Andreas Pleschun CRGB color = 0; - if (FFT_MajorPeak > 5120) FFT_MajorPeak = 0.0f; + //if (FFT_MajorPeak > 5120) FFT_MajorPeak = 0.0f; + if (FFT_MajorPeak > 10240) FFT_MajorPeak = 0.0f; // MajorPeak holds the freq. value which is most abundant in the last sample. // With our sampling rate of 10240Hz we have a usable freq range from roughtly 80Hz to 10240/2 Hz // we will treat everything with less than 65Hz as 0 @@ -6910,7 +6920,8 @@ uint16_t mode_gravfreq(void) { // Gravfreq. By Andrew Tuline. for (int i=0; i Date: Wed, 17 Aug 2022 13:40:54 +0200 Subject: [PATCH 19/19] AR: added dynamics limiter usermod cfg options - On/Off controls the complete feature - Rise Time and Fall Time are the minimum times (in milliseconds) for "volume" to go from 0% to 80% and back. - when "On" we also use some filtering to smooth FFTResults[]. Rise and Fall Times do not affect Frequency reactive effects otherwise. --- usermods/audioreactive/audio_reactive.h | 44 +++++++++++++++++-------- 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index 67f0d8629..c7c7e5f8f 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -27,9 +27,6 @@ // #define NO_MIC_LOGGER // exclude MIC_LOGGER from SR_DEBUG // hackers corner -#if !defined(SOUND_DYNAMICS_LIMITER) && !defined(NO_SOUND_DYNAMICS_LIMITER) -#define SOUND_DYNAMICS_LIMITER // experimental: define to enable a dynamics limiter that avoids "sudden flashes" at onsets. Makes some effects look more "smooth and fluent" -#endif #ifdef SR_DEBUG #define DEBUGSR_PRINT(x) Serial.print(x) @@ -64,8 +61,9 @@ static uint8_t soundAgc = 0; // Automagic gain control: 0 - non static uint8_t audioSyncEnabled = 0; // bit field: bit 0 - send, bit 1 - receive (config value) // user settable parameters for limitSoundDynamics() -static int attackTime = 80; // int: attack time in milliseconds. Default 0.1sec -static int decayTime = 1400; // int: decay time in milliseconds. Default 1.4sec +static bool limiterOn = true; // bool: enable / disable dynamics limiter +static uint16_t attackTime = 80; // int: attack time in milliseconds. Default 0.08sec +static uint16_t decayTime = 1400; // int: decay time in milliseconds. Default 1.40sec // // AGC presets @@ -289,11 +287,10 @@ void FFTcode(void * parameter) //fftAvg[i] = fftCalc[i]*0.05f + 0.95f*fftAvg[i]; // will need approx 10 cycles (250ms) for converging against fftCalc[i] // Now, let's dump it all into fftResult. Need to do this, otherwise other routines might grab fftResult values prematurely. -#if !defined(SOUND_DYNAMICS_LIMITER) - fftResult[i] = constrain((int)fftCalc[i], 0, 254); -#else - fftResult[i] = constrain((int)fftAvg[i], 0, 254); -#endif + if(limiterOn == true) + fftResult[i] = constrain((int)fftAvg[i], 0, 254); + else + fftResult[i] = constrain((int)fftCalc[i], 0, 254); } #ifdef WLED_DEBUG @@ -695,11 +692,12 @@ class AudioReactive : public Usermod { */ // effects: Gravimeter, Gravcenter, Gravcentric, Noisefire, Plasmoid, Freqpixels, Freqwave, Gravfreq, (2D Swirl, 2D Waverly) void limitSampleDynamics(void) { - #ifdef SOUND_DYNAMICS_LIMITER const float bigChange = 196; // just a representative number - a large, expected sample value static unsigned long last_time = 0; static float last_volumeSmth = 0.0f; + if(limiterOn == false) return; + long delta_time = millis() - last_time; delta_time = constrain(delta_time , 1, 1000); // below 1ms -> 1ms; above 1sec -> sily lil hick-up float deltaSample = volumeSmth - last_volumeSmth; @@ -717,7 +715,6 @@ class AudioReactive : public Usermod { last_volumeSmth = volumeSmth; last_time = millis(); - #endif } @@ -1080,10 +1077,10 @@ class AudioReactive : public Usermod { // peak sample from last 5 seconds if ((millis() - sampleMaxTimer) > CYCLE_SAMPLEMAX) { sampleMaxTimer = millis(); - maxSample5sec = (0.25 * maxSample5sec) + 0.75 *((soundAgc) ? sampleAgc : sampleAvg); // reset, with some smoothing + maxSample5sec = (0.15 * maxSample5sec) + 0.85 *((soundAgc) ? sampleAgc : sampleAvg); // reset, and start with some smoothing if (sampleAvg < 1) maxSample5sec = 0; // noise gate } else { - maxSample5sec = fmaxf(maxSample5sec, (soundAgc) ? sampleAgc : sampleAvg); // follow maximum + if ((sampleAvg >= 1)) maxSample5sec = fmaxf(maxSample5sec, (soundAgc) ? rawSampleAgc : sampleRaw); // follow maximum volume } //UDP Microphone Sync - transmit mode if ((audioSyncEnabled & 0x01) && (millis() - lastTime > 20)) { @@ -1370,6 +1367,11 @@ class AudioReactive : public Usermod { cfg[F("gain")] = sampleGain; cfg[F("AGC")] = soundAgc; + JsonObject dynLim = top.createNestedObject("dynamics"); + dynLim[F("Limiter")] = limiterOn; + dynLim[F("Rise")] = attackTime; + dynLim[F("Fall")] = decayTime; + JsonObject sync = top.createNestedObject("sync"); sync[F("port")] = audioSyncPort; sync[F("mode")] = audioSyncEnabled; @@ -1412,6 +1414,10 @@ class AudioReactive : public Usermod { configComplete &= getJsonValue(top["cfg"][F("gain")], sampleGain); configComplete &= getJsonValue(top["cfg"][F("AGC")], soundAgc); + configComplete &= getJsonValue(top["dynamics"][F("Limiter")], limiterOn); + configComplete &= getJsonValue(top["dynamics"][F("Rise")], attackTime); + configComplete &= getJsonValue(top["dynamics"][F("Fall")], decayTime); + configComplete &= getJsonValue(top["sync"][F("port")], audioSyncPort); configComplete &= getJsonValue(top["sync"][F("mode")], audioSyncEnabled); @@ -1433,6 +1439,16 @@ class AudioReactive : public Usermod { oappend(SET_F("addOption(dd,'Normal',1);")); oappend(SET_F("addOption(dd,'Vivid',2);")); oappend(SET_F("addOption(dd,'Lazy',3);")); + + oappend(SET_F("dd=addDropdown('AudioReactive','dynamics:Limiter');")); + oappend(SET_F("addOption(dd,'Off',0);")); + oappend(SET_F("addOption(dd,'On',1);")); + oappend(SET_F("addInfo('AudioReactive:dynamics:Limiter',0,' Limiter On ');")); // 0 is field type, 1 is actual field + //oappend(SET_F("addInfo('AudioReactive:dynamics:Rise',0,'min. ');")); + oappend(SET_F("addInfo('AudioReactive:dynamics:Rise',1,' ms
(volume reactive FX only)');")); + //oappend(SET_F("addInfo('AudioReactive:dynamics:Fall',0,'min. ');")); + oappend(SET_F("addInfo('AudioReactive:dynamics:Fall',1,' ms
(volume reactive FX only)');")); + oappend(SET_F("dd=addDropdown('AudioReactive','sync:mode');")); oappend(SET_F("addOption(dd,'Off',0);")); oappend(SET_F("addOption(dd,'Send',1);"));