From 8847d47beef8a505c04f9eecf85f92d0de4c1a3f Mon Sep 17 00:00:00 2001
From: Frank <91616163+softhack007@users.noreply.github.com>
Date: Mon, 17 Oct 2022 18:40:15 +0200
Subject: [PATCH] cherry-picking from audioreactive development
A few goodies from my development branch, before I continue refactoring so much that I can't extracts nothing anymore.
- GEQ fades a bit slower
- updates to mic profiles
- simple peak detection without FFT (low freqs only)
---
usermods/audioreactive/audio_reactive.h | 80 +++++++++++++++++--------
1 file changed, 55 insertions(+), 25 deletions(-)
diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h
index ea44c01c..567b7e1f 100644
--- a/usermods/audioreactive/audio_reactive.h
+++ b/usermods/audioreactive/audio_reactive.h
@@ -21,6 +21,7 @@
*/
#define FFT_PREFER_EXACT_PEAKS // use different FFT wndowing -> results in "sharper" peaks and less "leaking" into other frequencies
+//#define SR_STATS
// Comment/Uncomment to toggle usb serial debugging
// #define MIC_LOGGER // MIC sampling & sound input debugging (serial plotter)
@@ -59,6 +60,7 @@ static uint8_t soundSquelch = 10; // squelch value for volume reacti
static uint8_t sampleGain = 60; // sample gain (config value)
static uint8_t soundAgc = 0; // Automagic gain control: 0 - none, 1 - normal, 2 - vivid, 3 - lazy (config value)
static uint8_t audioSyncEnabled = 0; // bit field: bit 0 - send, bit 1 - receive (config value)
+static bool udpSyncConnected = false; // UDP connection status -> true if connected to multicast group
// user settable parameters for limitSoundDynamics()
static bool limiterOn = true; // bool: enable / disable dynamics limiter
@@ -94,6 +96,12 @@ static volatile bool disableSoundProcessing = false; // if true, sound proc
static float micDataReal = 0.0f; // MicIn data with full 24bit resolution - lowest 8bit after decimal point
static float multAgc = 1.0f; // sample * multAgc = sampleAgc. Our AGC multiplier
static float sampleAvg = 0.0f; // Smoothed Average sample - sampleAvg < 1 means "quiet" (simple noise gate)
+static float sampleAgc = 0.0f; // Smoothed AGC sample
+
+// variables used in effects
+static float volumeSmth = 0.0f; // either sampleAvg or sampleAgc depending on soundAgc; smoothed sample
+//static int16_t volumeRaw = 0; // either sampleRaw or rawSampleAgc depending on soundAgc
+//static float my_magnitude =0.0f; // FFT_Magnitude, scaled by multAgc
// peak detection
static bool samplePeak = false; // Boolean flag for peak - used in effects. Responding routine may reset this flag. Auto-reset after strip.getMinShowDelay()
@@ -149,7 +157,7 @@ static float fftAvg[NUM_GEQ_CHANNELS] = {0.0f}; // Calcula
static float fftResultMax[NUM_GEQ_CHANNELS] = {0.0f}; // A table used for testing to determine how our post-processing is working.
#endif
-#if defined(WLED_DEBUG) || defined(SR_DEBUG)
+#if defined(WLED_DEBUG) || defined(SR_DEBUG) || defined(SR_STATS)
static uint64_t fftTime = 0;
static uint64_t sampleTime = 0;
#endif
@@ -168,8 +176,8 @@ static const float fftResultPink[MAX_PINK+1][NUM_GEQ_CHANNELS] = {
{ 2.75f, 1.60f, 1.40f, 1.46f, 1.52f, 1.57f, 1.68f, 1.80f, 1.89f, 2.00f, 2.11f, 2.21f, 2.30f, 1.75f, 2.55f, 3.60f }, // ICS-43434 datasheet response * pink noise
{ 2.25f, 1.20f, 1.00f, 1.20f, 1.80f, 3.20f, 5.10f, 5.50f, 4.00f, 4.80f, 6.70f, 6.40f, 5.80f, 3.90f, 6.00f, 5.10f }, // ICS-43434 - big speaker, strong bass
- { 2.25f, 1.60f, 1.30f, 1.60f, 2.20f, 3.20f, 3.06f, 2.60f, 2.85f, 3.50f, 3.90f, 4.50f, 3.35f, 3.20f, 3.60f, 4.20f }, // ICS-43434 - userdef #1 for ewowi (enhance median freqs)
- { 4.75f, 3.60f, 2.40f, 2.46f, 3.52f, 1.60f, 1.68f, 3.20f, 2.20f, 2.00f, 2.30f, 2.41f, 2.30f, 1.25f, 4.55f, 6.50f }, // ICS-43434 - userdef #2 for softhack (mic hidden inside mini-shield)
+ { 2.25f, 1.60f, 1.30f, 1.60f, 2.20f, 3.20f, 3.06f, 2.60f, 2.85f, 3.50f, 4.10f, 4.80f, 5.70f, 6.05f,10.50f,14.85f }, // userdef #1 for ewowi (enhance median/high freqs)
+ { 4.75f, 3.60f, 2.40f, 2.46f, 3.52f, 1.60f, 1.68f, 3.20f, 2.20f, 2.00f, 2.30f, 2.41f, 2.30f, 1.25f, 4.55f, 6.50f }, // userdef #2 for softhack (mic hidden inside mini-shield)
{ 2.38f, 2.18f, 2.07f, 1.70f, 1.70f, 1.70f, 1.70f, 1.70f, 1.70f, 1.70f, 1.70f, 1.70f, 1.95f, 1.70f, 2.13f, 2.47f } // almost FLAT (IMNP441 but no PINK noise adjustments)
};
@@ -244,7 +252,7 @@ void FFTcode(void * parameter)
continue;
}
-#if defined(WLED_DEBUG) || defined(SR_DEBUG)
+#if defined(WLED_DEBUG) || defined(SR_DEBUG)|| defined(SR_STATS)
uint64_t start = esp_timer_get_time();
bool haveDoneFFT = false; // indicates if second measurement (FFT time) is valid
#endif
@@ -252,7 +260,7 @@ void FFTcode(void * parameter)
// get a fresh batch of samples from I2S
if (audioSource) audioSource->getSamples(vReal, samplesFFT);
-#if defined(WLED_DEBUG) || defined(SR_DEBUG)
+#if defined(WLED_DEBUG) || defined(SR_DEBUG)|| defined(SR_STATS)
if (start < esp_timer_get_time()) { // filter out overflows
uint64_t sampleTimeInMillis = (esp_timer_get_time() - start +5ULL) / 10ULL; // "+5" to ensure proper rounding
sampleTime = (sampleTimeInMillis*3 + sampleTime*7)/10; // smooth
@@ -275,11 +283,9 @@ void FFTcode(void * parameter)
// 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;
-#ifdef SR_DEBUG
- if (true) { // this allows measure FFT runtimes, as it disables the "only when needed" optimization
-#else
- if (sampleAvg > 0.5f) { // noise gate open means that FFT results will be used. Don't run FFT if results are not needed.
-#endif
+ // run FFT (takes 3-5ms on ESP32)
+ //if (fabsf(sampleAvg) > 0.25f) { // noise gate open
+ if (fabsf(volumeSmth) > 0.25f) { // noise gate open
// run FFT (takes 3-5ms on ESP32, ~12ms on ESP32-S2)
#ifdef UM_AUDIOREACTIVE_USE_NEW_FFT
@@ -312,7 +318,7 @@ void FFTcode(void * parameter)
#endif
FFT_MajorPeak = constrain(FFT_MajorPeak, 1.0f, 11025.0f); // restrict value to range expected by effects
-#if defined(WLED_DEBUG) || defined(SR_DEBUG)
+#if defined(WLED_DEBUG) || defined(SR_DEBUG) || defined(SR_STATS)
haveDoneFFT = true;
#endif
@@ -328,7 +334,8 @@ void FFTcode(void * parameter)
} // for()
// mapping of FFT result bins to frequency channels
- if (sampleAvg > 0.5f) { // noise gate open
+ //if (fabsf(sampleAvg) > 0.25f) { // noise gate open
+ if (fabsf(volumeSmth) > 0.25f) { // 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.
*
@@ -387,7 +394,8 @@ void FFTcode(void * parameter)
if (pinkIndex > MAX_PINK) pinkIndex = MAX_PINK;
for (int i=0; i < NUM_GEQ_CHANNELS; i++) {
- if (sampleAvg > 0.5f) { // noise gate open
+ //if (fabsf(sampleAvg) > 0.25f) { // noise gate open
+ if (fabsf(volumeSmth) > 0.25f) { // noise gate open
// Adjustment for frequency curves.
fftCalc[i] *= fftResultPink[pinkIndex][i];
if (FFTScalingMode > 0) fftCalc[i] *= FFT_DOWNSCALE; // adjustment related to FFT windowing function
@@ -463,8 +471,9 @@ void FFTcode(void * parameter)
fftResult[i] = constrain((int)currentResult, 0, 255);
}
-#if defined(WLED_DEBUG) || defined(SR_DEBUG)
- if (haveDoneFFT && (start < esp_timer_get_time())) { // filter out overflows
+#if defined(WLED_DEBUG) || defined(SR_DEBUG)|| defined(SR_STATS)
+ //if (haveDoneFFT && (start < esp_timer_get_time())) { // filter out overflows
+ if ((start <= esp_timer_get_time())) { // filter out overflows
uint64_t fftTimeInMillis = ((esp_timer_get_time() - start) +5ULL) / 10ULL; // "+5" to ensure proper rounding
fftTime = (fftTimeInMillis*3 + fftTime*7)/10; // smooth
}
@@ -488,14 +497,30 @@ void FFTcode(void * parameter)
// peak detection is called from FFT task when vReal[] contains valid FFT results
static void detectSamplePeak(void) {
+ bool havePeak = false;
+
// Poor man's beat detection by seeing if sample > Average + some value.
+ // 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 ((sampleAvg > 1) && (maxVol > 0) && (binNum > 1) && (vReal[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.
+ havePeak = true;
+ }
+
+#if 0
+ // alternate detection, based on FFT_MajorPeak and FFT_Magnitude. Not much better...
+ if ((binNum > 1) && (binNum < 10) && (sampleAgc > 127) &&
+ (FFT_MajorPeak > 50) && (FFT_MajorPeak < 250) && (FFT_Magnitude > (16.0f * (maxVol+42.0)) /*my_magnitude > 136.0f*16.0f*/) &&
+ (millis() - timeOfPeak > 80)) {
+ havePeak = true;
+ }
+#endif
+
+ if (havePeak) {
samplePeak = true;
timeOfPeak = millis();
udpSamplePeak = true;
}
+
}
static void autoResetPeak(void) {
@@ -587,7 +612,6 @@ class AudioReactive : public Usermod {
// variables for UDP sound sync
WiFiUDP fftUdp; // UDP object for sound sync (from WiFi UDP, not Async UDP!)
- bool udpSyncConnected = false;// UDP connection status -> true if connected to multicast group
unsigned long lastTime = 0; // last time of running UDP Microphone Sync
const uint16_t delayMs = 10; // I don't want to sample too often and overload WLED
uint16_t audioSyncPort= 11988;// default port for UDP sound sync
@@ -604,10 +628,8 @@ class AudioReactive : public Usermod {
float sampleReal = 0.0f; // "sampleRaw" as float, to provide bits that are lost otherwise (before amplification by sampleGain or inputLevel). Needed for AGC.
int16_t sampleRaw = 0; // Current sample. Must only be updated ONCE!!! (amplified mic value by sampleGain and inputLevel)
int16_t rawSampleAgc = 0; // not smoothed AGC sample
- float sampleAgc = 0.0f; // Smoothed AGC sample
// variables used in effects
- float volumeSmth = 0.0f; // either sampleAvg or sampleAgc depending on soundAgc; smoothed sample
int16_t volumeRaw = 0; // either sampleRaw or rawSampleAgc depending on soundAgc
float my_magnitude =0.0f; // FFT_Magnitude, scaled by multAgc
@@ -855,6 +877,14 @@ class AudioReactive : public Usermod {
// 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
+#if 1
+ // another simple way to detect samplePeak
+ if ((binNum < 10) && (millis() - timeOfPeak > 80) && (sampleAvg > 1)) {
+ samplePeak = true;
+ timeOfPeak = millis();
+ udpSamplePeak = true;
+ }
+#endif
} else {
if ((multAgc*sampleMax > agcZoneStop[AGC_preset]) && (soundAgc > 0))
sampleMax += 0.5f * (sampleReal - sampleMax); // over AGC Zone - get back quickly
@@ -1471,13 +1501,13 @@ class AudioReactive : public Usermod {
infoArr.add("off");
if (audioSyncEnabled && !udpSyncConnected) infoArr.add(" (unconnected)");
- #if defined(WLED_DEBUG) || defined(SR_DEBUG)
+ #if defined(WLED_DEBUG) || defined(SR_DEBUG) || defined(SR_STATS)
infoArr = user.createNestedArray(F("Sampling time"));
infoArr.add(float(sampleTime)/100.0f);
infoArr.add(" ms");
infoArr = user.createNestedArray(F("FFT time"));
- infoArr.add(float(fftTime)/100.0f);
+ infoArr.add(float(fftTime)/100.0f);
if ((fftTime/100) >= FFT_MIN_CYCLE) // FFT time over budget -> I2S buffer will overflow
infoArr.add("! ms");
else if ((fftTime/80 + sampleTime/80) >= FFT_MIN_CYCLE) // FFT time >75% of budget -> risk of instability
@@ -1691,8 +1721,8 @@ class AudioReactive : public Usermod {
oappend(SET_F("addOption(dd,'IMNP441 - small speakers',4);"));
oappend(SET_F("addOption(dd,'ICS-43434',5);"));
oappend(SET_F("addOption(dd,'ICS-43434 - big speakers',6);"));
- oappend(SET_F("addOption(dd,'ICS-43434 - userdef #1',7);"));
- oappend(SET_F("addOption(dd,'ICS-43434 - userdef #2',8);"));
+ oappend(SET_F("addOption(dd,'userdefined #1',7);"));
+ oappend(SET_F("addOption(dd,'userdefined #2',8);"));
oappend(SET_F("addOption(dd,'flat - no adjustments',9);"));
oappend(SET_F("dd=addDropdown('AudioReactive','sync:mode');"));
@@ -1704,7 +1734,7 @@ class AudioReactive : public Usermod {
oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',1,'I2S WS');"));
oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',2,'I2S SCK');"));
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3)
- oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',3,'I2S Master CLK only use -1, 0, 1 or 3 for MCLK');"));
+ oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',3,'I2S Master CLK
only use -1, 0, 1 or 3 for MCLK');"));
#else
oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',3,'I2S Master CLK');"));
#endif