From 2fe86bde5db87bb43f6033f2df0dd2ae7789dbfb Mon Sep 17 00:00:00 2001 From: Troy <5659019+troyhacks@users.noreply.github.com> Date: Sat, 23 Mar 2024 17:41:34 -0400 Subject: [PATCH] Feature complete... and better! --- usermods/audioreactive/audio_reactive.h | 57 +++++--- .../usermod_v2_auto_playlist.h | 138 +++++++++++------- 2 files changed, 120 insertions(+), 75 deletions(-) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index b19b4532..db6f5065 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -141,9 +141,9 @@ static uint8_t fftResult[NUM_GEQ_CHANNELS]= {0}; // Our calculated freq. chann static float fftCalc[NUM_GEQ_CHANNELS] = {0.0f}; // Try and normalize fftBin values to a max of 4096, so that 4096/16 = 256. (also used by dynamics limiter) static float fftAvg[NUM_GEQ_CHANNELS] = {0.0f}; // Calculated frequency channel results, with smoothing (used if dynamics limiter is ON) -unsigned int zeroCrossingCount = 0; -unsigned int energy = 0; -unsigned int lowFreqencyContent = 0; +uint_fast32_t zeroCrossingCount = 0; +uint_fast32_t energy = 0; +uint_fast32_t lowFreqencyContent = 0; // TODO: probably best not used by receive nodes static float agcSensitivity = 128; // AGC sensitivity estimation, based on agc gain (multAgc). calculated by getSensitivity(). range 0..255 @@ -498,6 +498,21 @@ void FFTcode(void * parameter) // get a fresh batch of samples from I2S if (audioSource) audioSource->getSamples(vReal, samplesFFT); + + // WLED-MM/TroyHacks: Calculate zero crossings + // + zeroCrossingCount = 0; + energy = 0; + + for (int i=0; i < samplesFFT; i++) { + if (i < (samplesFFT)-2) { + if((vReal[i] >= 0 && vReal[i+1] < 0) || (vReal[i+1] < 0 && vReal[i+1] >= 0)) { + zeroCrossingCount++; + } + } + // WLED-MM/TroyHacks: Calculate energy + energy += (vReal[i] * vReal[i])/10000000; + } #if defined(WLED_DEBUG) || defined(SR_DEBUG)|| defined(SR_STATS) // debug info in case that stack usage changes @@ -549,24 +564,13 @@ void FFTcode(void * parameter) // find highest sample in the batch float maxSample = 0.0f; // max sample from FFT batch - zeroCrossingCount = 0; 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]) > maxSample) maxSample = fabsf((float)vReal[i]); - - // WLED-MM/TroyHacks: Calculate zero crossings - if (i < samplesFFT-2) { - if (vReal[i] * vReal[i+1] < 0) { - zeroCrossingCount++; - } - } - // WLED-MM/TroyHacks: Calculate energy - energy += vReal[i] * vReal[i]; } - energy /= 100000; // WLED-MM/TroyHacks: scale this down becasue we're gonna make it bigger later // 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. @@ -641,6 +645,17 @@ void FFTcode(void * parameter) #endif FFT_MajorPeak = constrain(FFT_MajorPeak, 1.0f, 11025.0f); // restrict value to range expected by effects FFT_MajPeakSmth = FFT_MajPeakSmth + 0.42 * (FFT_MajorPeak - FFT_MajPeakSmth); // I like this "swooping peak" look + + // WLED-MM/TroyHacks: Calculate Low-Frequency Content + // + lowFreqencyContent = fftAddAvg(2,4)/1000; + + // USER_PRINT("ZCR = "); + // USER_PRINT(zeroCrossingCount); + // USER_PRINT(" Energy = "); + // USER_PRINT(" LFC = "); + // USER_PRINT(lowFreqencyContent); + // USER_PRINTLN(); } else { // skip second run --> clear fft results, keep peaks memset(vReal, 0, sizeof(vReal)); @@ -662,8 +677,6 @@ void FFTcode(void * parameter) vReal[i] = t / 16.0f; // Reduce magnitude. Want end result to be scaled linear and ~4096 max. } // for() - lowFreqencyContent = fftAddAvg(1,4); // WLED-MM/TroyHacks: Calculate Low-Frequency Content - // mapping of FFT result bins to frequency channels //if (fabsf(sampleAvg) > 0.25f) { // noise gate open if (fabsf(volumeSmth) > 0.25f) { // noise gate open @@ -1738,7 +1751,7 @@ class AudioReactive : public Usermod { // usermod exchangeable data // we will assign all usermod exportable data here as pointers to original variables or arrays and allocate memory for pointers um_data = new um_data_t; - um_data->u_size = 11; + um_data->u_size = 13; um_data->u_type = new um_types_t[um_data->u_size]; um_data->u_data = new void*[um_data->u_size]; um_data->u_data[0] = &volumeSmth; //*used (New) @@ -1764,10 +1777,12 @@ class AudioReactive : public Usermod { um_data->u_type[9] = UMT_FLOAT; um_data->u_data[10] = &agcSensitivity; // used (New) um_data->u_type[10] = UMT_FLOAT; - unsigned int* extra[3] = {&zeroCrossingCount, &energy, &lowFreqencyContent}; - um_data->u_data[11] = extra; // - um_data->u_type[11] = UMT_INT16_ARR; - + um_data->u_data[11] = &zeroCrossingCount; + um_data->u_type[11] = UMT_UINT32; + um_data->u_data[12] = &energy; + um_data->u_type[12] = UMT_UINT32; + um_data->u_data[13] = &lowFreqencyContent; + um_data->u_type[13] = UMT_UINT32; #else // ESP8266 // See https://github.com/MoonModules/WLED/pull/60#issuecomment-1666972133 for explanation of these alternative sources of data diff --git a/usermods/usermod_v2_auto_playlist/usermod_v2_auto_playlist.h b/usermods/usermod_v2_auto_playlist/usermod_v2_auto_playlist.h index 2b21691f..e8061331 100644 --- a/usermods/usermod_v2_auto_playlist/usermod_v2_auto_playlist.h +++ b/usermods/usermod_v2_auto_playlist/usermod_v2_auto_playlist.h @@ -13,28 +13,32 @@ class AutoPlaylistUsermod : public Usermod { int timeout = 60; bool autoChange = false; byte lastAutoPlaylist = 0; + int change_timer = millis(); - int avg_long_energy = 10000; - int avg_long_lfc = 1000; - int avg_long_zcr = 100; + uint_fast32_t avg_long_energy = 250; + uint_fast32_t avg_long_lfc = 1000; + uint_fast32_t avg_long_zcr = 500; - int avg_short_energy = 10000; - int avg_short_lfc = 1000; - int avg_short_zcr = 100; + uint_fast32_t avg_short_energy = 250; + uint_fast32_t avg_short_lfc = 1000; + uint_fast32_t avg_short_zcr = 500; - int vector_energy = 0; - int vector_lfc = 0; - int vector_zcr = 0; + uint_fast32_t vector_energy = 0; + uint_fast32_t vector_lfc = 0; + uint_fast32_t vector_zcr = 0; + + uint_fast32_t distance = 0; + + // uint_fast64_t squared_distance = 0; - int squared_distance = 0; int lastchange = millis(); int last_beat_interval = millis(); - int change_threshold = 10; + int change_threshold = 50; - int change_lockout = 100; // never change below this numnber of millis. I think of this more like a debounce, but opinions may vary. - int ideal_change_min = 2000; // ideally change patterns no less than this number of millis - int ideal_change_max = 5000; // ideally change patterns no more than this number of millis + int change_lockout = 1000; // never change below this numnber of millis. I think of this more like a debounce, but opinions may vary. + int ideal_change_min = 10000; // ideally change patterns no less than this number of millis + int ideal_change_max = 20000; // ideally change patterns no more than this number of millis std::vector autoChangeIds; @@ -43,10 +47,15 @@ class AutoPlaylistUsermod : public Usermod { static const char _musicPlaylist[]; static const char _timeout[]; static const char _autoChange[]; + static const char _change_lockout[]; + static const char _ideal_change_min[]; + static const char _ideal_change_max[]; public: - AutoPlaylistUsermod(bool enabled):Usermod("AutoPlaylist", enabled) {} + AutoPlaylistUsermod(bool enabled):Usermod("AutoPlaylist", enabled) { + // noop + } // gets called once at boot. Do all initialization that doesn't depend on // network here @@ -56,47 +65,62 @@ class AutoPlaylistUsermod : public Usermod { // gets called every time WiFi is (re-)connected. Initialize own network // interfaces here - void connected() {} + void connected() { + // noop + } void change(um_data_t *um_data) { - int *extra = (int*) um_data->u_data[11]; - - unsigned int zcr = extra[0]; - int energy = extra[1]; - int lfc = extra[2]; // getFFTFromRange(um_data, 2 , 4); - - USER_PRINTF("zcr = %d, energy = %d, lfc = %d\n",zcr,energy,lfc); + uint_fast32_t zcr = *(uint_fast32_t*)um_data->u_data[11]; + uint_fast32_t energy = *(uint_fast32_t*)um_data->u_data[12]; + uint_fast32_t lfc = *(uint_fast32_t*)um_data->u_data[13]; // WLED-MM/TroyHacks: Calculate the long- and short-running averages // and the squared_distance for the vector. - avg_long_energy = avg_long_energy * 0.99 + energy * 0.01; - avg_long_lfc = avg_long_lfc * 0.99 + lfc * 0.01; - avg_long_zcr = avg_long_zcr * 0.99 + zcr * 0.01; + if (volumeSmth > 0.1) { - avg_short_energy = avg_short_energy * 0.9 + energy * 0.1; - avg_short_lfc = avg_short_lfc * 0.9 + lfc * 0.1; - avg_short_zcr = avg_short_zcr * 0.9 + zcr * 0.1; + avg_long_energy = avg_long_energy * 0.99 + energy * 0.01; + avg_long_lfc = avg_long_lfc * 0.99 + lfc * 0.01; + avg_long_zcr = avg_long_zcr * 0.99 + zcr * 0.01; - energy = 0; - lfc = 0; - zcr = 0; + avg_short_energy = avg_short_energy * 0.9 + energy * 0.1; + avg_short_lfc = avg_short_lfc * 0.9 + lfc * 0.1; + avg_short_zcr = avg_short_zcr * 0.9 + zcr * 0.1; - // allegedly this is faster than pow(whatever,2) - vector_lfc = (avg_short_lfc-avg_long_lfc)*(avg_short_lfc-avg_long_lfc); - vector_energy = (avg_short_energy-avg_long_energy)*(avg_short_energy-avg_long_energy); - vector_zcr = (avg_short_zcr-avg_long_zcr)*(avg_short_zcr-avg_long_zcr); + // allegedly this is faster than pow(whatever,2) + vector_lfc = (avg_short_lfc-avg_long_lfc)*(avg_short_lfc-avg_long_lfc); + vector_energy = (avg_short_energy-avg_long_energy)*(avg_short_energy-avg_long_energy); + vector_zcr = (avg_short_zcr-avg_long_zcr)*(avg_short_zcr-avg_long_zcr); - squared_distance = vector_lfc + vector_energy + vector_zcr; + } + + distance = vector_lfc + vector_energy + vector_zcr; // USER_PRINTF("squared_distance = %d\n", squared_distance * squared_distance / 10000000); - squared_distance = squared_distance * squared_distance / 10000000; // shorten just because it's a big number + // squared_distance = distance * distance; + + int change_interval = millis()-lastchange; + + // if (millis() > change_timer + 100) { + // // if (change_interval > ideal_change_max) { + // // USER_PRINTF("Increasing sensitivity to: %d\n",change_threshold++); + // // } + // USER_PRINT("\tDistance: "); + // USER_PRINT(distance); + // USER_PRINT("\tv_lfc: "); + // USER_PRINT(vector_lfc); + // USER_PRINT("\tv_energy: "); + // USER_PRINT(vector_energy); + // USER_PRINT("\tv_zcr: "); + // USER_PRINTLN(vector_zcr); + + // change_timer = millis(); + // } // WLED-MM/TroyHacks - Change pattern testing // - int change_interval = millis()-lastchange; - if (squared_distance <= change_threshold && change_interval > change_lockout) { + if (distance <= change_threshold && change_interval > change_lockout && volumeSmth > 0.1) { if (change_interval > ideal_change_max) { change_threshold += 1; } else if (change_interval < ideal_change_min) { @@ -122,19 +146,16 @@ class AutoPlaylistUsermod : public Usermod { while (currentPreset == newpreset); applyPreset(newpreset); - USER_PRINTF("*** CHANGE! Squared distance = %d - change interval was %d ms - next change min is %d\n",squared_distance, change_interval, change_threshold); + USER_PRINT("*** CHANGE! Vector distance = "); + USER_PRINT(distance); + USER_PRINT(" - change interval was "); + USER_PRINT(change_interval); + USER_PRINT("ms - next change min is "); + USER_PRINTLN(change_threshold); lastchange = millis(); } } - // // static float fftAddAvgLin(int from, int to) { - // float result = 0.0f; - // for (int i = from; i <= to; i++) { - // result += vReal[i]; - // } - // return result / float(to - from + 1); - // } - uint8_t getFFTFromRange(um_data_t *data, uint8_t from, uint8_t to) { uint8_t *fftResult = (uint8_t*) data->u_data[2]; uint16_t result = 0; @@ -268,6 +289,9 @@ class AutoPlaylistUsermod : public Usermod { top[FPSTR(_ambientPlaylist)] = ambientPlaylist; // usermodparam top[FPSTR(_musicPlaylist)] = musicPlaylist; // usermodparam top[FPSTR(_autoChange)] = autoChange; + top[FPSTR(_change_lockout)] = change_lockout; + top[FPSTR(_ideal_change_min)] = ideal_change_min; + top[FPSTR(_ideal_change_max)] = ideal_change_max; lastAutoPlaylist = 0; DEBUG_PRINTLN(F("AutoPlaylist config saved.")); } @@ -296,6 +320,9 @@ class AutoPlaylistUsermod : public Usermod { getJsonValue(top[_ambientPlaylist], ambientPlaylist); getJsonValue(top[_musicPlaylist], musicPlaylist); getJsonValue(top[_autoChange], autoChange); + getJsonValue(top[_change_lockout], change_lockout); + getJsonValue(top[_ideal_change_min], ideal_change_min); + getJsonValue(top[_ideal_change_max], ideal_change_max); DEBUG_PRINTLN(F(" config (re)loaded.")); @@ -324,8 +351,11 @@ class AutoPlaylistUsermod : public Usermod { }; -const char AutoPlaylistUsermod::_enabled[] PROGMEM = "enabled"; -const char AutoPlaylistUsermod::_ambientPlaylist[] PROGMEM = "ambientPlaylist"; -const char AutoPlaylistUsermod::_musicPlaylist[] PROGMEM = "musicPlaylist"; -const char AutoPlaylistUsermod::_timeout[] PROGMEM = "timeout"; -const char AutoPlaylistUsermod::_autoChange[] PROGMEM = "autoChange"; +const char AutoPlaylistUsermod::_enabled[] PROGMEM = "enabled"; +const char AutoPlaylistUsermod::_ambientPlaylist[] PROGMEM = "ambientPlaylist"; +const char AutoPlaylistUsermod::_musicPlaylist[] PROGMEM = "musicPlaylist"; +const char AutoPlaylistUsermod::_timeout[] PROGMEM = "timeout"; +const char AutoPlaylistUsermod::_autoChange[] PROGMEM = "autoChange"; +const char AutoPlaylistUsermod::_change_lockout[] PROGMEM = "change_lockout"; +const char AutoPlaylistUsermod::_ideal_change_min[] PROGMEM = "ideal_change_min"; +const char AutoPlaylistUsermod::_ideal_change_max[] PROGMEM = "ideal_change_max";