From b09077627e4c939a4410afa11d68ccfe7542f1b2 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Thu, 6 Apr 2023 17:56:32 +0200 Subject: [PATCH] low-cut audio input filtering * 40Hz low-cut and DC blocker filter - will remove any signal offsets and also removes rumbling noise up to 12db * DC blocker set as default for all sources (prerequisite for later measuring sound pressure) additional filtering options are in the making :-) --- usermods/audioreactive/audio_reactive.h | 50 ++++++++++++++++++------- 1 file changed, 36 insertions(+), 14 deletions(-) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index 64b33548..3d60a30a 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -121,7 +121,7 @@ const float agcSampleSmooth[AGC_NUM_PRESETS] = { 1/12.f, 1/6.f, 1/16.f}; // 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 bool useBandPassFilter = false; // if true, enables a bandpass filter 80Hz-16Khz to remove noise. Applies before FFT. +static uint8_t useInputFilter = 0; // enables low-cut filtering. Applies before FFT. //WLEDMM add experimental settings static uint8_t micLevelMethod = 0; // 0=old "floating" miclev, 1=new "freeze" mode, 2=fast freeze mode (mode 2 may not work for you) @@ -183,7 +183,7 @@ static TaskHandle_t FFT_Task = nullptr; static const float fftResultPink[MAX_PINK+1][NUM_GEQ_CHANNELS] = { { 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 }, // 0 default from SR WLED // { 1.30f, 1.32f, 1.40f, 1.46f, 1.52f, 1.57f, 1.68f, 1.80f, 1.89f, 2.00f, 2.11f, 2.21f, 2.30f, 2.39f, 3.09f, 4.34f }, // - Line-In Generic -> pink noise adjustment only - { 1.24f, 1.20f, 1.30f, 1.40f, 1.48f, 1.57f, 1.68f, 1.80f, 1.89f, 2.00f, 2.14f, 2.26f, 2.60f, 3.00f, 3.70f, 5.20f }, // 1 Line-In CS5343 + { 2.35f, 1.32f, 1.32f, 1.40f, 1.48f, 1.57f, 1.68f, 1.80f, 1.89f, 1.95f, 2.14f, 2.26f, 2.50f, 2.90f, 4.20f, 6.50f }, // 1 Line-In CS5343 + DC blocker { 1.82f, 1.72f, 1.70f, 1.50f, 1.52f, 1.57f, 1.68f, 1.80f, 1.89f, 2.00f, 2.11f, 2.21f, 2.30f, 2.90f, 3.86f, 6.29f}, // 2 IMNP441 datasheet response profile * pink noise { 2.80f, 2.20f, 1.30f, 1.15f, 1.55f, 2.45f, 4.20f, 2.80f, 3.20f, 3.60f, 4.20f, 4.90f, 5.70f, 6.05f,10.50f,14.85f}, // 3 IMNP441 - big speaker, strong bass @@ -341,6 +341,23 @@ constexpr bool skipSecondFFT = true; #else constexpr bool skipSecondFFT = false; #endif + +// High-Pass "DC blocker" filter +// see https://www.dsprelated.com/freebooks/filters/DC_Blocker.html +static void runDCBlocker(uint_fast16_t numSamples, float *sampleBuffer) { + constexpr float filterR = 0.990f; // around 40hz + static float xm1 = 0.0f; + static float ym1 = 0.0f; + + for (unsigned i=0; i < numSamples; i++) { + float value = sampleBuffer[i]; + float filtered = value-xm1 + filterR*ym1; + xm1 = value; + ym1 = filtered; + sampleBuffer[i] = filtered; + } +} + // // FFT main task // @@ -410,7 +427,12 @@ void FFTcode(void * parameter) // band pass filter - can reduce noise floor by a factor of 50 // downside: frequencies below 100Hz will be ignored - if (useBandPassFilter) runMicFilter(samplesFFT, vReal); + if ((useInputFilter > 0) && (useInputFilter < 99)) { + switch(useInputFilter) { + case 1: runMicFilter(samplesFFT, vReal); break; // PDM microphone bandpass + case 2: runDCBlocker(samplesFFT, vReal); break; // generic Low-Cut + DC blocker (~40hz cut-off) + } + } // find highest sample in the batch float maxSample = 0.0f; // max sample from FFT batch @@ -531,7 +553,7 @@ void FFTcode(void * parameter) if (freqDist == 0) { /* new mapping, optimized for 22050 Hz by softhack007 --- update: removed overlap */ // bins frequency range - if (useBandPassFilter) { + if (useInputFilter==1) { // skip frequencies below 100hz fftCalc[ 0] = 0.8f * fftAddAvg(3,3); fftCalc[ 1] = 0.9f * fftAddAvg(4,4); @@ -560,7 +582,7 @@ void FFTcode(void * parameter) fftCalc[14] = fftAddAvg(104,164) * 0.88f; // 61 4479 - 7106 high mid + high -- with slight damping } else if (freqDist == 1) { //WLEDMM: Rightshft: note ewowi: frequencies in comments are not correct - if (useBandPassFilter) { + if (useInputFilter==1) { // skip frequencies below 100hz fftCalc[ 0] = 0.8f * fftAddAvg(1,1); fftCalc[ 1] = 0.9f * fftAddAvg(2,2); @@ -1429,7 +1451,7 @@ class AudioReactive : public Usermod { #endif delay(100); // Give that poor microphone some time to setup. - useBandPassFilter = false; + useInputFilter = 2; // default: DC blocker switch (dmType) { #if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S3) // stub cases for not-yet-supported I2S modes on other ESP32 chips @@ -1447,7 +1469,7 @@ class AudioReactive : public Usermod { break; case 2: DEBUGSR_PRINTLN(F("AR: ES7243 Microphone (right channel only).")); - //useBandPassFilter = true; + //useInputFilter = 0; // in case you need to disable low-cut software filtering audioSource = new ES7243(SAMPLE_RATE, BLOCK_SIZE); delay(100); if (audioSource) audioSource->initialize(sdaPin, sclPin, i2swsPin, i2ssdPin, i2sckPin, mclkPin); @@ -1460,7 +1482,6 @@ class AudioReactive : public Usermod { break; case 4: DEBUGSR_PRINT(F("AR: Generic I2S Microphone with Master Clock - ")); DEBUGSR_PRINTLN(F(I2S_MIC_CHANNEL_TEXT)); - //useBandPassFilter = true; audioSource = new I2SSource(SAMPLE_RATE, BLOCK_SIZE, 1.0f/24.0f); //audioSource = new I2SSource(SAMPLE_RATE, BLOCK_SIZE, 1.0f/24.0f, false); // I2S SLAVE mode - does not work, unfortunately delay(100); @@ -1470,21 +1491,22 @@ class AudioReactive : public Usermod { case 5: DEBUGSR_PRINT(F("AR: I2S PDM Microphone - ")); DEBUGSR_PRINTLN(F(I2S_PDM_MIC_CHANNEL_TEXT)); audioSource = new I2SSource(SAMPLE_RATE, BLOCK_SIZE, 1.0f/4.0f); - useBandPassFilter = true; // this reduces the noise floor on SPM1423 from 5% Vpp (~380) down to 0.05% Vpp (~5) + useInputFilter = 1; // PDM bandpass filter - this reduces the noise floor on SPM1423 from 5% Vpp (~380) down to 0.05% Vpp (~5) delay(100); if (audioSource) audioSource->initialize(i2swsPin, i2ssdPin); break; case 51: DEBUGSR_PRINT(F("AR: Legacy PDM Microphone - ")); DEBUGSR_PRINTLN(F(I2S_PDM_MIC_CHANNEL_TEXT)); audioSource = new I2SSource(SAMPLE_RATE, BLOCK_SIZE, 1.0f); - useBandPassFilter = true; + useInputFilter = 1; // PDM bandpass filter delay(100); if (audioSource) audioSource->initialize(i2swsPin, i2ssdPin); break; #endif case 6: DEBUGSR_PRINTLN(F("AR: ES8388 Source")); - audioSource = new ES8388Source(SAMPLE_RATE, BLOCK_SIZE); + audioSource = new ES8388Source(SAMPLE_RATE, BLOCK_SIZE, 1.0f); + //useInputFilter = 0; // to disable low-cut software filtering and restore previous behaviour delay(100); if (audioSource) audioSource->initialize(sdaPin, sclPin, i2swsPin, i2ssdPin, i2sckPin, mclkPin); break; @@ -1494,7 +1516,7 @@ class AudioReactive : public Usermod { case 0: default: DEBUGSR_PRINTLN(F("AR: Analog Microphone (left channel only).")); - useBandPassFilter = true; + useInputFilter = 1; // PDM bandpass filter seems to work well for analog, too audioSource = new I2SAdcSource(SAMPLE_RATE, BLOCK_SIZE); delay(100); if (audioSource) audioSource->initialize(audioPin); @@ -2102,8 +2124,8 @@ class AudioReactive : public Usermod { if (dmType == 51) dmType = SR_DMTYPE; // MCU does not support legacy PDM #endif #else - if (dmType == 5) useBandPassFilter = true; // enable filter for PDM - if (dmType == 51) useBandPassFilter = true /*false*/; // switch on filter for legacy PDM + if (dmType == 5) useInputFilter = 1; // enable filter for PDM + if (dmType == 51) useInputFilter = 1; // switch on filter for legacy PDM #endif configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["pin"][0], i2ssdPin);