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)
This commit is contained in:
@@ -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(" <i>(unconnected)</i>");
|
||||
|
||||
#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("<b style=\"color:red;\">! ms</b>");
|
||||
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 <i>only use -1, 0, 1 or 3 for MCLK</i>');"));
|
||||
oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',3,'I2S Master CLK <br/><i>only use -1, 0, 1 or 3 for MCLK</i>');"));
|
||||
#else
|
||||
oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',3,'I2S Master CLK');"));
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user