From 3e494cc55155c9c6cfc0079bd95ce15f884f3f60 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Thu, 18 Aug 2022 19:07:37 +0200 Subject: [PATCH 01/13] removed broken frequency squelch, added frequency scaling options - removed broken FFTResult "squelch" feature. It was completely broken, and caused flashes in GEQ. - added Frequency scaling options: linear and logarithmic - fixed a few numerical accidents in FX.cpp (bouncing_balls, ripplepeak, freqmap, gravfreq, waterfall) --- usermods/audioreactive/audio_reactive.h | 150 +++++++++++++++++------- wled00/FX.cpp | 14 ++- 2 files changed, 117 insertions(+), 47 deletions(-) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index c7c7e5f8..02b20def 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -64,6 +64,8 @@ static uint8_t audioSyncEnabled = 0; // bit field: bit 0 - send, bit 1 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 +// user settable options for FFTResult scaling +static uint8_t FFTScalingMode = 2; // 0 none; 1 optimized logarithmic; 2 optimized linear // // AGC presets @@ -88,8 +90,14 @@ 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 float micDataReal = 0.0f; // MicIn data with full 24bit resolution - lowest 8bit after decimal point +static float sampleReal = 0.0f; // "sampleRaw" as float, to provide bits that are lost otherwise (before amplification by sampleGain or inputLevel). Needed for AGC. static float multAgc = 1.0f; // sample * multAgc = sampleAgc. Our AGC multiplier +static int16_t sampleRaw = 0; // Current sample. Must only be updated ONCE!!! (amplified mic value by sampleGain and inputLevel) +static int16_t rawSampleAgc = 0; // not smoothed AGC sample +static float sampleAvg = 0.0f; // Smoothed Average sampleRaw +static float sampleAgc = 0.0f; // Smoothed AGC sample + //////////////////// // Begin FFT Code // //////////////////// @@ -113,6 +121,9 @@ static float vReal[samplesFFT] = {0.0f}; static float vImag[samplesFFT] = {0.0f}; static float fftBin[samplesFFT_2] = {0.0f}; +#define FFT_DOWNSCALE 0.65f // downscaling factor for FFT results - "Flat-Top" window +#define LOG_256 5.54517744 + #ifdef UM_AUDIOREACTIVE_USE_NEW_FFT static float windowWeighingFactors[samplesFFT] = {0.0f}; #endif @@ -131,9 +142,6 @@ static unsigned long fftTime = 0; static unsigned long sampleTime = 0; #endif -// Table of linearNoise results to be multiplied by soundSquelch in order to reduce squelch across fftResult bins. -static uint8_t linearNoise[16] = { 34, 28, 26, 25, 20, 12, 9, 6, 4, 4, 3, 2, 2, 2, 2, 2 }; - // Table of multiplication factors so that we can even out the frequency response. static float fftResultPink[16] = { 1.70f, 1.71f, 1.73f, 1.78f, 1.68f, 1.56f, 1.55f, 1.63f, 1.79f, 1.62f, 1.80f, 2.06f, 2.47f, 3.35f, 6.83f, 9.55f }; @@ -146,6 +154,11 @@ static arduinoFFT FFT = arduinoFFT(vReal, vImag, samplesFFT, SAMPLE_RATE); static TaskHandle_t FFT_Task = nullptr; +// float version of map() +static float mapf(float x, float in_min, float in_max, float out_min, float out_max){ + return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; +} + static float fftAddAvg(int from, int to) { float result = 0.0f; for (int i = from; i <= to; i++) { @@ -213,7 +226,8 @@ void FFTcode(void * parameter) #ifdef UM_AUDIOREACTIVE_USE_NEW_FFT FFT.dcRemoval(); // remove DC offset - FFT.windowing( FFTWindow::Flat_top, FFTDirection::Forward); // Weigh data + FFT.windowing( FFTWindow::Flat_top, FFTDirection::Forward); // Weigh data using "Flat Top" function - better amplitude accuracy + //FFT.windowing(FFTWindow::Blackman_Harris, FFTDirection::Forward); // Weigh data using "Blackman- Harris" window - sharp peaks due to excellent sideband rejection FFT.compute( FFTDirection::Forward ); // Compute FFT FFT.complexToMagnitude(); // Compute magnitudes #else @@ -253,44 +267,84 @@ void FFTcode(void * parameter) * Multiplier = (End frequency/ Start frequency) ^ 1/16 * Multiplier = 1.320367784 */ + if (sampleAvg > 1) { // noise gate open // Range - fftCalc[ 0] = fftAddAvg(3,4); // 60 - 100 - fftCalc[ 1] = fftAddAvg(4,5); // 80 - 120 - fftCalc[ 2] = fftAddAvg(5,7); // 100 - 160 - fftCalc[ 3] = fftAddAvg(7,9); // 140 - 200 - 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,29); // 420 - 600 - fftCalc[ 8] = fftAddAvg(29,37); // 580 - 760 - fftCalc[ 9] = fftAddAvg(37,48); // 740 - 980 - fftCalc[10] = fftAddAvg(48,64); // 960 - 1300 - fftCalc[11] = fftAddAvg(64,84); // 1280 - 1700 - fftCalc[12] = fftAddAvg(84,111); // 1680 - 2240 - fftCalc[13] = fftAddAvg(111,147); // 2220 - 2960 - fftCalc[14] = fftAddAvg(147,194); // 2940 - 3900 - fftCalc[15] = fftAddAvg(194,255); // 3880 - 5120 + fftCalc[ 0] = fftAddAvg(2,4); // 60 - 100 + fftCalc[ 1] = fftAddAvg(4,5); // 80 - 120 + fftCalc[ 2] = fftAddAvg(5,7); // 100 - 160 + fftCalc[ 3] = fftAddAvg(7,9); // 140 - 200 + 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,29); // 420 - 600 + fftCalc[ 8] = fftAddAvg(29,37); // 580 - 760 + fftCalc[ 9] = fftAddAvg(37,48); // 740 - 980 + fftCalc[10] = fftAddAvg(48,64); // 960 - 1300 + fftCalc[11] = fftAddAvg(64,84); // 1280 - 1700 + fftCalc[12] = fftAddAvg(84,111); // 1680 - 2240 + fftCalc[13] = fftAddAvg(111,147); // 2220 - 2960 + fftCalc[14] = fftAddAvg(147,194); // 2940 - 3900 + fftCalc[15] = fftAddAvg(194,250); // 3880 - 5000 // avoid the last 5 bins, which are usually inaccurate + + } else { // noise gate closed + for (int i=0; i < 16; i++) { + fftCalc[i] *= 0.82f; // decay to zero + if (fftCalc[i] < 4.0f) fftCalc[i] = 0.0f; + } + } for (int i=0; i < 16; i++) { - // Noise supression of fftCalc bins using soundSquelch adjustment for different input types. - fftCalc[i] = (fftCalc[i] < ((float)soundSquelch * (float)linearNoise[i] / 4.0f)) ? 0 : fftCalc[i]; - // Adjustment for frequency curves. - fftCalc[i] *= fftResultPink[i]; - // 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 - + + if (sampleAvg > 1) { // noise gate open + // Adjustment for frequency curves. + fftCalc[i] *= fftResultPink[i]; + if (FFTScalingMode > 0) fftCalc[i] *= FFT_DOWNSCALE; // adjustment related to FFT windowing function + // 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 + if(fftCalc[i] < 0) fftCalc[i] = 0; + } + // 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] + fftAvg[i] = fftCalc[i]*0.17f + 0.83f*fftAvg[i]; // will need approx 5 cycles (150ms) for converging against fftCalc[i] + + // constrain internal vars - just to be sure + fftCalc[i] = constrain(fftCalc[i], 0.0f, 1023.0f); + fftAvg[i] = constrain(fftAvg[i], 0.0f, 1023.0f); + + float currentResult; + if(limiterOn == true) + currentResult = fftAvg[i]; + else + currentResult = fftCalc[i]; + + if (FFTScalingMode > 0) { + if (FFTScalingMode == 1) { + // Logarithmic scaling + currentResult *= 0.42; // 42 is the answer ;-) + currentResult -= 8.0; // this skips the lowest row, giving some room for peaks + if (currentResult > 1.0) + currentResult = logf(currentResult); // log to base "e", which is the fastest log() function + else currentResult = 0.0; // special handling, because log(1) = 0; log(0) = undefined + + currentResult *= 0.85f + (float(i)/18.0f); // extra up-scaling for high frequencies + currentResult = mapf(currentResult, 0, LOG_256, 0, 255); // map [log(1) ... log(255)] to [0 ... 255] + + } else { + // Linear scaling + currentResult *= 0.30f; // needs a bit more damping, get stay below 255 + currentResult -= 4.0; // giving a bit more room for peaks + if (currentResult < 1.0f) currentResult = 0.0f; + currentResult *= 0.85f + (float(i)/1.8f); // extra up-scaling for high frequencies + } + } else { + currentResult -= 4; // just a bit more room for peaks + } // Now, let's dump it all into fftResult. Need to do this, otherwise other routines might grab fftResult values prematurely. - if(limiterOn == true) - fftResult[i] = constrain((int)fftAvg[i], 0, 254); - else - fftResult[i] = constrain((int)fftCalc[i], 0, 254); + fftResult[i] = constrain((int)currentResult, 0, 255); } #ifdef WLED_DEBUG @@ -396,12 +450,7 @@ class AudioReactive : public Usermod { 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 = 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 - float sampleAgc = 0.0f; // Our AGC sample - int16_t rawSampleAgc = 0; // Our AGC sample - raw uint32_t timeOfPeak = 0; unsigned long lastTime = 0; // last time of running UDP Microphone Sync float micLev = 0.0f; // Used to convert returned value to have '0' as minimum. A leveller @@ -941,7 +990,7 @@ class AudioReactive : public Usermod { return; } // We cannot wait indefinitely before processing audio data - if (strip.isUpdating() && (millis() - lastUMRun < 2)) return; // be nice, but not too nice + if (strip.isUpdating() && (millis() - lastUMRun < 1)) 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 @@ -1115,6 +1164,11 @@ class AudioReactive : public Usermod { sampleRaw = 0; rawSampleAgc = 0; my_magnitude = 0; FFT_Magnitude = 0; FFT_MajorPeak = 0; multAgc = 1; + // reset FFT data + memset(fftCalc, 0, sizeof(fftCalc)); + memset(fftAvg, 0, sizeof(fftAvg)); + memset(fftResult, 0, sizeof(fftResult)); + for(int i=(init?0:1); i<16; i+=2) fftResult[i] = 16; // make a tiny pattern if (init && FFT_Task) { vTaskSuspend(FFT_Task); // update is about to begin, disable task to prevent crash @@ -1372,6 +1426,9 @@ class AudioReactive : public Usermod { dynLim[F("Rise")] = attackTime; dynLim[F("Fall")] = decayTime; + JsonObject freqScale = top.createNestedObject("Frequency"); + freqScale[F("Scale")] = FFTScalingMode; + JsonObject sync = top.createNestedObject("sync"); sync[F("port")] = audioSyncPort; sync[F("mode")] = audioSyncEnabled; @@ -1418,6 +1475,8 @@ class AudioReactive : public Usermod { configComplete &= getJsonValue(top["dynamics"][F("Rise")], attackTime); configComplete &= getJsonValue(top["dynamics"][F("Fall")], decayTime); + configComplete &= getJsonValue(top["Frequency"][F("Scale")], FFTScalingMode); + configComplete &= getJsonValue(top["sync"][F("port")], audioSyncPort); configComplete &= getJsonValue(top["sync"][F("mode")], audioSyncEnabled); @@ -1443,11 +1502,14 @@ class AudioReactive : public Usermod { 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("addInfo('AudioReactive:dynamics:Limiter',0,' On ');")); // 0 is field type, 1 is actual field + oappend(SET_F("addInfo('AudioReactive:dynamics:Rise',1,'ms (♪ effects only)');")); + oappend(SET_F("addInfo('AudioReactive:dynamics:Fall',1,'ms (♪ effects only)');")); + + oappend(SET_F("dd=addDropdown('AudioReactive','Frequency:Scale');")); + oappend(SET_F("addOption(dd,'None',0);")); + oappend(SET_F("addOption(dd,'Linear (Amplitude)',2);")); + oappend(SET_F("addOption(dd,'Logarithmic (Loudness)',1);")); oappend(SET_F("dd=addDropdown('AudioReactive','sync:mode');")); oappend(SET_F("addOption(dd,'Off',0);")); diff --git a/wled00/FX.cpp b/wled00/FX.cpp index b4104937..b5a9ab79 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -2842,12 +2842,13 @@ uint16_t mode_bouncing_balls(void) { for (size_t i = 0; i < numBalls; i++) { float timeSinceLastBounce = (time - balls[i].lastBounceTime)/((255-SEGMENT.speed)*8/256 +1); - balls[i].height = 0.5 * gravity * pow(timeSinceLastBounce/1000 , 2.0) + balls[i].impactVelocity * timeSinceLastBounce/1000; + float timeSec = timeSinceLastBounce/1000.0f; + balls[i].height = 0.5 * gravity * (timeSec * timeSec) + balls[i].impactVelocity * timeSec; // avoid use pow(x, 2) - its extremely slow ! if (balls[i].height < 0) { //start bounce balls[i].height = 0; //damping for better effect using multiple balls - float dampening = 0.90 - float(i)/pow(numBalls,2); + float dampening = 0.90 - float(i)/(float(numBalls) * float(numBalls)); // avoid use pow(x, 2) - its extremely slow ! balls[i].impactVelocity = dampening * balls[i].impactVelocity; balls[i].lastBounceTime = time; @@ -2863,7 +2864,7 @@ uint16_t mode_bouncing_balls(void) { color = SEGCOLOR(i % NUM_COLORS); } - uint16_t pos = round(balls[i].height * (SEGLEN - 1)); + uint16_t pos = roundf(balls[i].height * (SEGLEN - 1)); SEGMENT.setPixelColor(pos, color); } @@ -6006,7 +6007,9 @@ uint16_t mode_ripplepeak(void) { // * Ripple peak. By Andrew Tuli case 255: // Initialize ripple variables. ripples[i].pos = random16(SEGLEN); #ifdef ESP32 + if (FFT_MajorPeak > 1) // log10(0) is "forbidden" (throws exception) ripples[i].color = (int)(log10f(FFT_MajorPeak)*128); + else ripples[i].color = 0; #else ripples[i].color = random8(); #endif @@ -6716,6 +6719,7 @@ uint16_t mode_freqmap(void) { // Map FFT_MajorPeak to SEGLEN. } float FFT_MajorPeak = *(float*) um_data->u_data[4]; float my_magnitude = *(float*) um_data->u_data[5] / 4.0f; + if (FFT_MajorPeak < 1) FFT_MajorPeak = 1; // log10(0) is "forbidden" (throws exception) SEGMENT.fade_out(SEGMENT.speed); @@ -6806,6 +6810,7 @@ uint16_t mode_freqpixels(void) { // Freqpixel. By Andrew Tuline. } float FFT_MajorPeak = *(float*) um_data->u_data[4]; float my_magnitude = *(float*) um_data->u_data[5] / 16.0f; + if (FFT_MajorPeak < 1) FFT_MajorPeak = 1; // log10(0) is "forbidden" (throws exception) uint16_t fadeRate = 2*SEGMENT.speed - SEGMENT.speed*SEGMENT.speed/255; // Get to 255 as quick as you can. SEGMENT.fade_out(fadeRate); @@ -6908,6 +6913,7 @@ uint16_t mode_gravfreq(void) { // Gravfreq. By Andrew Tuline. } float FFT_MajorPeak = *(float*) um_data->u_data[4]; float volumeSmth = *(float*) um_data->u_data[0]; + if (FFT_MajorPeak < 1) FFT_MajorPeak = 1; // log10(0) is "forbidden" (throws exception) SEGMENT.fade_out(240); @@ -7022,6 +7028,8 @@ uint16_t mode_waterfall(void) { // Waterfall. By: Andrew Tulin uint8_t *binNum = (uint8_t*)um_data->u_data[7]; float my_magnitude = *(float*) um_data->u_data[5] / 8.0f; + if (FFT_MajorPeak < 1) FFT_MajorPeak = 1; // log10(0) is "forbidden" (throws exception) + if (SEGENV.call == 0) { SEGMENT.setUpLeds(); SEGMENT.fill(BLACK); From 753ae51dd524e6b7c0aed4ed5855bb23d6837cc0 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Thu, 18 Aug 2022 20:42:58 +0200 Subject: [PATCH 02/13] Stop & restart UDP on pause/update. --- usermods/audioreactive/audio_reactive.h | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index 02b20def..d33fd218 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -1172,11 +1172,16 @@ class AudioReactive : public Usermod { if (init && FFT_Task) { vTaskSuspend(FFT_Task); // update is about to begin, disable task to prevent crash + if (udpSyncConnected) { // close UDP sync connection (if open) + udpSyncConnected = false; + fftUdp.stop(); + } } else { // update has failed or create task requested - if (FFT_Task) + if (FFT_Task) { vTaskResume(FFT_Task); - else + connected(); // resume UDP + } else // xTaskCreatePinnedToCore( xTaskCreate( // no need to "pin" this task to core #0 FFTcode, // Function to implement the task @@ -1184,11 +1189,11 @@ class AudioReactive : public Usermod { 5000, // Stack size in words NULL, // Task input parameter 1, // Priority of the task - &FFT_Task // Task handle + &FFT_Task // Task handle // , 0 // Core where the task should run ); } - micDataReal = 0.0f; // just to ber sure + micDataReal = 0.0f; // just to be sure if (enabled) disableSoundProcessing = false; } From 3c57e2e2b98d1eefbb020d840a8bfd17fd3c4a8d Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Fri, 19 Aug 2022 14:36:47 +0200 Subject: [PATCH 03/13] AR: special gain for GEO, some bugfixes andparameter tinkering - new feature: "Input Level" (info page) can be used as global "GEQ gain" - only when AGC is ON (was already possible when AGC=off) - some parameter tweaking in FFT function - hidden feature: FFT decay is slower when setting a high "dynamics Limiter Fall time" (steps: <1000, <2000, <3000, >3000) - FFT_MajorPeak default 1.0f (as log(0.0) is invalid) - FX.cppp: ensure that fftResult[] is always used inside array bounds --- usermods/audioreactive/audio_reactive.h | 62 +++++++++++++++++-------- wled00/FX.cpp | 6 ++- 2 files changed, 47 insertions(+), 21 deletions(-) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index d33fd218..b2b66b08 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -113,7 +113,7 @@ static float sampleAgc = 0.0f; // Smoothed AGC sample 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 - only the "lower half" contains useful information. -static float FFT_MajorPeak = 0.0f; +static float FFT_MajorPeak = 1.0f; static float FFT_Magnitude = 0.0f; // These are the input and output vectors. Input vectors receive computed results from FFT. @@ -250,9 +250,10 @@ void FFTcode(void * parameter) #else FFT.MajorPeak(&FFT_MajorPeak, &FFT_Magnitude); // let the effects know which freq was most dominant #endif + FFT_MajorPeak = constrain(FFT_MajorPeak, 1.0f, 10240.0f); 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 + float t = fabsf(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() @@ -288,7 +289,8 @@ void FFTcode(void * parameter) } else { // noise gate closed for (int i=0; i < 16; i++) { - fftCalc[i] *= 0.82f; // decay to zero + //fftCalc[i] *= 0.82f; // decay to zero + fftCalc[i] *= 0.85f; // decay to zero if (fftCalc[i] < 4.0f) fftCalc[i] = 0.0f; } } @@ -307,9 +309,12 @@ void FFTcode(void * parameter) // 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.17f + 0.83f*fftAvg[i]; // will need approx 5 cycles (150ms) for converging against fftCalc[i] - + else { // fall slow + if (decayTime < 1000) fftAvg[i] = fftCalc[i]*0.22f + 0.78f*fftAvg[i]; // approx 5 cycles (225ms) for falling to zero + else if (decayTime < 2000) fftAvg[i] = fftCalc[i]*0.17f + 0.83f*fftAvg[i]; // default - approx 9 cycles (225ms) for falling to zero + else if (decayTime < 3000) fftAvg[i] = fftCalc[i]*0.14f + 0.86f*fftAvg[i]; // approx 14 cycles (350ms) for falling to zero + else fftAvg[i] = fftCalc[i]*0.1f + 0.9f*fftAvg[i]; // approx 20 cycles (500ms) for falling to zero + } // constrain internal vars - just to be sure fftCalc[i] = constrain(fftCalc[i], 0.0f, 1023.0f); fftAvg[i] = constrain(fftAvg[i], 0.0f, 1023.0f); @@ -344,6 +349,11 @@ void FFTcode(void * parameter) } // Now, let's dump it all into fftResult. Need to do this, otherwise other routines might grab fftResult values prematurely. + if (soundAgc > 0) { // apply extra "GEQ Gain" if set by user + float post_gain = (float)inputLevel/128.0f; + if (post_gain < 1.0f) post_gain = ((post_gain -1.0f) * 0.8f) +1.0f; + currentResult *= post_gain; + } fftResult[i] = constrain((int)currentResult, 0, 255); } @@ -849,7 +859,7 @@ class AudioReactive : public Usermod { my_magnitude = fmaxf(receivedPacket->FFT_Magnitude, 0.0f); FFT_Magnitude = my_magnitude; - FFT_MajorPeak = fmaxf(receivedPacket->FFT_MajorPeak, 0.0f); + FFT_MajorPeak = fmaxf(receivedPacket->FFT_MajorPeak, 1.0f); //DEBUGSR_PRINTLN("Finished parsing UDP Sync Packet"); haveFreshData = true; } @@ -990,7 +1000,7 @@ class AudioReactive : public Usermod { return; } // We cannot wait indefinitely before processing audio data - if (strip.isUpdating() && (millis() - lastUMRun < 1)) 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 @@ -1059,6 +1069,9 @@ class AudioReactive : public Usermod { limitSampleDynamics(); // optional - makes volumeSmth very smooth and fluent +#if 0 + /* currently this is _not_ working. Code relies on "musical note" symbol as second char of the effect name */ + #error I told you its not working right now // 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 @@ -1099,6 +1112,7 @@ class AudioReactive : public Usermod { last_user_inputLevel = new_user_inputLevel; } } +#endif } @@ -1162,13 +1176,14 @@ class AudioReactive : public Usermod { volumeRaw = 0; volumeSmth = 0; sampleAgc = 0; sampleAvg = 0; sampleRaw = 0; rawSampleAgc = 0; - my_magnitude = 0; FFT_Magnitude = 0; FFT_MajorPeak = 0; + my_magnitude = 0; FFT_Magnitude = 0; FFT_MajorPeak = 1; multAgc = 1; // reset FFT data memset(fftCalc, 0, sizeof(fftCalc)); memset(fftAvg, 0, sizeof(fftAvg)); memset(fftResult, 0, sizeof(fftResult)); for(int i=(init?0:1); i<16; i+=2) fftResult[i] = 16; // make a tiny pattern + inputLevel = 128; // resset level slider to default if (init && FFT_Task) { vTaskSuspend(FFT_Task); // update is about to begin, disable task to prevent crash @@ -1241,15 +1256,22 @@ class AudioReactive : public Usermod { infoArr.add(uiDomString); if (enabled) { - infoArr = user.createNestedArray(F("Input level")); - uiDomString = F("
"); // - infoArr.add(uiDomString); + // Input Level Slider + if (disableSoundProcessing == false) { // only show slider when audio processing is running + if (soundAgc > 0) + infoArr = user.createNestedArray(F("GEQ Input Level")); // if AGC is on, this slider only affects fftResult[] frequencies + else + infoArr = user.createNestedArray(F("Audio Input Level")); + uiDomString = F("
"); // + infoArr.add(uiDomString); + } + //else infoArr.add("
 
