From 197e120e3bd35b5be6ab2c963ccde86ddf0eff2d Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Thu, 6 Apr 2023 18:58:20 +0200 Subject: [PATCH] estimated audio sound pressure --- usermods/audioreactive/audio_reactive.h | 73 ++++++++++++++++++++++++- wled00/FX.cpp | 6 +- wled00/util.cpp | 5 +- 3 files changed, 78 insertions(+), 6 deletions(-) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index 3d60a30a..31e185ea 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -232,6 +232,7 @@ static const float fftResultPink[MAX_PINK+1][NUM_GEQ_CHANNELS] = { // globals and FFT Output variables shared with animations static float FFT_MajorPeak = 1.0f; // FFT: strongest (peak) frequency static float FFT_Magnitude = 0.0f; // FFT: volume (magnitude) of peak frequency +static float FFT_MajPeakSmth = 1.0f; // FFT: (peak) frequency, smooth static uint8_t fftResult[NUM_GEQ_CHANNELS]= {0};// Our calculated freq. channel result table to be used by effects #if defined(WLED_DEBUG) || defined(SR_DEBUG) || defined(SR_STATS) static float fftTaskCycle = 0; // avg cycle time for FFT task @@ -498,6 +499,7 @@ void FFTcode(void * parameter) FFT.MajorPeak(&FFT_MajorPeak, &FFT_Magnitude); // let the effects know which freq was most dominant #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 } else { // skip second run --> clear fft results, keep peaks memset(vReal, 0, sizeof(vReal)); @@ -918,6 +920,8 @@ class AudioReactive : public Usermod { // variables used in effects int16_t volumeRaw = 0; // either sampleRaw or rawSampleAgc depending on soundAgc float my_magnitude =0.0f; // FFT_Magnitude, scaled by multAgc + float agcSensitivity = 128; // AGC sensitivity estimation, based on agc gain (multAgc). calculated by getSensitivity(). range 0..255 + float soundPressure = 0; // Sound Pressure estimation, based on microphone raw readings. 0 ->5db, 255 ->105db // used to feed "Info" Page unsigned long last_UDPTime = 0; // time of last valid UDP sound sync datapacket @@ -1228,6 +1232,53 @@ class AudioReactive : public Usermod { } // getSample() + // current sensitivity, based on AGC gain (multAgc) + float getSensitivity() + { + // start with AGC gain factor + float tmpSound = multAgc; + // experimental: this gives you a calculated "real gain" + // if ((sampleAvg> 1.0) && (sampleReal > 0.05)) tmpSound = (float)sampleRaw / sampleReal; // calculate gain from sampleReal + // else tmpSound = ((float)sampleGain/40.0f * (float)inputLevel/128.0f) + 1.0f/16.0f; // silence --> use values from user settings + + if (soundAgc == 0) + tmpSound = ((float)sampleGain/40.0f * (float)inputLevel/128.0f) + 1.0f/16.0f; // AGC off -> use non-AGC gain from presets + else + tmpSound /= (float)sampleGain/40.0f + 1.0f/16.0f; // AGC ON -> scale value so 1 = middle value + + // scale to 0..255. Actually I'm not absolutely happy with this, but it works + if (tmpSound > 1.0) tmpSound = sqrtf(tmpSound); + if (tmpSound > 1.25) tmpSound = ((tmpSound-1.25f)/3.42f) +1.25f; + // we have a value now that should be between 0 and 4 (representing gain 1/16 ... 16.0) + return fminf(fmaxf(128.0*tmpSound -6.0f, 0), 255.0); // return scaled non-inverted value // "-6" to ignore values below 1/24 + } + + // estimate sound pressure, based on some assumptions : + // * sample max = 32676 -> Acoustic overload point --> 105db ==> 255 + // * sample < squelch -> just above hearing level --> 5db ==> 0 + // see https://en.wikipedia.org/wiki/Sound_pressure#Examples_of_sound_pressure + // use with I2S digital microphones. Expect stupid values for analog in, and with Line-In !! + float estimatePressure() { + // some constants + constexpr float logMinSample = 1.2237754316221f; // ln(3.4) + constexpr float sampleMin = 3.4f; + constexpr float logMaxSample = 10.1895683436f; // ln(32767 - 6144) + constexpr float sampleMax = 32767.0f - 6144.0f; + + // take the max sample from last I2S batch. + float micSampleMax = fabsf(sampleReal); // from getSample() - nice results, however distorted by MicLev processing + //float micSampleMax = fabsf(micDataReal); // from FFTCode() - better source, but I'll do more testing before activating this + // make sure we are in expected ranges + if(micSampleMax <= sampleMin) return 0.0f; + if(micSampleMax >= sampleMax) return 255.0f; + + // apply logarithmic scaling + float scaledvalue = logf(micSampleMax); + scaledvalue = (scaledvalue - logMinSample) / (logMaxSample - logMinSample); // 0...1 + return fminf(fmaxf(256.0*scaledvalue, 0), 255.0); // scaled value + } + + /* Limits the dynamics of volumeSmth (= sampleAvg or sampleAgc). * does not affect FFTResult[] or volumeRaw ( = sample or rawSampleAgc) */ @@ -1422,12 +1473,12 @@ 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 = 8; + um_data->u_size = 11; 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) um_data->u_type[0] = UMT_FLOAT; - um_data->u_data[1] = &volumeRaw; // used (New) + um_data->u_data[1] = &volumeRaw; // used (New) um_data->u_type[1] = UMT_UINT16; um_data->u_data[2] = fftResult; //*used (Blurz, DJ Light, Noisemove, GEQ_base, 2D Funky Plank, Akemi) um_data->u_type[2] = UMT_BYTE_ARR; @@ -1435,12 +1486,18 @@ class AudioReactive : public Usermod { um_data->u_type[3] = UMT_BYTE; um_data->u_data[4] = &FFT_MajorPeak; //*used (Ripplepeak, Freqmap, Freqmatrix, Freqpixels, Freqwave, Gravfreq, Rocktaves, Waterfall) um_data->u_type[4] = UMT_FLOAT; - um_data->u_data[5] = &my_magnitude; // used (New) + um_data->u_data[5] = &my_magnitude; // used (New) um_data->u_type[5] = UMT_FLOAT; um_data->u_data[6] = &maxVol; // assigned in effect function from UI element!!! (Puddlepeak, Ripplepeak, Waterfall) um_data->u_type[6] = UMT_BYTE; um_data->u_data[7] = &binNum; // assigned in effect function from UI element!!! (Puddlepeak, Ripplepeak, Waterfall) um_data->u_type[7] = UMT_BYTE; + um_data->u_data[8] = &FFT_MajPeakSmth; // new + um_data->u_type[8] = UMT_FLOAT; + um_data->u_data[9] = &soundPressure; // used (New) + um_data->u_type[9] = UMT_FLOAT; + um_data->u_data[10] = &agcSensitivity; // used (New) + um_data->u_type[10] = UMT_FLOAT; } // Reset I2S peripheral for good measure @@ -1666,6 +1723,16 @@ class AudioReactive : public Usermod { if (soundAgc) my_magnitude *= multAgc; if (volumeSmth < 1 ) my_magnitude = 0.001f; // noise gate closed - mute + // get AGC sensitivity and sound pressure + static unsigned long lastEstimate = 0; + if (millis() - lastEstimate > 12) { + lastEstimate = millis(); + agcSensitivity = getSensitivity(); + if (limiterOn) + soundPressure = soundPressure + 0.38f * (estimatePressure() - soundPressure); // dynamics limiter on -> some smoothing + else + soundPressure = soundPressure + 0.95f * (estimatePressure() - soundPressure); // dynamics limiter on -> raw value + } limitSampleDynamics(); } // if (!disableSoundProcessing) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index b2c64012..5a3de00c 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -5976,14 +5976,16 @@ static const char _data_FX_MODE_2DDRIFTROSE[] PROGMEM = "Drift Rose@Fade,Blur;;; um_data_t *um_data; if (usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { volumeSmth = *(float*) um_data->u_data[0]; - volumeRaw = *(float*) um_data->u_data[1]; + volumeRaw = *(int16_t*) um_data->u_data[1]; fftResult = (uint8_t*) um_data->u_data[2]; samplePeak = *(uint8_t*) um_data->u_data[3]; FFT_MajorPeak = *(float*) um_data->u_data[4]; my_magnitude = *(float*) um_data->u_data[5]; maxVol = (uint8_t*) um_data->u_data[6]; // requires UI element (SEGMENT.customX?), changes source element binNum = (uint8_t*) um_data->u_data[7]; // requires UI element (SEGMENT.customX?), changes source element - fftBin = (float*) um_data->u_data[8]; + FFT_MajPeakSmth= *(float*) um_data->u_data[8]; // FFT Majorpeak smoothed + soundPressure = *(float*) um_data->u_data[9]; // sound pressure ( = logarithmic scale microphone input). Range 0...255 + agcSensitivity = *(float*) um_data->u_data[10]; // current AGC gain, scaled to 0...255. use "255.0f - agcSensitivity" to get MIC input level } else { // add support for no audio data um_data = simulateSound(SEGMENT.soundSim); diff --git a/wled00/util.cpp b/wled00/util.cpp index 5b93780d..e850fff7 100644 --- a/wled00/util.cpp +++ b/wled00/util.cpp @@ -423,7 +423,7 @@ um_data_t* simulateSound(uint8_t simulationId) // NOTE!!! // This may change as AudioReactive usermod may change um_data = new um_data_t; - um_data->u_size = 8; + um_data->u_size = 11; 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; @@ -434,6 +434,9 @@ um_data_t* simulateSound(uint8_t simulationId) um_data->u_data[5] = &my_magnitude; um_data->u_data[6] = &maxVol; um_data->u_data[7] = &binNum; + um_data->u_data[8] = &FFT_MajorPeak; // dummy (samplePeak smoothed) + um_data->u_data[9] = &volumeSmth; // dummy (soundPressure) + um_data->u_data[10] = &volumeSmth; // dummy (agcSensitivity) } else { // get arrays from um_data fftResult = (uint8_t*)um_data->u_data[2];