"); // no processing - add empty line // current Audio input infoArr = user.createNestedArray(F("Audio Source")); @@ -1262,7 +1284,7 @@ class AudioReactive : public Usermod { else infoArr.add(" - idle"); } else { - infoArr.add(" - no network"); + infoArr.add(" - no connection"); } } else { // Analog or I2S digital input @@ -1319,6 +1341,8 @@ class AudioReactive : public Usermod { } } else infoArr.add("off"); + if (audioSyncEnabled && !udpSyncConnected) infoArr.add(" (unconnected)"); + //if (!udpSyncConnected) infoArr.add(" (unconnected)"); #ifdef WLED_DEBUG infoArr = user.createNestedArray(F("Sampling time")); diff --git a/wled00/FX.cpp b/wled00/FX.cpp index b5a9ab79..2ba78c89 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -7093,6 +7093,7 @@ uint16_t mode_2DGEQ(void) { // By Will Tatam. Code reduction by Ewoud Wijma. for (int x=0; x < cols; x++) { uint8_t band = map(x, 0, cols-1, 0, NUM_BANDS - 1); + band = constrain(band, 0, 15); uint16_t colorIndex = band * 17; uint16_t barHeight = map(fftResult[band], 0, 255, 0, rows); // do not subtract -1 from rows here if (barHeight > previousBarHeight[x]) previousBarHeight[x] = barHeight; //drive the peak up @@ -7153,8 +7154,8 @@ uint16_t mode_2DFunkyPlank(void) { // Written by ??? Adapted by Wil // display values of int b = 0; for (int band = 0; band < NUMB_BANDS; band += bandInc, b++) { - int hue = fftResult[band]; - int v = map(fftResult[band], 0, 255, 10, 255); + int hue = fftResult[band % 16]; + int v = map(fftResult[band % 16], 0, 255, 10, 255); for (int w = 0; w < barWidth; w++) { int xpos = (barWidth * b) + w; SEGMENT.setPixelColorXY(xpos, 0, CHSV(hue, 255, v)); @@ -7269,6 +7270,7 @@ uint16_t mode_2DAkemi(void) { if (um_data && fftResult) { for (int x=0; x < cols/8; x++) { uint16_t band = x * cols/8; + band = constrain(band, 0, 15); uint16_t barHeight = map(fftResult[band], 0, 255, 0, 17*rows/32); CRGB color = SEGMENT.color_from_palette((band * 35), false, PALETTE_SOLID_WRAP, 0); From f7652bd2ef561a84fb83a2a7e9f5ffc696dedec0 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Fri, 19 Aug 2022 15:17:04 +0200 Subject: [PATCH 04/13] Fix audio sync check --- usermods/audioreactive/audio_reactive.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index c7c7e5f8..54314700 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -911,7 +911,7 @@ class AudioReactive : public Usermod { */ void connected() { - if (audioSyncPort > 0 || (audioSyncEnabled & 0x03)) { + if (audioSyncPort > 0 && (audioSyncEnabled & 0x03)) { #ifndef ESP8266 udpSyncConnected = fftUdp.beginMulticast(IPAddress(239, 0, 0, 1), audioSyncPort); #else From b8db47e528677b545f7b9800aa0f5cf95463809a Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Fri, 19 Aug 2022 16:11:50 +0200 Subject: [PATCH 05/13] AR: new freq scaling option "square root" also looks nice. It's a compromise between log() and linear. OK enough tinkering for today :-) --- usermods/audioreactive/audio_reactive.h | 30 ++++++++++++++++++------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index b2b66b08..6607d7c5 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -325,27 +325,40 @@ void FFTcode(void * parameter) else currentResult = fftCalc[i]; - if (FFTScalingMode > 0) { - if (FFTScalingMode == 1) { + switch (FFTScalingMode) { + case 1: // Logarithmic scaling currentResult *= 0.42; // 42 is the answer ;-) currentResult -= 8.0; // this skips the lowest row, giving some room for peaks if (currentResult > 1.0) currentResult = logf(currentResult); // log to base "e", which is the fastest log() function else currentResult = 0.0; // special handling, because log(1) = 0; log(0) = undefined - currentResult *= 0.85f + (float(i)/18.0f); // extra up-scaling for high frequencies currentResult = mapf(currentResult, 0, LOG_256, 0, 255); // map [log(1) ... log(255)] to [0 ... 255] - - } else { + break; + case 2: // Linear scaling currentResult *= 0.30f; // needs a bit more damping, get stay below 255 currentResult -= 4.0; // giving a bit more room for peaks if (currentResult < 1.0f) currentResult = 0.0f; currentResult *= 0.85f + (float(i)/1.8f); // extra up-scaling for high frequencies - } - } else { - currentResult -= 4; // just a bit more room for peaks + break; + case 3: + // square root scaling + currentResult *= 0.38f; + currentResult -= 6.0f; + if (currentResult > 1.0) + currentResult = sqrtf(currentResult); + else currentResult = 0.0; // special handling, because sqrt(0) = undefined + currentResult *= 0.85f + (float(i)/5.0f); // extra up-scaling for high frequencies + currentResult = mapf(currentResult, 0.0, 16.0, 0.0, 255.0); // map [sqrt(1) ... sqrt(256)] to [0 ... 255] + break; + + case 0: + default: + // no scaling - leave freq bins as-is + currentResult -= 4; // just a bit more room for peaks + break; } // Now, let's dump it all into fftResult. Need to do this, otherwise other routines might grab fftResult values prematurely. @@ -1538,6 +1551,7 @@ class AudioReactive : public Usermod { oappend(SET_F("dd=addDropdown('AudioReactive','Frequency:Scale');")); oappend(SET_F("addOption(dd,'None',0);")); oappend(SET_F("addOption(dd,'Linear (Amplitude)',2);")); + oappend(SET_F("addOption(dd,'Square Root (Energy)',3);")); oappend(SET_F("addOption(dd,'Logarithmic (Loudness)',1);")); oappend(SET_F("dd=addDropdown('AudioReactive','sync:mode');")); From 44a4b11d369862dbf26e693999dfe89c30a398a3 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Fri, 19 Aug 2022 21:14:49 +0200 Subject: [PATCH 06/13] Replace setOption/getOption --- wled00/FX.cpp | 6 ++-- wled00/FX.h | 50 +++++++++++++++++-------------- wled00/FX_fcn.cpp | 4 +-- wled00/button.cpp | 4 +-- wled00/json.cpp | 68 +++++++++++++++++++++--------------------- wled00/set.cpp | 18 +++++------ wled00/udp.cpp | 6 ++-- wled00/wled_eeprom.cpp | 10 +++---- 8 files changed, 85 insertions(+), 81 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index dd693bb1..91e1da53 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -75,7 +75,7 @@ int8_t tristate_square8(uint8_t x, uint8_t pulsewidth, uint8_t attdec) { */ uint16_t mode_static(void) { SEGMENT.fill(SEGCOLOR(0)); - return /*(SEGMENT.getOption(SEG_OPTION_TRANSITIONAL)) ? FRAMETIME :*/ 350; //update faster if in transition + return 350; } static const char _data_FX_MODE_STATIC[] PROGMEM = "Solid"; @@ -3986,7 +3986,7 @@ uint16_t mode_flow(void) { uint8_t colorIndex = (i * 255 / zoneLen) - counter; uint16_t led = (z & 0x01) ? i : (zoneLen -1) -i; - if (SEGMENT.getOption(SEG_OPTION_REVERSED)) led = (zoneLen -1) -led; + if (SEGMENT.reverse) led = (zoneLen -1) -led; SEGMENT.setPixelColor(pos + led, SEGMENT.color_from_palette(colorIndex, false, true, 255)); } } @@ -4927,7 +4927,7 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: SEGENV.aux1 = SEGENV.aux0; SEGENV.aux0 = crc; - return (SEGMENT.getOption(SEG_OPTION_TRANSITIONAL)) ? FRAMETIME : FRAMETIME_FIXED * (128-(SEGMENT.speed>>1)); // update only when appropriate time passes (in 42 FPS slots) + return FRAMETIME_FIXED * (128-(SEGMENT.speed>>1)); // update only when appropriate time passes (in 42 FPS slots) } // mode_2Dgameoflife() static const char _data_FX_MODE_2DGAMEOFLIFE[] PROGMEM = "Game Of Life@!,;!,!;!;2d"; diff --git a/wled00/FX.h b/wled00/FX.h index 486a07e1..486c9af4 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -344,7 +344,7 @@ typedef enum mapping1D2D { M12_Block = 3 } mapping1D2D_t; -// segment, 68 (92 in memory) bytes +// segment, 72 bytes typedef struct Segment { public: uint16_t start; // start index / start X coordinate 2D (left) @@ -357,34 +357,38 @@ typedef struct Segment { union { uint16_t options; //bit pattern: msb first: [transposed mirrorY reverseY] transitional (tbd) paused needspixelstate mirrored on reverse selected struct { - bool selected : 1; // 0 : selected - bool reverse : 1; // 1 : reversed - bool on : 1; // 2 : is On - bool mirror : 1; // 3 : mirrored - bool pxs : 1; // 4 : indicates that the effect does not use FRAMETIME or needs getPixelColor (?) - bool freeze : 1; // 5 : paused/frozen - bool reset : 1; // 6 : indicates that Segment runtime requires reset - bool transitional: 1; // 7 : transitional (there is transition occuring) - bool reverse_y : 1; // 8 : reversed Y (2D) - bool mirror_y : 1; // 9 : mirrored Y (2D) - bool transpose : 1; // 10 : transposed (2D, swapped X & Y) - uint8_t map1D2D : 2; // 11-12 : mapping for 1D effect on 2D (0-strip, 1-expand vertically, 2-circular, 3-rectangular) - uint8_t soundSim : 3; // 13-15 : 0-7 sound simulation types + bool selected : 1; // 0 : selected + bool reverse : 1; // 1 : reversed + bool on : 1; // 2 : is On + bool mirror : 1; // 3 : mirrored + bool pxs : 1; // 4 : indicates that the effect does not use FRAMETIME or needs getPixelColor (?) + bool freeze : 1; // 5 : paused/frozen + bool reset : 1; // 6 : indicates that Segment runtime requires reset + bool transitional: 1; // 7 : transitional (there is transition occuring) + bool reverse_y : 1; // 8 : reversed Y (2D) + bool mirror_y : 1; // 9 : mirrored Y (2D) + bool transpose : 1; // 10 : transposed (2D, swapped X & Y) + uint8_t map1D2D : 3; // 11-13 : mapping for 1D effect on 2D (0-use as strip, 1-expand vertically, 2-circular/arc, 3-rectangular/corner, ...) + uint8_t soundSim : 2; // 14-15 : 0-3 sound simulation types }; }; uint8_t grouping, spacing; + //struct { + // uint8_t grouping : 4; // maximum 15 pixels in a group + // uint8_t spacing : 4; // maximum 15 pixels per gap + //}; uint8_t opacity; uint32_t colors[NUM_COLORS]; - uint8_t cct; //0==1900K, 255==10091K - uint8_t custom1, custom2; // custom FX parameters/sliders + uint8_t cct; //0==1900K, 255==10091K + uint8_t custom1, custom2; // custom FX parameters/sliders struct { - uint8_t custom3 : 5; // reduced range slider (0-31) - bool check1 : 1; // checkmark 1 - bool check2 : 1; // checkmark 2 - bool check3 : 1; // checkmark 3 + uint8_t custom3 : 5; // reduced range slider (0-31) + bool check1 : 1; // checkmark 1 + bool check2 : 1; // checkmark 2 + bool check3 : 1; // checkmark 3 }; - uint16_t startY; // start Y coodrinate 2D (top) - uint16_t stopY; // stop Y coordinate 2D (bottom) + uint8_t startY; // start Y coodrinate 2D (top); there should be no more than 255 rows + uint8_t stopY; // stop Y coordinate 2D (bottom); there should be no more than 255 rows char *name; // runtime data @@ -627,7 +631,7 @@ typedef struct Segment { void wu_pixel(uint32_t x, uint32_t y, CRGB c) {} #endif } segment; -//static int i = sizeof(Segment); +//static int segSize = sizeof(Segment); // main "strip" class class WS2812FX { // 96 bytes diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 13fe77dc..dedff0ef 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1106,7 +1106,7 @@ void WS2812FX::setBrightness(uint8_t b, bool direct) { _brightness = b; if (_brightness == 0) { //unfreeze all segments on power off for (segment &seg : _segments) { - seg.setOption(SEG_OPTION_FREEZE, false); + seg.freeze = false; } } if (direct) { @@ -1319,7 +1319,7 @@ void WS2812FX::makeAutoSegments(bool forceReset) { _segments.clear(); for (size_t i = 0; i < s; i++) { Segment seg = Segment(segStarts[i], segStops[i]); - seg.setOption(SEG_OPTION_SELECTED, true); + seg.selected = true; _segments.push_back(seg); } _mainSegment = 0; diff --git a/wled00/button.cpp b/wled00/button.cpp index b472f927..81e3c8c3 100644 --- a/wled00/button.cpp +++ b/wled00/button.cpp @@ -197,10 +197,10 @@ void handleAnalog(uint8_t b) // otherwise use "double press" for segment selection Segment& seg = strip.getSegment(macroDoublePress[b]); if (aRead == 0) { - seg.setOption(SEG_OPTION_ON, 0); // off + seg.on = false; // off } else { seg.setOpacity(aRead); - seg.setOption(SEG_OPTION_ON, 1); + seg.on = true; } // this will notify clients of update (websockets,mqtt,etc) updateInterfaces(CALL_MODE_BUTTON); diff --git a/wled00/json.cpp b/wled00/json.cpp index e3ed8a66..a2f654df 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -84,8 +84,8 @@ void deserializeSegment(JsonObject elem, byte it, byte presetId) if ((spc>0 && spc!=seg.spacing) || seg.map1D2D!=map1D2D) seg.fill(BLACK); // clear spacing gaps - seg.map1D2D = map1D2D & 0x03; - seg.soundSim = soundSim & 0x07; + seg.map1D2D = map1D2D & 0x07; + seg.soundSim = soundSim & 0x03; uint16_t len = 1; if (stop > start) len = stop - start; @@ -102,15 +102,15 @@ void deserializeSegment(JsonObject elem, byte it, byte presetId) byte segbri = seg.opacity; if (getVal(elem["bri"], &segbri)) { if (segbri > 0) seg.setOpacity(segbri); - seg.setOption(SEG_OPTION_ON, segbri); + seg.on = segbri; } - bool on = elem["on"] | seg.getOption(SEG_OPTION_ON); + bool on = elem["on"] | seg.on; if (elem["on"].is() && elem["on"].as()[0] == 't') on = !on; - seg.setOption(SEG_OPTION_ON, on); - bool frz = elem["frz"] | seg.getOption(SEG_OPTION_FREEZE); - if (elem["frz"].is() && elem["frz"].as()[0] == 't') frz = !seg.getOption(SEG_OPTION_FREEZE); - seg.setOption(SEG_OPTION_FREEZE, frz); + seg.on = on; + bool frz = elem["frz"] | seg.freeze; + if (elem["frz"].is() && elem["frz"].as()[0] == 't') frz = !seg.freeze; + seg.freeze = frz; seg.setCCT(elem["cct"] | seg.cct); @@ -162,13 +162,13 @@ void deserializeSegment(JsonObject elem, byte it, byte presetId) } #endif - seg.setOption(SEG_OPTION_SELECTED, elem["sel"] | seg.getOption(SEG_OPTION_SELECTED)); - seg.setOption(SEG_OPTION_REVERSED, elem["rev"] | seg.getOption(SEG_OPTION_REVERSED)); - seg.setOption(SEG_OPTION_MIRROR , elem[F("mi")] | seg.getOption(SEG_OPTION_MIRROR )); + seg.selected = elem["sel"] | seg.selected; + seg.reverse = elem["rev"] | seg.reverse; + seg.mirror = elem[F("mi")] | seg.mirror; #ifndef WLED_DISABLE_2D - seg.setOption(SEG_OPTION_REVERSED_Y, elem[F("rY")] | seg.getOption(SEG_OPTION_REVERSED_Y)); - seg.setOption(SEG_OPTION_MIRROR_Y , elem[F("mY")] | seg.getOption(SEG_OPTION_MIRROR_Y )); - seg.setOption(SEG_OPTION_TRANSPOSED, elem[F("tp")] | seg.getOption(SEG_OPTION_TRANSPOSED)); + seg.reverse_y = elem[F("rY")] | seg.reverse_y; + seg.mirror_y = elem[F("mY")] | seg.mirror_y; + seg.transpose = elem[F("tp")] | seg.transpose; #endif byte fx = seg.mode; @@ -193,8 +193,8 @@ void deserializeSegment(JsonObject elem, byte it, byte presetId) sOpt = extractModeDefaults(fx, SET_F("c1")); if (sOpt >= 0) seg.custom1 = sOpt; sOpt = extractModeDefaults(fx, SET_F("c2")); if (sOpt >= 0) seg.custom2 = sOpt; sOpt = extractModeDefaults(fx, SET_F("c3")); if (sOpt >= 0) seg.custom3 = sOpt; - sOpt = extractModeDefaults(fx, SET_F("mp12")); if (sOpt >= 0) seg.map1D2D = sOpt & 0x03; - sOpt = extractModeDefaults(fx, SET_F("ssim")); if (sOpt >= 0) seg.soundSim = sOpt & 0x07; + sOpt = extractModeDefaults(fx, SET_F("mp12")); if (sOpt >= 0) seg.map1D2D = sOpt & 0x07; + sOpt = extractModeDefaults(fx, SET_F("ssim")); if (sOpt >= 0) seg.soundSim = sOpt & 0x03; sOpt = extractModeDefaults(fx, "rev"); if (sOpt >= 0) seg.reverse = (bool)sOpt; sOpt = extractModeDefaults(fx, SET_F("mi")); if (sOpt >= 0) seg.mirror = (bool)sOpt; // NOTE: setting this option is a risky business sOpt = extractModeDefaults(fx, SET_F("rY")); if (sOpt >= 0) seg.reverse_y = (bool)sOpt; @@ -238,8 +238,8 @@ void deserializeSegment(JsonObject elem, byte it, byte presetId) strip.setBrightness(scaledBri(bri), true); // freeze and init to black - if (!seg.getOption(SEG_OPTION_FREEZE)) { - seg.setOption(SEG_OPTION_FREEZE, true); + if (!seg.freeze) { + seg.freeze = true; seg.fill(BLACK); } @@ -285,7 +285,7 @@ void deserializeSegment(JsonObject elem, byte it, byte presetId) } // send UDP if not in preset and something changed that is not just selection // send UDP if something changed that is not just selection or segment power/opacity - if ((seg.differs(prev) & 0x7E) && seg.getOption(SEG_OPTION_ON)==prev.getOption(SEG_OPTION_ON)) stateChanged = true; + if ((seg.differs(prev) & 0x7E) && seg.on == prev.on) stateChanged = true; } // deserializes WLED state (fileDoc points to doc object if called from web server) @@ -315,10 +315,10 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId) if (bri && !onBefore) { // unfreeze all segments when turning on for (size_t s=0; s < strip.getSegmentsNum(); s++) { - strip.getSegment(s).setOption(SEG_OPTION_FREEZE, false); + strip.getSegment(s).freeze = false; } if (realtimeMode && !realtimeOverride && useMainSegmentOnly) { // keep live segment frozen if live - strip.getMainSegment().setOption(SEG_OPTION_FREEZE, true); + strip.getMainSegment().freeze = true; } } @@ -371,7 +371,7 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId) realtimeOverride = root[F("lor")] | realtimeOverride; if (realtimeOverride > 2) realtimeOverride = REALTIME_OVERRIDE_ALWAYS; if (realtimeMode && useMainSegmentOnly) { - strip.getMainSegment().setOption(SEG_OPTION_FREEZE, !realtimeOverride); + strip.getMainSegment().freeze = !realtimeOverride; } if (root.containsKey("live")) { @@ -472,14 +472,14 @@ void serializeSegment(JsonObject& root, Segment& seg, byte id, bool forPreset, b } } if (!forPreset) root["len"] = seg.stop - seg.start; - root["grp"] = seg.grouping; + root["grp"] = seg.grouping; root[F("spc")] = seg.spacing; - root[F("of")] = seg.offset; - root["on"] = seg.getOption(SEG_OPTION_ON); - root["frz"] = seg.getOption(SEG_OPTION_FREEZE); - byte segbri = seg.opacity; - root["bri"] = (segbri) ? segbri : 255; - root["cct"] = seg.cct; + root[F("of")] = seg.offset; + root["on"] = seg.on; + root["frz"] = seg.freeze; + byte segbri = seg.opacity; + root["bri"] = (segbri) ? segbri : 255; + root["cct"] = seg.cct; if (segmentBounds && seg.name != nullptr) root["n"] = reinterpret_cast(seg.name); //not good practice, but decreases required JSON buffer @@ -509,12 +509,12 @@ void serializeSegment(JsonObject& root, Segment& seg, byte id, bool forPreset, b root[F("c2")] = seg.custom2; root[F("c3")] = seg.custom3; root[F("sel")] = seg.isSelected(); - root["rev"] = seg.getOption(SEG_OPTION_REVERSED); - root[F("mi")] = seg.getOption(SEG_OPTION_MIRROR); + root["rev"] = seg.reverse; + root[F("mi")] = seg.mirror; if (strip.isMatrix) { - root[F("rY")] = seg.getOption(SEG_OPTION_REVERSED_Y); - root[F("mY")] = seg.getOption(SEG_OPTION_MIRROR_Y); - root[F("tp")] = seg.getOption(SEG_OPTION_TRANSPOSED); + root[F("rY")] = seg.reverse_y; + root[F("mY")] = seg.mirror_y; + root[F("tp")] = seg.transpose; } root[F("o1")] = seg.check1; root[F("o2")] = seg.check2; diff --git a/wled00/set.cpp b/wled00/set.cpp index 7dd3d05f..417fe768 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -665,8 +665,8 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply) pos = req.indexOf(F("SV=")); //segment selected if (pos > 0) { byte t = getNumVal(&req, pos); - if (t == 2) for (uint8_t i = 0; i < strip.getSegmentsNum(); i++) strip.getSegment(i).setOption(SEG_OPTION_SELECTED, 0); // unselect other segments - selseg.setOption(SEG_OPTION_SELECTED, t); + if (t == 2) for (uint8_t i = 0; i < strip.getSegmentsNum(); i++) strip.getSegment(i).selected = false; // unselect other segments + selseg.selected = t; } // temporary values, write directly to segments, globals are updated by setValuesFromFirstSelectedSeg() @@ -705,15 +705,15 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply) strip.setSegment(selectedSeg, startI, stopI, grpI, spcI, UINT16_MAX, startY, stopY); pos = req.indexOf(F("RV=")); //Segment reverse - if (pos > 0) selseg.setOption(SEG_OPTION_REVERSED, req.charAt(pos+3) != '0'); + if (pos > 0) selseg.reverse = req.charAt(pos+3) != '0'; pos = req.indexOf(F("MI=")); //Segment mirror - if (pos > 0) selseg.setOption(SEG_OPTION_MIRROR, req.charAt(pos+3) != '0'); + if (pos > 0) selseg.mirror = req.charAt(pos+3) != '0'; pos = req.indexOf(F("SB=")); //Segment brightness/opacity if (pos > 0) { byte segbri = getNumVal(&req, pos); - selseg.setOption(SEG_OPTION_ON, segbri); + selseg.on = segbri; if (segbri) { selseg.setOpacity(segbri); } @@ -722,9 +722,9 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply) pos = req.indexOf(F("SW=")); //segment power if (pos > 0) { switch (getNumVal(&req, pos)) { - case 0: selseg.setOption(SEG_OPTION_ON, false); break; - case 1: selseg.setOption(SEG_OPTION_ON, true); break; - default: selseg.setOption(SEG_OPTION_ON, !selseg.getOption(SEG_OPTION_ON)); break; + case 0: selseg.on = false; break; + case 1: selseg.on = true; break; + default: selseg.on = !selseg.on; break; } } @@ -981,7 +981,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply) realtimeOverride = getNumVal(&req, pos); if (realtimeOverride > 2) realtimeOverride = REALTIME_OVERRIDE_ALWAYS; if (realtimeMode && useMainSegmentOnly) { - strip.getMainSegment().setOption(SEG_OPTION_FREEZE, !realtimeOverride); + strip.getMainSegment().freeze = !realtimeOverride; } } diff --git a/wled00/udp.cpp b/wled00/udp.cpp index aeef791c..9fe03e3a 100644 --- a/wled00/udp.cpp +++ b/wled00/udp.cpp @@ -149,7 +149,7 @@ void realtimeLock(uint32_t timeoutMs, byte md) Segment& mainseg = strip.getMainSegment(); start = mainseg.start; stop = mainseg.stop; - mainseg.setOption(SEG_OPTION_FREEZE, true); + mainseg.freeze = true; } else { start = 0; stop = strip.getLengthTotal(); @@ -159,7 +159,7 @@ void realtimeLock(uint32_t timeoutMs, byte md) // if WLED was off and using main segment only, freeze non-main segments so they stay off if (useMainSegmentOnly && bri == 0) { for (size_t s=0; s < strip.getSegmentsNum(); s++) { - strip.getSegment(s).setOption(SEG_OPTION_FREEZE, true); + strip.getSegment(s).freeze = true; } } } @@ -186,7 +186,7 @@ void exitRealtime() { realtimeMode = REALTIME_MODE_INACTIVE; // inform UI immediately realtimeIP[0] = 0; if (useMainSegmentOnly) { // unfreeze live segment again - strip.getMainSegment().setOption(SEG_OPTION_FREEZE, false); + strip.getMainSegment().freeze = false; } updateInterfaces(CALL_MODE_WS_SEND); } diff --git a/wled00/wled_eeprom.cpp b/wled00/wled_eeprom.cpp index 1fec4e8a..0f2194be 100644 --- a/wled00/wled_eeprom.cpp +++ b/wled00/wled_eeprom.cpp @@ -414,10 +414,10 @@ void deEEP() { for (byte j = 0; j < numChannels; j++) colX.add(EEPROM.read(memloc + j)); } - segObj["fx"] = EEPROM.read(i+10); - segObj[F("sx")] = EEPROM.read(i+11); - segObj[F("ix")] = EEPROM.read(i+16); - segObj["pal"] = EEPROM.read(i+17); + segObj["fx"] = EEPROM.read(i+10); + segObj[F("sx")] = EEPROM.read(i+11); + segObj[F("ix")] = EEPROM.read(i+16); + segObj["pal"] = EEPROM.read(i+17); } else { Segment* seg = strip.getSegments(); memcpy(seg, EEPROM.getDataPtr() +i+2, 240); @@ -425,7 +425,7 @@ void deEEP() { for (byte j = 0; j < strip.getMaxSegments(); j++) { strip.getSegment(j).opacity = 255; - strip.getSegment(j).setOption(SEG_OPTION_ON, 1); + strip.getSegment(j).on = true; } } serializeState(pObj, true, false, true); From 5927332a5f90fa9705d8ca2295a79e3a53ce1800 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Fri, 19 Aug 2022 21:25:44 +0200 Subject: [PATCH 07/13] UI enhancement in PWM Fan usermod. --- usermods/PWM_fan/usermod_PWM_fan.h | 41 +++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/usermods/PWM_fan/usermod_PWM_fan.h b/usermods/PWM_fan/usermod_PWM_fan.h index 6dfe12fa..fec2e726 100644 --- a/usermods/PWM_fan/usermod_PWM_fan.h +++ b/usermods/PWM_fan/usermod_PWM_fan.h @@ -103,6 +103,7 @@ class PWMFanUsermod : public Usermod { // https://randomnerdtutorials.com/esp32-pwm-arduino-ide/ void initPWMfan(void) { if (pwmPin < 0 || !pinManager.allocatePin(pwmPin, true, PinOwner::UM_Unspecified)) { + enabled = false; pwmPin = -1; return; } @@ -218,11 +219,23 @@ class PWMFanUsermod : public Usermod { * Below it is shown how this could be used for e.g. a light sensor */ void addToJsonInfo(JsonObject& root) { + JsonObject user = root["u"]; + if (user.isNull()) user = root.createNestedObject("u"); + + JsonArray infoArr = user.createNestedArray(FPSTR(_name)); + String uiDomString = F(""); + infoArr.add(uiDomString); + if (enabled) { - JsonObject user = root["u"]; - if (user.isNull()) user = root.createNestedObject("u"); -// if (!tempUM) { - JsonArray infoArr = user.createNestedArray(F("Fan speed [%]")); + JsonArray infoArr = user.createNestedArray(F("Manual")); String uiDomString = F("
"); // infoArr.add(uiDomString); -// } + + JsonArray data = user.createNestedArray(F("Speed")); if (tachoPin >= 0) { - JsonArray data = user.createNestedArray(FPSTR(_name)); data.add(last_rpm); data.add(F("rpm")); } else { - JsonArray data = user.createNestedArray(FPSTR(_name)); if (lockFan) data.add(F("locked")); else data.add(F("auto")); } @@ -256,14 +268,19 @@ class PWMFanUsermod : public Usermod { * Values in the state object may be modified by connected clients */ void readFromJsonState(JsonObject& root) { - if (!initDone || !enabled) return; // prevent crash on boot applyPreset() + if (!initDone) return; // prevent crash on boot applyPreset() JsonObject usermod = root[FPSTR(_name)]; if (!usermod.isNull()) { - if (!usermod[FPSTR(_speed)].isNull() && usermod[FPSTR(_speed)].is()) { - int pwmValuePct = usermod[FPSTR(_speed)].as(); - updateFanSpeed((MAX(0,MIN(100,pwmValuePct)) * 255) / 100); + if (usermod[FPSTR(_enabled)].is()) { + enabled = usermod[FPSTR(_enabled)].as(); + if (!enabled) updateFanSpeed(0); } - if (!usermod[FPSTR(_lock)].isNull() && usermod[FPSTR(_lock)].is()) { + if (enabled && !usermod[FPSTR(_speed)].isNull() && usermod[FPSTR(_speed)].is()) { + pwmValuePct = usermod[FPSTR(_speed)].as(); + updateFanSpeed((constrain(pwmValuePct,0,100) * 255) / 100); + if (pwmValuePct) lockFan = true; + } + if (enabled && !usermod[FPSTR(_lock)].isNull() && usermod[FPSTR(_lock)].is()) { lockFan = usermod[FPSTR(_lock)].as(); } } From bbc80498323bf350f8e571c4372742b848c44ef8 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Sat, 20 Aug 2022 22:14:54 +0200 Subject: [PATCH 08/13] The Right Thing to Do (makes GEQ look awesome) ... found that stupid commit messages get more attention ;-) - use 22050 Hz for sampling, as it is a standard frequency. I think this is the best choise. - redesigned the GEQ channels (fftResult[]) for 22Khz, based on channels found on old HiFi equalizer equipment. 1Kzh is now at the center; Bass/Trebble channels are using 1/4 on left/right side respectively - similar to real equalizers. Looks nice :-) - adjusted effects that use FFT_MajorPeak so that the maximum frequency is supported. --- usermods/audioreactive/audio_reactive.h | 41 ++++++++++++++++++++----- wled00/FX.cpp | 31 ++++++++++++------- 2 files changed, 53 insertions(+), 19 deletions(-) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index 6607d7c5..93a3ca25 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -47,11 +47,12 @@ constexpr i2s_port_t I2S_PORT = I2S_NUM_0; constexpr int BLOCK_SIZE = 128; -//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 = 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 -#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!! +//#define FFT_MIN_CYCLE 22 // minimum time before FFT task is repeated. Use with 20Khz sampling +#define FFT_MIN_CYCLE 18 // minimum time before FFT task is repeated. Use with 22Khz sampling // globals static uint8_t inputLevel = 128; // UI slider value @@ -65,7 +66,7 @@ static bool limiterOn = true; // bool: enable / disable dynamics 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 // user settable options for FFTResult scaling -static uint8_t FFTScalingMode = 2; // 0 none; 1 optimized logarithmic; 2 optimized linear +static uint8_t FFTScalingMode = 3; // 0 none; 1 optimized logarithmic; 2 optimized linear; 3 optimized sqare root // // AGC presets @@ -121,7 +122,10 @@ static float vReal[samplesFFT] = {0.0f}; static float vImag[samplesFFT] = {0.0f}; static float fftBin[samplesFFT_2] = {0.0f}; -#define FFT_DOWNSCALE 0.65f // downscaling factor for FFT results - "Flat-Top" window +// the following are observed values, supported by a bit of "educated guessing" +//#define FFT_DOWNSCALE 0.65f // 20kHz - downscaling factor for FFT results - "Flat-Top" window @20Khz, old freq channels + +#define FFT_DOWNSCALE 0.46f // downscaling factor for FFT results - for "Flat-Top" window @22Khz, new freq channels #define LOG_256 5.54517744 #ifdef UM_AUDIOREACTIVE_USE_NEW_FFT @@ -269,7 +273,7 @@ void FFTcode(void * parameter) * Multiplier = 1.320367784 */ if (sampleAvg > 1) { // noise gate open - // Range +#if 0 // Range fftCalc[ 0] = fftAddAvg(2,4); // 60 - 100 fftCalc[ 1] = fftAddAvg(4,5); // 80 - 120 fftCalc[ 2] = fftAddAvg(5,7); // 100 - 160 @@ -286,6 +290,26 @@ void FFTcode(void * parameter) fftCalc[13] = fftAddAvg(111,147); // 2220 - 2960 fftCalc[14] = fftAddAvg(147,194); // 2940 - 3900 fftCalc[15] = fftAddAvg(194,250); // 3880 - 5000 // avoid the last 5 bins, which are usually inaccurate +#else + // optimized for 22050 Hz by softhack007 bins frequency range + fftCalc[ 0] = fftAddAvg(1,2); // 1 43 - 86 sub-bass + fftCalc[ 1] = fftAddAvg(2,3); // 1 86 - 129 bass + fftCalc[ 2] = fftAddAvg(3,5); // 2 129 - 216 bass + fftCalc[ 3] = fftAddAvg(5,7); // 2 216 - 301 bass + midrange + fftCalc[ 4] = fftAddAvg(7,10); // 3 301 - 430 midrange + fftCalc[ 5] = fftAddAvg(10,13); // 3 430 - 560 midrange + fftCalc[ 6] = fftAddAvg(13,19); // 5 560 - 818 midrange + fftCalc[ 7] = fftAddAvg(19,26); // 7 818 - 1120 midrange -- 1Khz should always be the center ! + fftCalc[ 8] = fftAddAvg(26,33); // 7 1120 - 1421 midrange + fftCalc[ 9] = fftAddAvg(33,44); // 9 1421 - 1895 midrange + fftCalc[10] = fftAddAvg(44,56); // 12 1895 - 2412 midrange + high mid + fftCalc[11] = fftAddAvg(56,70); // 14 2412 - 3015 high mid + fftCalc[12] = fftAddAvg(70,86); // 16 3015 - 3704 high mid + fftCalc[13] = fftAddAvg(86,104); // 18 3704 - 4479 high mid + fftCalc[14] = fftAddAvg(104,165) * 0.88f; // 61 4479 - 7106 high mid + high -- with slight damping + fftCalc[15] = fftAddAvg(165,215) * 0.70f; // 50 7106 - 9259 high -- with some damping + // don't use the last bins from 216 to 255. They are usually contaminated by aliasing (aka noise) +#endif } else { // noise gate closed for (int i=0; i < 16; i++) { @@ -350,7 +374,7 @@ void FFTcode(void * parameter) if (currentResult > 1.0) currentResult = sqrtf(currentResult); else currentResult = 0.0; // special handling, because sqrt(0) = undefined - currentResult *= 0.85f + (float(i)/5.0f); // extra up-scaling for high frequencies + currentResult *= 0.85f + (float(i)/4.5f); // extra up-scaling for high frequencies currentResult = mapf(currentResult, 0.0, 16.0, 0.0, 255.0); // map [sqrt(1) ... sqrt(256)] to [0 ... 255] break; @@ -872,7 +896,8 @@ class AudioReactive : public Usermod { my_magnitude = fmaxf(receivedPacket->FFT_Magnitude, 0.0f); FFT_Magnitude = my_magnitude; - FFT_MajorPeak = fmaxf(receivedPacket->FFT_MajorPeak, 1.0f); + FFT_MajorPeak = constrain(receivedPacket->FFT_MajorPeak, 1.0f, 11050.0f); // restrict value to range expected by effects + //DEBUGSR_PRINTLN("Finished parsing UDP Sync Packet"); haveFreshData = true; } diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 2ba78c89..aee00737 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -5938,7 +5938,7 @@ static const char _data_FX_MODE_2DDRIFTROSE[] PROGMEM = "Drift Rose@Fade,Blur;;; uint8_t *binNum = (uint8_t*)&SEGENV.aux1, *maxVol = (uint8_t*)(&SEGENV.aux1+1); // just in case assignment bool samplePeak = false; - float FFT_MajorPeak = 0.0; + float FFT_MajorPeak = 1.0; uint8_t *fftResult = nullptr; float *fftBin = nullptr; um_data_t *um_data; @@ -5958,6 +5958,15 @@ static const char _data_FX_MODE_2DDRIFTROSE[] PROGMEM = "Drift Rose@Fade,Blur;;; } */ +// a few constants needed for AudioReactive effects +// for 22Khz sampling +#define MAX_FREQUENCY 11025 // sample frequency / 2 (as per Nyquist criterion) +#define MAX_FREQ_LOG10 4.04238f // log10(MAX_FREQUENCY) + +// for 20Khz sampling +//#define MAX_FREQUENCY 10240 // sample frequency / 2 (as per Nyquist criterion) +//#define MAX_FREQ_LOG10 4.0103f // log10(MAX_FREQUENCY) + ///////////////////////////////// // * Ripple Peak // @@ -6724,12 +6733,12 @@ uint16_t mode_freqmap(void) { // Map FFT_MajorPeak to SEGLEN. SEGMENT.fade_out(SEGMENT.speed); // 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. - int locn = (log10f((float)FFT_MajorPeak) - 1.78f) * (float)SEGLEN/(4.0102f-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/(MAX_FREQ_LOG10 - 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. - 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 pixCol = (log10f(FFT_MajorPeak) - 1.78f) * 255.0f/(MAX_FREQ_LOG10 - 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)); @@ -6769,7 +6778,7 @@ uint16_t mode_freqmatrix(void) { // Freqmatrix. By Andreas Plesch CRGB color = CRGB::Black; //if (FFT_MajorPeak > 5120) FFT_MajorPeak = 0; - if (FFT_MajorPeak > 10240) FFT_MajorPeak = 0; + if (FFT_MajorPeak > MAX_FREQUENCY) FFT_MajorPeak = 1; // 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 @@ -6777,8 +6786,8 @@ uint16_t mode_freqmatrix(void) { // Freqmatrix. By Andreas Plesch if (FFT_MajorPeak < 80) { color = CRGB::Black; } else { - int upperLimit = 20 * SEGMENT.custom2; - int lowerLimit = 2 * SEGMENT.custom1; + int upperLimit = 80 + 42 * SEGMENT.custom2; + int lowerLimit = 80 + 3 * SEGMENT.custom1; int i = lowerLimit!=upperLimit ? map(FFT_MajorPeak, lowerLimit, upperLimit, 0, 255) : FFT_MajorPeak; uint16_t b = 255 * intensity; if (b > 255) b = 255; @@ -6818,7 +6827,7 @@ 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.78f) * 255.0f/(4.0102f-1.78f); // Scale log10 of frequency values to the 255 colour index. + uint8_t pixCol = (log10f(FFT_MajorPeak) - 1.78f) * 255.0f/(MAX_FREQ_LOG10 - 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)); } @@ -6869,7 +6878,7 @@ uint16_t mode_freqwave(void) { // Freqwave. By Andreas Pleschun CRGB color = 0; //if (FFT_MajorPeak > 5120) FFT_MajorPeak = 0.0f; - if (FFT_MajorPeak > 10240) FFT_MajorPeak = 0.0f; + if (FFT_MajorPeak > MAX_FREQUENCY) FFT_MajorPeak = 1.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 @@ -6877,8 +6886,8 @@ uint16_t mode_freqwave(void) { // Freqwave. By Andreas Pleschun if (FFT_MajorPeak < 80) { color = CRGB::Black; } else { - int upperLimit = 20 * SEGMENT.custom2; - int lowerLimit = 2 * SEGMENT.custom1; + int upperLimit = 80 + 42 * SEGMENT.custom2; + int lowerLimit = 80 + 3 * SEGMENT.custom1; int i = lowerLimit!=upperLimit ? map(FFT_MajorPeak, lowerLimit, upperLimit, 0, 255) : FFT_MajorPeak; uint16_t b = 255.0 * intensity; if (b > 255) b=255; @@ -6927,7 +6936,7 @@ uint16_t mode_gravfreq(void) { // Gravfreq. By Andrew Tuline. for (int i=0; i Date: Sun, 21 Aug 2022 09:51:15 +0200 Subject: [PATCH 09/13] Minor cleanup & fix for connected(). --- usermods/audioreactive/audio_reactive.h | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index 93a3ca25..7ba25f3b 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -185,16 +185,12 @@ void FFTcode(void * parameter) 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. + vTaskDelayUntil( &xLastWakeTime, xFrequency); // release CPU, and let I2S fill its buffers // 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 - vTaskDelayUntil( &xLastWakeTime, xFrequency); // release CPU, by doing nothing for FFT_MIN_CYCLE millis 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 @@ -401,10 +397,9 @@ void FFTcode(void * parameter) } #endif - //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; + micDataReal = maxSample2; // do we really need this? FFT now takes only about 2ms so no need for this } // for(;;) } // FFTcode() @@ -1008,7 +1003,7 @@ class AudioReactive : public Usermod { */ void connected() { - if (audioSyncPort > 0 || (audioSyncEnabled & 0x03)) { + if (audioSyncPort > 0 && (audioSyncEnabled & 0x03)) { #ifndef ESP8266 udpSyncConnected = fftUdp.beginMulticast(IPAddress(239, 0, 0, 1), audioSyncPort); #else From d053bc562fadae507ab6ea9c9182e1a741093fae Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Sun, 21 Aug 2022 13:10:16 +0200 Subject: [PATCH 10/13] code cleanup, few optimizations, and fixing more overflows - code cleanup in audio_reactive.h - fixing some more under/overflows in fx.cpp --- usermods/audioreactive/audio_reactive.h | 119 ++++++------------------ wled00/FX.cpp | 33 ++++--- 2 files changed, 47 insertions(+), 105 deletions(-) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index 7ba25f3b..ea1ca79f 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -23,11 +23,9 @@ // 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 (including MIC_LOGGER) +// #define SR_DEBUG // generic SR DEBUG messages // #define NO_MIC_LOGGER // exclude MIC_LOGGER from SR_DEBUG -// hackers corner - #ifdef SR_DEBUG #define DEBUGSR_PRINT(x) Serial.print(x) #define DEBUGSR_PRINTLN(x) Serial.println(x) @@ -37,10 +35,6 @@ #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" @@ -49,10 +43,11 @@ constexpr i2s_port_t I2S_PORT = I2S_NUM_0; constexpr int BLOCK_SIZE = 128; 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 +//constexpr int SAMPLE_RATE = 10240; // Base sample rate in Hz - previous default. Physical sample time -> 50ms -//#define FFT_MIN_CYCLE 22 // minimum time before FFT task is repeated. Use with 20Khz sampling #define FFT_MIN_CYCLE 18 // minimum time before FFT task is repeated. Use with 22Khz sampling +//#define FFT_MIN_CYCLE 22 // minimum time before FFT task is repeated. Use with 20Khz sampling +//#define FFT_MIN_CYCLE 44 // minimum time before FFT task is repeated. Use with 10Khz sampling // globals static uint8_t inputLevel = 128; // UI slider value @@ -204,26 +199,19 @@ void FFTcode(void * parameter) } #endif - 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 - for (int i=0; i < halfSamplesFFT; i++) { + //const int halfSamplesFFT = samplesFFT / 2; // samplesFFT divided by 2 + float maxSample = 0.0f; // max sample from FFT batch + for (int i=0; i < samplesFFT; i++) { // set imaginary parts to 0 vImag[i] = 0; // pick our our current mic sample - we take the max value from all samples that go into FFT if ((vReal[i] <= (INT16_MAX - 1024)) && (vReal[i] >= (INT16_MIN + 1024))) //skip extreme values - normally these are artefacts - if (fabsf((float)vReal[i]) > maxSample1) maxSample1 = fabsf((float)vReal[i]); + if (fabsf((float)vReal[i]) > maxSample) maxSample = fabsf((float)vReal[i]); } - for (int i=halfSamplesFFT; i < samplesFFT; i++) { - // set imaginary parts to 0 - vImag[i] = 0; - // pick our our current mic sample - we take the max value from all samples that go into FFT - if ((vReal[i] <= (INT16_MAX - 1024)) && (vReal[i] >= (INT16_MIN + 1024))) //skip extreme values - normally these are artefacts - if (fabsf((float)vReal[i]) > maxSample2) maxSample2 = fabsf((float)vReal[i]); - } - // release first sample to volume reactive effects - micDataReal = maxSample1; + // release sample to volume reactive effects + micDataReal = maxSample; // doing this early allows filters (getSample() and agcAvg()) to run on latest values - we'll have up-to-date gain and noise gate values when FFT is done + // run FFT #ifdef UM_AUDIOREACTIVE_USE_NEW_FFT FFT.dcRemoval(); // remove DC offset FFT.windowing( FFTWindow::Flat_top, FFTDirection::Forward); // Weigh data using "Flat Top" function - better amplitude accuracy @@ -246,11 +234,11 @@ void FFTcode(void * parameter) // #ifdef UM_AUDIOREACTIVE_USE_NEW_FFT - FFT.majorPeak(FFT_MajorPeak, FFT_Magnitude); // let the effects know which freq was most dominant + FFT.majorPeak(FFT_MajorPeak, FFT_Magnitude); // let the effects know which freq was most dominant #else - FFT.MajorPeak(&FFT_MajorPeak, &FFT_Magnitude); // let the effects know which freq was most dominant + FFT.MajorPeak(&FFT_MajorPeak, &FFT_Magnitude); // let the effects know which freq was most dominant #endif - FFT_MajorPeak = constrain(FFT_MajorPeak, 1.0f, 10240.0f); + FFT_MajorPeak = constrain(FFT_MajorPeak, 1.0f, 11025.0f); // restrict value to range expected by effects 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 = fabsf(vReal[i]); // just to be sure - values in fft bins should be positive any way @@ -258,18 +246,17 @@ void FFTcode(void * parameter) } // for() -/* This FFT post processing is a DIY endeavour. What we really need is someone with sound engineering expertise to do a great job here AND most importantly, that the animations look GREAT as a result. - * - * - * Andrew's updated mapping of 256 bins down to the 16 result bins with Sample Freq = 10240, samplesFFT = 512 and some overlap. - * Based on testing, the lowest/Start frequency is 60 Hz (with bin 3) and a highest/End frequency of 5120 Hz in bin 255. - * Now, Take the 60Hz and multiply by 1.320367784 to get the next frequency and so on until the end. Then detetermine the bins. - * End frequency = Start frequency * multiplier ^ 16 - * Multiplier = (End frequency/ Start frequency) ^ 1/16 - * Multiplier = 1.320367784 - */ if (sampleAvg > 1) { // noise gate open -#if 0 // Range +#if 0 + /* This FFT post processing is a DIY endeavour. What we really need is someone with sound engineering expertise to do a great job here AND most importantly, that the animations look GREAT as a result. + * + * Andrew's updated mapping of 256 bins down to the 16 result bins with Sample Freq = 10240, samplesFFT = 512 and some overlap. + * Based on testing, the lowest/Start frequency is 60 Hz (with bin 3) and a highest/End frequency of 5120 Hz in bin 255. + * Now, Take the 60Hz and multiply by 1.320367784 to get the next frequency and so on until the end. Then detetermine the bins. + * End frequency = Start frequency * multiplier ^ 16 + * Multiplier = (End frequency/ Start frequency) ^ 1/16 + * Multiplier = 1.320367784 + */ // Range fftCalc[ 0] = fftAddAvg(2,4); // 60 - 100 fftCalc[ 1] = fftAddAvg(4,5); // 80 - 120 fftCalc[ 2] = fftAddAvg(5,7); // 100 - 160 @@ -287,7 +274,8 @@ void FFTcode(void * parameter) fftCalc[14] = fftAddAvg(147,194); // 2940 - 3900 fftCalc[15] = fftAddAvg(194,250); // 3880 - 5000 // avoid the last 5 bins, which are usually inaccurate #else - // optimized for 22050 Hz by softhack007 bins frequency range + /* new mapping, optimized for 22050 Hz by softhack007 */ + // bins frequency range fftCalc[ 0] = fftAddAvg(1,2); // 1 43 - 86 sub-bass fftCalc[ 1] = fftAddAvg(2,3); // 1 86 - 129 bass fftCalc[ 2] = fftAddAvg(3,5); // 2 129 - 216 bass @@ -397,10 +385,6 @@ void FFTcode(void * parameter) } #endif - // release second sample to volume reactive effects. - // Releasing a second sample now effectively doubles the "sample rate" - micDataReal = maxSample2; // do we really need this? FFT now takes only about 2ms so no need for this - } // for(;;) } // FFTcode() @@ -816,9 +800,6 @@ class AudioReactive : public Usermod { audioSyncPacket transmitData; strncpy_P(transmitData.header, PSTR(UDP_SYNC_HEADER), 6); - - //transmitData.sampleRaw = volumeRaw; - //transmitData.sampleSmth = volumeSmth; // transmit samples that were not modified by limitSampleDynamics() transmitData.sampleRaw = (soundAgc) ? rawSampleAgc: sampleRaw; transmitData.sampleSmth = (soundAgc) ? sampleAgc : sampleAvg; @@ -848,7 +829,6 @@ class AudioReactive : public Usermod { bool receiveAudioData() // check & process new data. return TRUE in case that new audio data was received. { if (!udpSyncConnected) return false; - //DEBUGSR_PRINTLN("Checking for UDP Microphone Packet"); bool haveFreshData = false; size_t packetSize = fftUdp.parsePacket(); if (packetSize > 5) { @@ -891,7 +871,7 @@ class AudioReactive : public Usermod { my_magnitude = fmaxf(receivedPacket->FFT_Magnitude, 0.0f); FFT_Magnitude = my_magnitude; - FFT_MajorPeak = constrain(receivedPacket->FFT_MajorPeak, 1.0f, 11050.0f); // restrict value to range expected by effects + FFT_MajorPeak = constrain(receivedPacket->FFT_MajorPeak, 1.0f, 11025.0f); // restrict value to range expected by effects //DEBUGSR_PRINTLN("Finished parsing UDP Sync Packet"); haveFreshData = true; @@ -1101,51 +1081,6 @@ class AudioReactive : public Usermod { if (volumeSmth < 1 ) my_magnitude = 0.001f; // noise gate closed - mute limitSampleDynamics(); // optional - makes volumeSmth very smooth and fluent - -#if 0 - /* currently this is _not_ working. Code relies on "musical note" symbol as second char of the effect name */ - #error I told you its not working right now - // 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 - agcEffect = (lineBuffer[1] == 226 && lineBuffer[2] == 153); // && (lineBuffer[3] == 170 || lineBuffer[3] == 171 ) encoding of ♪ or ♫ - // agcEffect = (lineBuffer[4] == 240 && lineBuffer[5] == 159 && lineBuffer[6] == 142 && lineBuffer[7] == 154 ); //encoding of 🎚 No clue why as not found here https://www.iemoji.com/view/emoji/918/objects/level-slider - lastMode = knownMode; - } - - // update inputLevel Slider based on current AGC gain - if ((soundAgc>0) && agcEffect) { - unsigned long now_time = millis(); - - // "user kick" feature - if user has moved the slider by at least 32 units, we "kick" AGC gain by 30% (up or down) - // only once in 3.5 seconds - if ( (lastMode == knownMode) - && (abs(last_user_inputLevel - inputLevel) > 31) - && (now_time - last_kick_time > 3500)) { - if (last_user_inputLevel > inputLevel) multAgc *= 0.60; // down -> reduce gain - if (last_user_inputLevel < inputLevel) multAgc *= 1.50; // up -> increase gain - last_kick_time = now_time; - } - - int new_user_inputLevel = 128.0f * multAgc; // scale AGC multiplier so that "1" is at 128 - if (multAgc > 1.0f) new_user_inputLevel = 128.0f * (((multAgc - 1.0f) / 4.0f) +1.0f); // compress range so we can show values up to 4 - new_user_inputLevel = MIN(MAX(new_user_inputLevel, 0),255); - - // update user interfaces - restrict frequency to avoid flooding UI's with small changes - if (( ((now_time - last_update_time > 3500) && (abs(new_user_inputLevel - inputLevel) > 2)) // small change - every 3.5 sec (max) - ||((now_time - last_update_time > 2200) && (abs(new_user_inputLevel - inputLevel) > 15)) // medium change - ||((now_time - last_update_time > 1200) && (abs(new_user_inputLevel - inputLevel) > 31))) // BIG change - every second - && !strip.isUpdating()) // don't interfere while strip is updating - { - inputLevel = new_user_inputLevel; // change of least 3 units -> update user variable - updateInterfaces(CALL_MODE_WS_SEND); // is this the correct way to notify UIs ? Yes says blazoncek - last_update_time = now_time; - last_user_inputLevel = new_user_inputLevel; - } - } -#endif } diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 6c797748..a57aaafb 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -5958,14 +5958,20 @@ static const char _data_FX_MODE_2DDRIFTROSE[] PROGMEM = "Drift Rose@Fade,Blur;;; } */ + // a few constants needed for AudioReactive effects + // for 22Khz sampling -#define MAX_FREQUENCY 11025 // sample frequency / 2 (as per Nyquist criterion) +#define MAX_FREQUENCY 11025 // sample frequency / 2 (as per Nyquist criterion) #define MAX_FREQ_LOG10 4.04238f // log10(MAX_FREQUENCY) // for 20Khz sampling -//#define MAX_FREQUENCY 10240 // sample frequency / 2 (as per Nyquist criterion) -//#define MAX_FREQ_LOG10 4.0103f // log10(MAX_FREQUENCY) +//#define MAX_FREQUENCY 10240 +//#define MAX_FREQ_LOG10 4.0103f + +// for 10Khz sampling +//#define MAX_FREQUENCY 5120 +//#define MAX_FREQ_LOG10 3.71f ///////////////////////////////// @@ -6719,7 +6725,7 @@ static const char _data_FX_MODE_DJLIGHT[] PROGMEM = "DJ Light@Speed;;;mp12=2,ssi //////////////////// uint16_t mode_freqmap(void) { // Map FFT_MajorPeak to SEGLEN. Would be better if a higher framerate. // Start frequency = 60 Hz and log10(60) = 1.78 - // End frequency = 5120 Hz and lo10(5120) = 3.71 + // End frequency = MAX_FREQUENCY in Hz and lo10(MAX_FREQUENCY) = MAX_FREQ_LOG10 um_data_t *um_data; if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { @@ -6732,13 +6738,13 @@ uint16_t mode_freqmap(void) { // Map FFT_MajorPeak to SEGLEN. SEGMENT.fade_out(SEGMENT.speed); -// 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. int locn = (log10f((float)FFT_MajorPeak) - 1.78f) * (float)SEGLEN/(MAX_FREQ_LOG10 - 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. uint16_t pixCol = (log10f(FFT_MajorPeak) - 1.78f) * 255.0f/(MAX_FREQ_LOG10 - 1.78f); // Scale log10 of frequency values to the 255 colour index. + if (FFT_MajorPeak < 61.0f) pixCol = 0; // handle underflow + 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)); @@ -6777,7 +6783,6 @@ uint16_t mode_freqmatrix(void) { // Freqmatrix. By Andreas Plesch CRGB color = CRGB::Black; - //if (FFT_MajorPeak > 5120) FFT_MajorPeak = 0; if (FFT_MajorPeak > MAX_FREQUENCY) FFT_MajorPeak = 1; // 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 @@ -6788,7 +6793,7 @@ uint16_t mode_freqmatrix(void) { // Freqmatrix. By Andreas Plesch } else { int upperLimit = 80 + 42 * SEGMENT.custom2; int lowerLimit = 80 + 3 * SEGMENT.custom1; - int i = lowerLimit!=upperLimit ? map(FFT_MajorPeak, lowerLimit, upperLimit, 0, 255) : FFT_MajorPeak; + uint8_t i = lowerLimit!=upperLimit ? map(FFT_MajorPeak, lowerLimit, upperLimit, 0, 255) : FFT_MajorPeak; // may under/overflow - so we enforce uint8_t uint16_t b = 255 * intensity; if (b > 255) b = 255; color = CHSV(i, 240, (uint8_t)b); // implicit conversion to RGB supplied by FastLED @@ -6826,8 +6831,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.78f) * 255.0f/(MAX_FREQ_LOG10 - 1.78f); // Scale log10 of frequency values to the 255 colour index. + if (FFT_MajorPeak < 61.0f) pixCol = 0; // handle underflow SEGMENT.setPixelColor(locn, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(SEGMENT.intensity+pixCol, false, PALETTE_SOLID_WRAP, 0), (int)my_magnitude)); } @@ -6877,7 +6882,6 @@ uint16_t mode_freqwave(void) { // Freqwave. By Andreas Pleschun CRGB color = 0; - //if (FFT_MajorPeak > 5120) FFT_MajorPeak = 0.0f; if (FFT_MajorPeak > MAX_FREQUENCY) FFT_MajorPeak = 1.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 @@ -6888,7 +6892,7 @@ uint16_t mode_freqwave(void) { // Freqwave. By Andreas Pleschun } else { int upperLimit = 80 + 42 * SEGMENT.custom2; int lowerLimit = 80 + 3 * SEGMENT.custom1; - int i = lowerLimit!=upperLimit ? map(FFT_MajorPeak, lowerLimit, upperLimit, 0, 255) : FFT_MajorPeak; + uint8_t i = lowerLimit!=upperLimit ? map(FFT_MajorPeak, lowerLimit, upperLimit, 0, 255) : FFT_MajorPeak; // may under/overflow - so we enforce uint8_t uint16_t b = 255.0 * intensity; if (b > 255) b=255; color = CHSV(i, 240, (uint8_t)b); // implicit conversion to RGB supplied by FastLED @@ -6924,7 +6928,7 @@ uint16_t mode_gravfreq(void) { // Gravfreq. By Andrew Tuline. float volumeSmth = *(float*) um_data->u_data[0]; if (FFT_MajorPeak < 1) FFT_MajorPeak = 1; // log10(0) is "forbidden" (throws exception) - SEGMENT.fade_out(240); + SEGMENT.fade_out(250); float segmentSampleAvg = volumeSmth * (float)SEGMENT.intensity / 255.0; segmentSampleAvg *= 0.125; // divide by 8, to compensate for later "sensitivty" upscaling @@ -7012,6 +7016,7 @@ uint16_t mode_rocktaves(void) { // Rocktaves. Same note from eac frTemp = fabs(frTemp * 2.1); // Fudge factors to compress octave range starting at 0 and going to 255; uint16_t i = map(beatsin8(8+octCount*4, 0, 255, 0, octCount*8), 0, 255, 0, SEGLEN-1); + i = constrain(i, 0, SEGLEN-1); SEGMENT.addPixelColor(i, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette((uint8_t)frTemp, false, PALETTE_SOLID_WRAP, 0), volTemp)); return FRAMETIME; @@ -7054,7 +7059,9 @@ uint16_t mode_waterfall(void) { // Waterfall. By: Andrew Tulin if (SEGENV.aux0 != secondHand) { // Triggered millis timing. SEGENV.aux0 = secondHand; - uint8_t pixCol = (log10f((float)FFT_MajorPeak) - 2.26f) * 177; // log10 frequency range is from 2.26 to 3.7. Let's scale accordingly. + //uint8_t pixCol = (log10f((float)FFT_MajorPeak) - 2.26f) * 177; // 10Khz sampling - log10 frequency range is from 2.26 (182hz) to 3.7 (5012hz). Let's scale accordingly. + uint8_t pixCol = (log10f(FFT_MajorPeak) - 2.26f) * 150; // 22Khz sampling - log10 frequency range is from 2.26 (182hz) to 3.967 (9260hz). Let's scale accordingly. + if (FFT_MajorPeak < 182.0f) pixCol = 0; // handle underflow if (samplePeak) { SEGMENT.setPixelColor(SEGLEN-1, CHSV(92,92,92)); From 720fae872014c1e6dbc2cafc8688ae98b5bebf3e Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Sun, 21 Aug 2022 19:15:42 +0200 Subject: [PATCH 11/13] Code sanitation. Default analog pin -1 --- usermods/audioreactive/audio_reactive.h | 164 +++++++++++------------- 1 file changed, 75 insertions(+), 89 deletions(-) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index ea1ca79f..ca8a3287 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -173,7 +173,6 @@ void FFTcode(void * parameter) // see https://www.freertos.org/vtaskdelayuntil.html const TickType_t xFrequency = FFT_MIN_CYCLE * portTICK_PERIOD_MS; - //const TickType_t xFrequency_2 = (FFT_MIN_CYCLE * portTICK_PERIOD_MS) / 2; for(;;) { TickType_t xLastWakeTime = xTaskGetTickCount(); @@ -208,8 +207,8 @@ void FFTcode(void * parameter) if ((vReal[i] <= (INT16_MAX - 1024)) && (vReal[i] >= (INT16_MIN + 1024))) //skip extreme values - normally these are artefacts if (fabsf((float)vReal[i]) > maxSample) maxSample = fabsf((float)vReal[i]); } - // release sample to volume reactive effects - micDataReal = maxSample; // doing this early allows filters (getSample() and agcAvg()) to run on latest values - we'll have up-to-date gain and noise gate values when FFT is done + // release sample to volume reactive effects (not really necessary as float FFT calculation takes only 2ms) + micDataReal = maxSample; // run FFT #ifdef UM_AUDIOREACTIVE_USE_NEW_FFT @@ -228,10 +227,6 @@ void FFTcode(void * parameter) FFT.Compute( FFT_FORWARD ); // Compute FFT FFT.ComplexToMagnitude(); // Compute magnitudes #endif - // - // 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 UM_AUDIOREACTIVE_USE_NEW_FFT FFT.majorPeak(FFT_MajorPeak, FFT_Magnitude); // let the effects know which freq was most dominant @@ -240,9 +235,9 @@ void FFTcode(void * parameter) #endif FFT_MajorPeak = constrain(FFT_MajorPeak, 1.0f, 11025.0f); // restrict value to range expected by effects - for (int i = 0; i < samplesFFT_2; 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 = fabsf(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. + fftBin[i] = t / 16.0f; // Reduce magnitude. Want end result to be linear and ~4096 max. } // for() @@ -294,7 +289,6 @@ void FFTcode(void * parameter) fftCalc[15] = fftAddAvg(165,215) * 0.70f; // 50 7106 - 9259 high -- with some damping // don't use the last bins from 216 to 255. They are usually contaminated by aliasing (aka noise) #endif - } else { // noise gate closed for (int i=0; i < 16; i++) { //fftCalc[i] *= 0.82f; // decay to zero @@ -316,7 +310,7 @@ void FFTcode(void * parameter) // 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] + fftAvg[i] = fftCalc[i] *0.75f + 0.25f*fftAvg[i]; // will need approx 2 cycles (50ms) for converging against fftCalc[i] else { // fall slow if (decayTime < 1000) fftAvg[i] = fftCalc[i]*0.22f + 0.78f*fftAvg[i]; // approx 5 cycles (225ms) for falling to zero else if (decayTime < 2000) fftAvg[i] = fftCalc[i]*0.17f + 0.83f*fftAvg[i]; // default - approx 9 cycles (225ms) for falling to zero @@ -336,29 +330,27 @@ void FFTcode(void * parameter) switch (FFTScalingMode) { case 1: // Logarithmic scaling - currentResult *= 0.42; // 42 is the answer ;-) - currentResult -= 8.0; // this skips the lowest row, giving some room for peaks - if (currentResult > 1.0) - currentResult = logf(currentResult); // log to base "e", which is the fastest log() function - else currentResult = 0.0; // special handling, because log(1) = 0; log(0) = undefined - currentResult *= 0.85f + (float(i)/18.0f); // extra up-scaling for high frequencies + currentResult *= 0.42; // 42 is the answer ;-) + currentResult -= 8.0; // this skips the lowest row, giving some room for peaks + if (currentResult > 1.0) currentResult = logf(currentResult); // log to base "e", which is the fastest log() function + else currentResult = 0.0; // special handling, because log(1) = 0; log(0) = undefined + currentResult *= 0.85f + (float(i)/18.0f); // extra up-scaling for high frequencies currentResult = mapf(currentResult, 0, LOG_256, 0, 255); // map [log(1) ... log(255)] to [0 ... 255] break; case 2: // Linear scaling - currentResult *= 0.30f; // needs a bit more damping, get stay below 255 - currentResult -= 4.0; // giving a bit more room for peaks + currentResult *= 0.30f; // needs a bit more damping, get stay below 255 + currentResult -= 4.0; // giving a bit more room for peaks if (currentResult < 1.0f) currentResult = 0.0f; - currentResult *= 0.85f + (float(i)/1.8f); // extra up-scaling for high frequencies + currentResult *= 0.85f + (float(i)/1.8f); // extra up-scaling for high frequencies break; case 3: // square root scaling currentResult *= 0.38f; currentResult -= 6.0f; - if (currentResult > 1.0) - currentResult = sqrtf(currentResult); - else currentResult = 0.0; // special handling, because sqrt(0) = undefined - currentResult *= 0.85f + (float(i)/4.5f); // extra up-scaling for high frequencies + if (currentResult > 1.0) currentResult = sqrtf(currentResult); + else currentResult = 0.0; // special handling, because sqrt(0) = undefined + currentResult *= 0.85f + (float(i)/4.5f); // extra up-scaling for high frequencies currentResult = mapf(currentResult, 0.0, 16.0, 0.0, 255.0); // map [sqrt(1) ... sqrt(256)] to [0 ... 255] break; @@ -394,7 +386,7 @@ class AudioReactive : public Usermod { private: #ifndef AUDIOPIN - int8_t audioPin = 36; + int8_t audioPin = -1; #else int8_t audioPin = AUDIOPIN; #endif @@ -615,7 +607,6 @@ class AudioReactive : public Usermod { if((fabs(sampleReal) < 2.0f) || (sampleMax < 1.0f)) { // MIC signal is "squelched" - deliver silence - //multAgcTemp = multAgc; // keep old control value (no change) tmpAgc = 0; // we need to "spin down" the intgrated error buffer if (fabs(control_integrated) < 0.01) control_integrated = 0.0; @@ -623,27 +614,26 @@ class AudioReactive : public Usermod { } else { // compute new setpoint if (tmpAgc <= agcTarget0Up[AGC_preset]) - multAgcTemp = agcTarget0[AGC_preset] / sampleMax; // Make the multiplier so that sampleMax * multiplier = first setpoint + multAgcTemp = agcTarget0[AGC_preset] / sampleMax; // Make the multiplier so that sampleMax * multiplier = first setpoint else - multAgcTemp = agcTarget1[AGC_preset] / sampleMax; // Make the multiplier so that sampleMax * multiplier = second setpoint + multAgcTemp = agcTarget1[AGC_preset] / sampleMax; // Make the multiplier so that sampleMax * multiplier = second setpoint } // limit amplification - //multAgcTemp = constrain(multAgcTemp, 0.015625f, 32.0f); // 1/64 < multAgcTemp < 32 if (multAgcTemp > 32.0f) multAgcTemp = 32.0f; if (multAgcTemp < 1.0f/64.0f) multAgcTemp = 1.0f/64.0f; // compute error terms control_error = multAgcTemp - lastMultAgc; - if (((multAgcTemp > 0.085f) && (multAgcTemp < 6.5f)) //integrator anti-windup by clamping - && (multAgc*sampleMax < agcZoneStop[AGC_preset])) //integrator ceiling (>140% of max) - control_integrated += control_error * 0.002 * 0.25; // 2ms = intgration time; 0.25 for damping + if (((multAgcTemp > 0.085f) && (multAgcTemp < 6.5f)) //integrator anti-windup by clamping + && (multAgc*sampleMax < agcZoneStop[AGC_preset])) //integrator ceiling (>140% of max) + control_integrated += control_error * 0.002 * 0.25; // 2ms = intgration time; 0.25 for damping else - control_integrated *= 0.9; // spin down that beasty integrator + control_integrated *= 0.9; // spin down that beasty integrator // apply PI Control - tmpAgc = sampleReal * lastMultAgc; // check "zone" of the signal using previous gain - if ((tmpAgc > agcZoneHigh[AGC_preset]) || (tmpAgc < soundSquelch + agcZoneLow[AGC_preset])) { // upper/lower emergy zone + tmpAgc = sampleReal * lastMultAgc; // check "zone" of the signal using previous gain + if ((tmpAgc > agcZoneHigh[AGC_preset]) || (tmpAgc < soundSquelch + agcZoneLow[AGC_preset])) { // upper/lower emergy zone multAgcTemp = lastMultAgc + agcFollowFast[AGC_preset] * agcControlKp[AGC_preset] * control_error; multAgcTemp += agcFollowFast[AGC_preset] * agcControlKi[AGC_preset] * control_integrated; } else { // "normal zone" @@ -673,9 +663,6 @@ class AudioReactive : public Usermod { else sampleAgc += agcSampleSmooth[AGC_preset] * (tmpAgc - sampleAgc); // smooth path - //userVar0 = sampleAvg * 4; - //if (userVar0 > 255) userVar0 = 255; - last_soundAgc = soundAgc; } // agcAvg() @@ -711,7 +698,7 @@ class AudioReactive : public Usermod { 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 + micIn -= micLev; // Let's center it to 0 now // 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); @@ -725,12 +712,12 @@ class AudioReactive : public Usermod { sampleAdj = tmpSample * sampleGain / 40.0f * inputLevel/128.0f + tmpSample / 16.0f; // Adjust the gain. with inputLevel adjustment sampleReal = tmpSample; - sampleAdj = fmax(fmin(sampleAdj, 255), 0); // Question: why are we limiting the value to 8 bits ??? - sampleRaw = (int16_t)sampleAdj; // ONLY update sample ONCE!!!! + sampleAdj = fmax(fmin(sampleAdj, 255), 0); // Question: why are we limiting the value to 8 bits ??? + sampleRaw = (int16_t)sampleAdj; // ONLY update sample ONCE!!!! // keep "peak" sample, but decay value if current sample is below peak if ((sampleMax < sampleReal) && (sampleReal > 0.5f)) { - sampleMax = sampleMax + 0.5f * (sampleReal - sampleMax); // new peak - with some filtering + sampleMax = sampleMax + 0.5f * (sampleReal - sampleMax); // new peak - with some filtering } else { if ((multAgc*sampleMax > agcZoneStop[AGC_preset]) && (soundAgc > 0)) sampleMax += 0.5f * (sampleReal - sampleMax); // over AGC Zone - get back quickly @@ -751,13 +738,12 @@ class AudioReactive : public Usermod { //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 ((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. + 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(); udpSamplePeak = true; - //userVar1 = samplePeak; } } // getSample() @@ -771,7 +757,7 @@ class AudioReactive : public Usermod { static unsigned long last_time = 0; static float last_volumeSmth = 0.0f; - if(limiterOn == false) return; + 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 @@ -1043,7 +1029,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 + if (!audioSource->isInitialized()) disableSoundProcessing = true; // no audio source // Only run the sampling code IF we're not in Receive mode or realtime mode @@ -1070,7 +1056,7 @@ class AudioReactive : public Usermod { 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 + lastUMRun = t_now; // update time keeping // update samples for effects (raw, smooth) volumeSmth = (soundAgc) ? sampleAgc : sampleAvg; @@ -1078,7 +1064,7 @@ class AudioReactive : public Usermod { // 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 + if (volumeSmth < 1 ) my_magnitude = 0.001f; // noise gate closed - mute limitSampleDynamics(); // optional - makes volumeSmth very smooth and fluent } @@ -1239,61 +1225,62 @@ class AudioReactive : public Usermod { uiDomString += F(" />
"); // infoArr.add(uiDomString); } - //else infoArr.add("
 
"); // no processing - add empty line + + // The following can be used for troubleshooting user errors and is so not enclosed in #ifdef WLED_DEBUG // 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 connection"); - } + // UDP sound sync - receive mode + infoArr.add(F("UDP sound sync")); + if (udpSyncConnected) { + if (millis() - last_UDPTime < 2500) + infoArr.add(F(" - receiving")); + else + infoArr.add(F(" - idle")); + } else { + infoArr.add(F(" - no connection")); + } } 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"); - } + // audio source sucessfully configured + if (audioSource->getType() == AudioSource::Type_I2SAdc) { + infoArr.add(F("ADC analog")); + } else { + infoArr.add(F("I2S digital")); + } + // input level or "silence" + if (maxSample5sec > 1.0) { + float my_usage = 100.0f * (maxSample5sec / 255.0f); + snprintf_P(myStringBuffer, 15, PSTR(" - peak %3d%%"), int(my_usage)); + infoArr.add(myStringBuffer); + } else { + infoArr.add(F(" - quiet")); + } } else { - // error during audio source setup - infoArr.add("not initialized"); - infoArr.add(" - check GPIO config"); + // error during audio source setup + infoArr.add(F("not initialized")); + infoArr.add(F(" - check GPIO config")); } } // Sound processing (FFT and input filters) infoArr = user.createNestedArray(F("Sound Processing")); if (audioSource && (disableSoundProcessing == false)) { - infoArr.add("running"); + infoArr.add(F("running")); } else { - infoArr.add("suspended"); + infoArr.add(F("suspended")); } // AGC or manual Gain - if((soundAgc==0) && (disableSoundProcessing == false) && !(audioSyncEnabled & 0x02)) { + 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)) { + if (soundAgc && (disableSoundProcessing == false) && !(audioSyncEnabled & 0x02)) { infoArr = user.createNestedArray(F("AGC Gain")); infoArr.add(roundf(multAgc*100.0f) / 100.0f); infoArr.add("x"); @@ -1302,15 +1289,14 @@ class AudioReactive : public Usermod { // 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"); - } + if (audioSyncEnabled & 0x01) { + infoArr.add(F("send mode")); + } else if (audioSyncEnabled & 0x02) { + infoArr.add(F("receive mode")); + } } else - infoArr.add("off"); + infoArr.add("off"); if (audioSyncEnabled && !udpSyncConnected) infoArr.add(" (unconnected)"); - //if (!udpSyncConnected) infoArr.add(" (unconnected)"); #ifdef WLED_DEBUG infoArr = user.createNestedArray(F("Sampling time")); From b722c618bdfb034ba9c33bb39367b140b578abdc Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Sun, 21 Aug 2022 20:50:24 +0200 Subject: [PATCH 12/13] Fixes in NodeStruct & bus manager. --- wled00/NodeStruct.h | 1 - wled00/bus_manager.h | 42 +++++++++++++++++------------------------- wled00/wled.h | 4 ++-- 3 files changed, 19 insertions(+), 28 deletions(-) diff --git a/wled00/NodeStruct.h b/wled00/NodeStruct.h index a0fd2f63..bc95d1e8 100644 --- a/wled00/NodeStruct.h +++ b/wled00/NodeStruct.h @@ -19,7 +19,6 @@ struct NodeStruct { String nodeName; IPAddress ip; - uint8_t unit; uint8_t age; uint8_t nodeType; uint32_t build; diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h index 405c402a..a5c88daf 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -134,7 +134,12 @@ struct ColorOrderMap { //parent class of BusDigital, BusPwm, and BusNetwork class Bus { public: - Bus(uint8_t type, uint16_t start, uint8_t aw) { + Bus(uint8_t type, uint16_t start, uint8_t aw) + : _bri(255) + , _len(1) + , _valid(false) + , _needsRefresh(false) + { _type = type; _start = start; _autoWhiteMode = Bus::isRgbw(_type) ? aw : RGBW_MODE_MANUAL_ONLY; @@ -142,13 +147,13 @@ class Bus { virtual ~Bus() {} //throw the bus under the bus - virtual void show() {} + virtual void show() = 0; virtual bool canShow() { return true; } virtual void setStatusPixel(uint32_t c) {} - virtual void setPixelColor(uint16_t pix, uint32_t c) {} + virtual void setPixelColor(uint16_t pix, uint32_t c) = 0; virtual uint32_t getPixelColor(uint16_t pix) { return 0; } - virtual void setBrightness(uint8_t b) {} - virtual void cleanup() {} + virtual void setBrightness(uint8_t b) { _bri = b; }; + virtual void cleanup() = 0; virtual uint8_t getPins(uint8_t* pinArray) { return 0; } virtual uint16_t getLength() { return _len; } virtual void setColorOrder() {} @@ -195,12 +200,12 @@ class Bus { bool reversed = false; protected: - uint8_t _type = TYPE_NONE; - uint8_t _bri = 255; - uint16_t _start = 0; - uint16_t _len = 1; - bool _valid = false; - bool _needsRefresh = false; + uint8_t _type; + uint8_t _bri; + uint16_t _start; + uint16_t _len; + bool _valid; + bool _needsRefresh; uint8_t _autoWhiteMode; static uint8_t _gAWM; // definition in FX_fcn.cpp static int16_t _cct; // definition in FX_fcn.cpp @@ -262,7 +267,7 @@ class BusDigital : public Bus { if (_pins[0] == LED_BUILTIN || _pins[1] == LED_BUILTIN) PolyBus::begin(_busPtr, _iType, _pins); } #endif - _bri = b; + Bus::setBrightness(b); PolyBus::setBrightness(_busPtr, _iType, b); } @@ -448,10 +453,6 @@ class BusPwm : public Bus { } } - inline void setBrightness(uint8_t b) { - _bri = b; - } - uint8_t getPins(uint8_t* pinArray) { if (!_valid) return 0; uint8_t numPins = NUM_PWM_PINS(_type); @@ -531,10 +532,6 @@ class BusOnOff : public Bus { digitalWrite(_pin, reversed ? !(bool)_data : (bool)_data); } - inline void setBrightness(uint8_t b) { - _bri = b; - } - uint8_t getPins(uint8_t* pinArray) { if (!_valid) return 0; pinArray[0] = _pin; @@ -623,10 +620,6 @@ class BusNetwork : public Bus { return !_broadcastLock; } - inline void setBrightness(uint8_t b) { - _bri = b; - } - uint8_t getPins(uint8_t* pinArray) { for (uint8_t i = 0; i < 4; i++) { pinArray[i] = _client[i]; @@ -655,7 +648,6 @@ class BusNetwork : public Bus { private: IPAddress _client; - uint8_t _bri = 255; uint8_t _UDPtype; uint8_t _UDPchannels; bool _rgbw; diff --git a/wled00/wled.h b/wled00/wled.h index bff5b49a..ade76c86 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -8,7 +8,7 @@ */ // version code in format yymmddb (b = daily build) -#define VERSION 2208191 +#define VERSION 2208211 //uncomment this if you have a "my_config.h" file you'd like to use //#define WLED_USE_MY_CONFIG @@ -27,7 +27,7 @@ //#define WLED_DISABLE_ALEXA // saves 11kb //#define WLED_DISABLE_BLYNK // saves 6kb //#define WLED_DISABLE_HUESYNC // saves 4kb -//#define WLED_DISABLE_INFRARED // there is no pin left for this on ESP8266-01, saves 12kb +//#define WLED_DISABLE_INFRARED // saves 12kb #ifndef WLED_DISABLE_MQTT #define WLED_ENABLE_MQTT // saves 12kb #endif From be7e7ac2746573bc8244a1bd13492930281678a7 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Mon, 22 Aug 2022 10:08:22 +0200 Subject: [PATCH 13/13] AR: documentation - clarified a misleading comment in FFTCode - added a few more comments to describe steps of the processing - removed some commented-out code --- usermods/audioreactive/audio_reactive.h | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index ca8a3287..826ad7f4 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -189,6 +189,7 @@ void FFTcode(void * parameter) uint64_t start = esp_timer_get_time(); #endif + // get a fresh batch of samples from I2S if (audioSource) audioSource->getSamples(vReal, samplesFFT); #ifdef WLED_DEBUG @@ -198,7 +199,7 @@ void FFTcode(void * parameter) } #endif - //const int halfSamplesFFT = samplesFFT / 2; // samplesFFT divided by 2 + // find highest sample in the batch float maxSample = 0.0f; // max sample from FFT batch for (int i=0; i < samplesFFT; i++) { // set imaginary parts to 0 @@ -207,10 +208,11 @@ void FFTcode(void * parameter) if ((vReal[i] <= (INT16_MAX - 1024)) && (vReal[i] >= (INT16_MIN + 1024))) //skip extreme values - normally these are artefacts if (fabsf((float)vReal[i]) > maxSample) maxSample = fabsf((float)vReal[i]); } - // release sample to volume reactive effects (not really necessary as float FFT calculation takes only 2ms) + // release highest sample to volume reactive effects early - not strictly necessary here - could also be done at the end of the function + // early release allows the filters (getSample() and agcAvg()) to work with fresh values - we will have matching gain and noise gate values when we want to process the FFT results. micDataReal = maxSample; micDataReal = maxSample; - // run FFT + // run FFT (takes 3-5ms on ESP32) #ifdef UM_AUDIOREACTIVE_USE_NEW_FFT FFT.dcRemoval(); // remove DC offset FFT.windowing( FFTWindow::Flat_top, FFTDirection::Forward); // Weigh data using "Flat Top" function - better amplitude accuracy @@ -240,7 +242,7 @@ void FFTcode(void * parameter) fftBin[i] = t / 16.0f; // Reduce magnitude. Want end result to be linear and ~4096 max. } // for() - + // mapping of FFT result bins to frequency channels if (sampleAvg > 1) { // noise gate open #if 0 /* This FFT post processing is a DIY endeavour. What we really need is someone with sound engineering expertise to do a great job here AND most importantly, that the animations look GREAT as a result. @@ -289,14 +291,14 @@ void FFTcode(void * parameter) fftCalc[15] = fftAddAvg(165,215) * 0.70f; // 50 7106 - 9259 high -- with some damping // don't use the last bins from 216 to 255. They are usually contaminated by aliasing (aka noise) #endif - } else { // noise gate closed + } else { // noise gate closed - just decay old values for (int i=0; i < 16; i++) { - //fftCalc[i] *= 0.82f; // decay to zero fftCalc[i] *= 0.85f; // decay to zero if (fftCalc[i] < 4.0f) fftCalc[i] = 0.0f; } } + // post-processing of frequency channels (pink noise adjustment, AGC, smooting, scaling) for (int i=0; i < 16; i++) { if (sampleAvg > 1) { // noise gate open @@ -304,7 +306,7 @@ void FFTcode(void * parameter) fftCalc[i] *= fftResultPink[i]; if (FFTScalingMode > 0) fftCalc[i] *= FFT_DOWNSCALE; // adjustment related to FFT windowing function // 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 + fftCalc[i] *= soundAgc ? multAgc : ((float)sampleGain/40.0f * (float)inputLevel/128.0f + 1.0f/16.0f); //apply gain, with inputLevel adjustment if(fftCalc[i] < 0) fftCalc[i] = 0; }