audioreactive usermod update (align with MoonMod code) (#2907)
* audioreactive driver update - Better handling of PDM and I2S Line-in - Bugfixes for ES7243 (allocateMultiplePins) - More error messages for ES7243 - sample scaling (needed for sources that use full scale of samples) * audiorective update * align SR_DEBUG with WLED_DEBUG * optional bandpass filter (needed for PDM mics) * sample scaling for PDM and Line-In * small improvements for analog input * bugfixes and small performance improvements * code for FFT task refactored, for better readablity. Introduces separate functions for filtering and post-processing * small improvement for beat detection * default mic settings can be configured at compile time * correct mic type if MCU does not support PDM or ADC * hide analog PIN config if not supported by MCU * audioreactive updates - minor updates to source code (see discussion in PR #2907) - usermod readme improvements * small readme update * one think I overlooked * ok, another edit. Now its final. Hopefully. * small upps wrong parameter order in debug message.
This commit is contained in:
parent
f7004e7a7c
commit
98138a02e3
@ -4,7 +4,7 @@
|
|||||||
#include <driver/i2s.h>
|
#include <driver/i2s.h>
|
||||||
#include <driver/adc.h>
|
#include <driver/adc.h>
|
||||||
|
|
||||||
#ifndef ESP32
|
#ifndef ARDUINO_ARCH_ESP32
|
||||||
#error This audio reactive usermod does not support the ESP8266.
|
#error This audio reactive usermod does not support the ESP8266.
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -25,38 +25,46 @@
|
|||||||
// #define FFT_SAMPLING_LOG // FFT result debugging
|
// #define FFT_SAMPLING_LOG // FFT result debugging
|
||||||
// #define SR_DEBUG // generic SR DEBUG messages
|
// #define SR_DEBUG // generic SR DEBUG messages
|
||||||
|
|
||||||
|
|
||||||
#ifdef SR_DEBUG
|
#ifdef SR_DEBUG
|
||||||
#define DEBUGSR_PRINT(x) Serial.print(x)
|
#define DEBUGSR_PRINT(x) DEBUGOUT.print(x)
|
||||||
#define DEBUGSR_PRINTLN(x) Serial.println(x)
|
#define DEBUGSR_PRINTLN(x) DEBUGOUT.println(x)
|
||||||
#define DEBUGSR_PRINTF(x...) Serial.printf(x)
|
#define DEBUGSR_PRINTF(x...) DEBUGOUT.printf(x)
|
||||||
#else
|
#else
|
||||||
#define DEBUGSR_PRINT(x)
|
#define DEBUGSR_PRINT(x)
|
||||||
#define DEBUGSR_PRINTLN(x)
|
#define DEBUGSR_PRINTLN(x)
|
||||||
#define DEBUGSR_PRINTF(x...)
|
#define DEBUGSR_PRINTF(x...)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if defined(MIC_LOGGER) || defined(FFT_SAMPLING_LOG)
|
||||||
|
#define PLOT_PRINT(x) DEBUGOUT.print(x)
|
||||||
|
#define PLOT_PRINTLN(x) DEBUGOUT.println(x)
|
||||||
|
#define PLOT_PRINTF(x...) DEBUGOUT.printf(x)
|
||||||
|
#else
|
||||||
|
#define PLOT_PRINT(x)
|
||||||
|
#define PLOT_PRINTLN(x)
|
||||||
|
#define PLOT_PRINTF(x...)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// use audio source class (ESP32 specific)
|
||||||
#include "audio_source.h"
|
#include "audio_source.h"
|
||||||
|
constexpr i2s_port_t I2S_PORT = I2S_NUM_0; // I2S port to use (do not change !)
|
||||||
constexpr i2s_port_t I2S_PORT = I2S_NUM_0;
|
constexpr int BLOCK_SIZE = 128; // I2S buffer size (samples)
|
||||||
constexpr int BLOCK_SIZE = 128;
|
|
||||||
constexpr SRate_t SAMPLE_RATE = 22050; // Base sample rate in Hz - 22Khz is a standard rate. Physical sample time -> 23ms
|
|
||||||
//constexpr SRate_t SAMPLE_RATE = 16000; // 16kHz - use if FFTtask takes more than 20ms. Physical sample time -> 32ms
|
|
||||||
//constexpr SRate_t SAMPLE_RATE = 20480; // Base sample rate in Hz - 20Khz is experimental. Physical sample time -> 25ms
|
|
||||||
//constexpr SRate_t SAMPLE_RATE = 10240; // Base sample rate in Hz - previous default. Physical sample time -> 50ms
|
|
||||||
|
|
||||||
#define FFT_MIN_CYCLE 21 // minimum time before FFT task is repeated. Use with 22Khz sampling
|
|
||||||
//#define FFT_MIN_CYCLE 30 // Use with 16Khz sampling
|
|
||||||
//#define FFT_MIN_CYCLE 23 // minimum time before FFT task is repeated. Use with 20Khz sampling
|
|
||||||
//#define FFT_MIN_CYCLE 46 // minimum time before FFT task is repeated. Use with 10Khz sampling
|
|
||||||
|
|
||||||
// globals
|
// globals
|
||||||
static uint8_t inputLevel = 128; // UI slider value
|
static uint8_t inputLevel = 128; // UI slider value
|
||||||
static uint8_t soundSquelch = 10; // squelch value for volume reactive routines (config value)
|
#ifndef SR_SQUELCH
|
||||||
static uint8_t sampleGain = 60; // sample gain (config value)
|
uint8_t soundSquelch = 10; // squelch value for volume reactive routines (config value)
|
||||||
static uint8_t soundAgc = 0; // Automagic gain control: 0 - none, 1 - normal, 2 - vivid, 3 - lazy (config value)
|
#else
|
||||||
|
uint8_t soundSquelch = SR_SQUELCH; // squelch value for volume reactive routines (config value)
|
||||||
|
#endif
|
||||||
|
#ifndef SR_GAIN
|
||||||
|
uint8_t sampleGain = 60; // sample gain (config value)
|
||||||
|
#else
|
||||||
|
uint8_t sampleGain = SR_GAIN; // sample gain (config value)
|
||||||
|
#endif
|
||||||
|
static uint8_t soundAgc = 1; // 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 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()
|
// user settable parameters for limitSoundDynamics()
|
||||||
static bool limiterOn = true; // bool: enable / disable dynamics limiter
|
static bool limiterOn = true; // bool: enable / disable dynamics limiter
|
||||||
@ -86,11 +94,13 @@ const float agcSampleSmooth[AGC_NUM_PRESETS] = { 1/12.f, 1/6.f, 1/16.f}; //
|
|||||||
|
|
||||||
static AudioSource *audioSource = nullptr;
|
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 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.
|
||||||
|
|
||||||
// audioreactive variables shared with FFT task
|
// audioreactive variables shared with FFT task
|
||||||
static float micDataReal = 0.0f; // MicIn data with full 24bit resolution - lowest 8bit after decimal point
|
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 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 sampleAvg = 0.0f; // Smoothed Average sample - sampleAvg < 1 means "quiet" (simple noise gate)
|
||||||
|
static float sampleAgc = 0.0f; // Smoothed AGC sample
|
||||||
|
|
||||||
// peak detection
|
// peak detection
|
||||||
static bool samplePeak = false; // Boolean flag for peak - used in effects. Responding routine may reset this flag. Auto-reset after strip.getMinShowDelay()
|
static bool samplePeak = false; // Boolean flag for peak - used in effects. Responding routine may reset this flag. Auto-reset after strip.getMinShowDelay()
|
||||||
@ -106,6 +116,62 @@ static void autoResetPeak(void); // peak auto-reset function
|
|||||||
// Begin FFT Code //
|
// Begin FFT Code //
|
||||||
////////////////////
|
////////////////////
|
||||||
|
|
||||||
|
// some prototypes, to ensure consistent interfaces
|
||||||
|
static float mapf(float x, float in_min, float in_max, float out_min, float out_max); // map function for float
|
||||||
|
static float fftAddAvg(int from, int to); // average of several FFT result bins
|
||||||
|
void FFTcode(void * parameter); // audio processing task: read samples, run FFT, fill GEQ channels from FFT results
|
||||||
|
static void runMicFilter(uint16_t numSamples, float *sampleBuffer); // pre-filtering of raw samples (band-pass)
|
||||||
|
static void postProcessFFTResults(bool noiseGateOpen, int numberOfChannels); // post-processing and post-amp of GEQ channels
|
||||||
|
|
||||||
|
#define NUM_GEQ_CHANNELS 16 // number of frequency channels. Don't change !!
|
||||||
|
|
||||||
|
static TaskHandle_t FFT_Task = nullptr;
|
||||||
|
|
||||||
|
// Table of multiplication factors so that we can even out the frequency response.
|
||||||
|
static float fftResultPink[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 };
|
||||||
|
|
||||||
|
// 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 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)
|
||||||
|
static uint64_t fftTime = 0;
|
||||||
|
static uint64_t sampleTime = 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// FFT Task variables (filtering and post-processing)
|
||||||
|
static float fftCalc[NUM_GEQ_CHANNELS] = {0.0f}; // Try and normalize fftBin values to a max of 4096, so that 4096/16 = 256.
|
||||||
|
static float fftAvg[NUM_GEQ_CHANNELS] = {0.0f}; // Calculated frequency channel results, with smoothing (used if dynamics limiter is ON)
|
||||||
|
#ifdef SR_DEBUG
|
||||||
|
static float fftResultMax[NUM_GEQ_CHANNELS] = {0.0f}; // A table used for testing to determine how our post-processing is working.
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// audio source parameters and constant
|
||||||
|
constexpr SRate_t SAMPLE_RATE = 22050; // Base sample rate in Hz - 22Khz is a standard rate. Physical sample time -> 23ms
|
||||||
|
//constexpr SRate_t SAMPLE_RATE = 16000; // 16kHz - use if FFTtask takes more than 20ms. Physical sample time -> 32ms
|
||||||
|
//constexpr SRate_t SAMPLE_RATE = 20480; // Base sample rate in Hz - 20Khz is experimental. Physical sample time -> 25ms
|
||||||
|
//constexpr SRate_t SAMPLE_RATE = 10240; // Base sample rate in Hz - previous default. Physical sample time -> 50ms
|
||||||
|
#define FFT_MIN_CYCLE 21 // minimum time before FFT task is repeated. Use with 22Khz sampling
|
||||||
|
//#define FFT_MIN_CYCLE 30 // Use with 16Khz sampling
|
||||||
|
//#define FFT_MIN_CYCLE 23 // minimum time before FFT task is repeated. Use with 20Khz sampling
|
||||||
|
//#define FFT_MIN_CYCLE 46 // minimum time before FFT task is repeated. Use with 10Khz sampling
|
||||||
|
|
||||||
|
// FFT Constants
|
||||||
|
constexpr uint16_t samplesFFT = 512; // Samples in an FFT batch - This value MUST ALWAYS be a power of 2
|
||||||
|
constexpr uint16_t samplesFFT_2 = 256; // meaningfull part of FFT results - only the "lower half" contains useful information.
|
||||||
|
// the following are observed values, supported by a bit of "educated guessing"
|
||||||
|
//#define FFT_DOWNSCALE 0.65f // 20kHz - downscaling factor for FFT results - "Flat-Top" window @20Khz, old freq channels
|
||||||
|
#define FFT_DOWNSCALE 0.46f // downscaling factor for FFT results - for "Flat-Top" window @22Khz, new freq channels
|
||||||
|
#define LOG_256 5.54517744f // log(256)
|
||||||
|
|
||||||
|
// These are the input and output vectors. Input vectors receive computed results from FFT.
|
||||||
|
static float vReal[samplesFFT] = {0.0f}; // FFT sample inputs / freq output - these are our raw result bins
|
||||||
|
static float vImag[samplesFFT] = {0.0f}; // imaginary parts
|
||||||
|
#ifdef UM_AUDIOREACTIVE_USE_NEW_FFT
|
||||||
|
static float windowWeighingFactors[samplesFFT] = {0.0f};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Create FFT object
|
||||||
#ifdef UM_AUDIOREACTIVE_USE_NEW_FFT
|
#ifdef UM_AUDIOREACTIVE_USE_NEW_FFT
|
||||||
// lib_deps += https://github.com/kosme/arduinoFFT#develop @ 1.9.2
|
// lib_deps += https://github.com/kosme/arduinoFFT#develop @ 1.9.2
|
||||||
#define FFT_SPEED_OVER_PRECISION // enables use of reciprocals (1/x etc), and an a few other speedups
|
#define FFT_SPEED_OVER_PRECISION // enables use of reciprocals (1/x etc), and an a few other speedups
|
||||||
@ -116,58 +182,20 @@ static void autoResetPeak(void); // peak auto-reset function
|
|||||||
#endif
|
#endif
|
||||||
#include <arduinoFFT.h>
|
#include <arduinoFFT.h>
|
||||||
|
|
||||||
// FFT Output variables shared with animations
|
|
||||||
#define NUM_GEQ_CHANNELS 16 // number of frequency channels. Don't change !!
|
|
||||||
static float FFT_MajorPeak = 1.0f; // FFT: strongest (peak) frequency
|
|
||||||
static float FFT_Magnitude = 0.0f; // FFT: volume (magnitude) of peak frequency
|
|
||||||
static uint8_t fftResult[NUM_GEQ_CHANNELS]= {0};// Our calculated freq. channel result table to be used by effects
|
|
||||||
|
|
||||||
// FFT Constants
|
|
||||||
constexpr uint16_t samplesFFT = 512; // Samples in an FFT batch - This value MUST ALWAYS be a power of 2
|
|
||||||
constexpr uint16_t samplesFFT_2 = 256; // meaningfull part of FFT results - only the "lower half" contains useful information.
|
|
||||||
|
|
||||||
// These are the input and output vectors. Input vectors receive computed results from FFT.
|
|
||||||
static float vReal[samplesFFT] = {0.0f}; // FFT sample inputs / freq output - these are our raw result bins
|
|
||||||
static float vImag[samplesFFT] = {0.0f}; // imaginary parts
|
|
||||||
|
|
||||||
// the following are observed values, supported by a bit of "educated guessing"
|
|
||||||
//#define FFT_DOWNSCALE 0.65f // 20kHz - downscaling factor for FFT results - "Flat-Top" window @20Khz, old freq channels
|
|
||||||
#define FFT_DOWNSCALE 0.46f // downscaling factor for FFT results - for "Flat-Top" window @22Khz, new freq channels
|
|
||||||
#define LOG_256 5.54517744
|
|
||||||
|
|
||||||
#ifdef UM_AUDIOREACTIVE_USE_NEW_FFT
|
|
||||||
static float windowWeighingFactors[samplesFFT] = {0.0f};
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Try and normalize fftBin values to a max of 4096, so that 4096/16 = 256.
|
|
||||||
static float fftCalc[NUM_GEQ_CHANNELS] = {0.0f};
|
|
||||||
static float fftAvg[NUM_GEQ_CHANNELS] = {0.0f}; // Calculated frequency channel results, with smoothing (used if dynamics limiter is ON)
|
|
||||||
#ifdef SR_DEBUG
|
|
||||||
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)
|
|
||||||
static uint64_t fftTime = 0;
|
|
||||||
static uint64_t sampleTime = 0;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Table of multiplication factors so that we can even out the frequency response.
|
|
||||||
static float fftResultPink[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 };
|
|
||||||
|
|
||||||
// Create FFT object
|
|
||||||
#ifdef UM_AUDIOREACTIVE_USE_NEW_FFT
|
#ifdef UM_AUDIOREACTIVE_USE_NEW_FFT
|
||||||
static ArduinoFFT<float> FFT = ArduinoFFT<float>( vReal, vImag, samplesFFT, SAMPLE_RATE, windowWeighingFactors);
|
static ArduinoFFT<float> FFT = ArduinoFFT<float>( vReal, vImag, samplesFFT, SAMPLE_RATE, windowWeighingFactors);
|
||||||
#else
|
#else
|
||||||
static arduinoFFT FFT = arduinoFFT(vReal, vImag, samplesFFT, SAMPLE_RATE);
|
static arduinoFFT FFT = arduinoFFT(vReal, vImag, samplesFFT, SAMPLE_RATE);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static TaskHandle_t FFT_Task = nullptr;
|
// Helper functions
|
||||||
|
|
||||||
// float version of map()
|
// float version of map()
|
||||||
static float mapf(float x, float in_min, float in_max, float out_min, float out_max){
|
static float mapf(float x, float in_min, float in_max, float out_min, float out_max){
|
||||||
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
|
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// compute average of several FFT resut bins
|
||||||
static float fftAddAvg(int from, int to) {
|
static float fftAddAvg(int from, int to) {
|
||||||
float result = 0.0f;
|
float result = 0.0f;
|
||||||
for (int i = from; i <= to; i++) {
|
for (int i = from; i <= to; i++) {
|
||||||
@ -176,7 +204,9 @@ static float fftAddAvg(int from, int to) {
|
|||||||
return result / float(to - from + 1);
|
return result / float(to - from + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
// FFT main task
|
// FFT main task
|
||||||
|
//
|
||||||
void FFTcode(void * parameter)
|
void FFTcode(void * parameter)
|
||||||
{
|
{
|
||||||
DEBUGSR_PRINT("FFT started on core: "); DEBUGSR_PRINTLN(xPortGetCoreID());
|
DEBUGSR_PRINT("FFT started on core: "); DEBUGSR_PRINTLN(xPortGetCoreID());
|
||||||
@ -213,6 +243,10 @@ void FFTcode(void * parameter)
|
|||||||
|
|
||||||
xLastWakeTime = xTaskGetTickCount(); // update "last unblocked time" for vTaskDelay
|
xLastWakeTime = xTaskGetTickCount(); // update "last unblocked time" for vTaskDelay
|
||||||
|
|
||||||
|
// band pass filter - can reduce noise floor by a factor of 50
|
||||||
|
// downside: frequencies below 100Hz will be ignored
|
||||||
|
if (useBandPassFilter) runMicFilter(samplesFFT, vReal);
|
||||||
|
|
||||||
// find highest sample in the batch
|
// find highest sample in the batch
|
||||||
float maxSample = 0.0f; // max sample from FFT batch
|
float maxSample = 0.0f; // max sample from FFT batch
|
||||||
for (int i=0; i < samplesFFT; i++) {
|
for (int i=0; i < samplesFFT; i++) {
|
||||||
@ -229,7 +263,7 @@ void FFTcode(void * parameter)
|
|||||||
#ifdef SR_DEBUG
|
#ifdef SR_DEBUG
|
||||||
if (true) { // this allows measure FFT runtimes, as it disables the "only when needed" optimization
|
if (true) { // this allows measure FFT runtimes, as it disables the "only when needed" optimization
|
||||||
#else
|
#else
|
||||||
if (sampleAvg > 0.5f) { // noise gate open means that FFT results will be used. Don't run FFT if results are not needed.
|
if (sampleAvg > 0.25f) { // noise gate open means that FFT results will be used. Don't run FFT if results are not needed.
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// run FFT (takes 3-5ms on ESP32, ~12ms on ESP32-S2)
|
// run FFT (takes 3-5ms on ESP32, ~12ms on ESP32-S2)
|
||||||
@ -273,7 +307,7 @@ void FFTcode(void * parameter)
|
|||||||
} // for()
|
} // for()
|
||||||
|
|
||||||
// mapping of FFT result bins to frequency channels
|
// mapping of FFT result bins to frequency channels
|
||||||
if (sampleAvg > 0.5f) { // noise gate open
|
if (fabsf(sampleAvg) > 0.5f) { // noise gate open
|
||||||
#if 0
|
#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.
|
/* 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.
|
||||||
*
|
*
|
||||||
@ -303,10 +337,22 @@ void FFTcode(void * parameter)
|
|||||||
#else
|
#else
|
||||||
/* new mapping, optimized for 22050 Hz by softhack007 */
|
/* new mapping, optimized for 22050 Hz by softhack007 */
|
||||||
// bins frequency range
|
// bins frequency range
|
||||||
|
if (useBandPassFilter) {
|
||||||
|
// skip frequencies below 100hz
|
||||||
|
fftCalc[ 0] = 0.8f * fftAddAvg(3,4);
|
||||||
|
fftCalc[ 1] = 0.9f * fftAddAvg(4,5);
|
||||||
|
fftCalc[ 2] = fftAddAvg(5,6);
|
||||||
|
fftCalc[ 3] = fftAddAvg(6,7);
|
||||||
|
// don't use the last bins from 206 to 255.
|
||||||
|
fftCalc[15] = fftAddAvg(165,205) * 0.75f; // 40 7106 - 8828 high -- with some damping
|
||||||
|
} else {
|
||||||
fftCalc[ 0] = fftAddAvg(1,2); // 1 43 - 86 sub-bass
|
fftCalc[ 0] = fftAddAvg(1,2); // 1 43 - 86 sub-bass
|
||||||
fftCalc[ 1] = fftAddAvg(2,3); // 1 86 - 129 bass
|
fftCalc[ 1] = fftAddAvg(2,3); // 1 86 - 129 bass
|
||||||
fftCalc[ 2] = fftAddAvg(3,5); // 2 129 - 216 bass
|
fftCalc[ 2] = fftAddAvg(3,5); // 2 129 - 216 bass
|
||||||
fftCalc[ 3] = fftAddAvg(5,7); // 2 216 - 301 bass + midrange
|
fftCalc[ 3] = fftAddAvg(5,7); // 2 216 - 301 bass + midrange
|
||||||
|
// don't use the last bins from 216 to 255. They are usually contaminated by aliasing (aka noise)
|
||||||
|
fftCalc[15] = fftAddAvg(165,215) * 0.70f; // 50 7106 - 9259 high -- with some damping
|
||||||
|
}
|
||||||
fftCalc[ 4] = fftAddAvg(7,10); // 3 301 - 430 midrange
|
fftCalc[ 4] = fftAddAvg(7,10); // 3 301 - 430 midrange
|
||||||
fftCalc[ 5] = fftAddAvg(10,13); // 3 430 - 560 midrange
|
fftCalc[ 5] = fftAddAvg(10,13); // 3 430 - 560 midrange
|
||||||
fftCalc[ 6] = fftAddAvg(13,19); // 5 560 - 818 midrange
|
fftCalc[ 6] = fftAddAvg(13,19); // 5 560 - 818 midrange
|
||||||
@ -318,8 +364,6 @@ void FFTcode(void * parameter)
|
|||||||
fftCalc[12] = fftAddAvg(70,86); // 16 3015 - 3704 high mid
|
fftCalc[12] = fftAddAvg(70,86); // 16 3015 - 3704 high mid
|
||||||
fftCalc[13] = fftAddAvg(86,104); // 18 3704 - 4479 high mid
|
fftCalc[13] = fftAddAvg(86,104); // 18 3704 - 4479 high mid
|
||||||
fftCalc[14] = fftAddAvg(104,165) * 0.88f; // 61 4479 - 7106 high mid + high -- with slight damping
|
fftCalc[14] = fftAddAvg(104,165) * 0.88f; // 61 4479 - 7106 high mid + high -- with slight damping
|
||||||
fftCalc[15] = fftAddAvg(165,215) * 0.70f; // 50 7106 - 9259 high -- with some damping
|
|
||||||
// don't use the last bins from 216 to 255. They are usually contaminated by aliasing (aka noise)
|
|
||||||
#endif
|
#endif
|
||||||
} else { // noise gate closed - just decay old values
|
} else { // noise gate closed - just decay old values
|
||||||
for (int i=0; i < NUM_GEQ_CHANNELS; i++) {
|
for (int i=0; i < NUM_GEQ_CHANNELS; i++) {
|
||||||
@ -329,9 +373,67 @@ void FFTcode(void * parameter)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// post-processing of frequency channels (pink noise adjustment, AGC, smooting, scaling)
|
// post-processing of frequency channels (pink noise adjustment, AGC, smooting, scaling)
|
||||||
for (int i=0; i < NUM_GEQ_CHANNELS; i++) {
|
postProcessFFTResults((fabsf(sampleAvg) > 0.25f)? true : false , NUM_GEQ_CHANNELS);
|
||||||
|
|
||||||
if (sampleAvg > 0.5f) { // noise gate open
|
#if defined(WLED_DEBUG) || defined(SR_DEBUG)
|
||||||
|
if (haveDoneFFT && (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
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
// run peak detection
|
||||||
|
autoResetPeak();
|
||||||
|
detectSamplePeak();
|
||||||
|
|
||||||
|
#if !defined(I2S_GRAB_ADC1_COMPLETELY)
|
||||||
|
if ((audioSource == nullptr) || (audioSource->getType() != AudioSource::Type_I2SAdc)) // the "delay trick" does not help for analog ADC
|
||||||
|
#endif
|
||||||
|
vTaskDelayUntil( &xLastWakeTime, xFrequency); // release CPU, and let I2S fill its buffers
|
||||||
|
|
||||||
|
} // for(;;)ever
|
||||||
|
} // FFTcode() task end
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////
|
||||||
|
// Pre / Postprocessing //
|
||||||
|
///////////////////////////
|
||||||
|
|
||||||
|
static void runMicFilter(uint16_t numSamples, float *sampleBuffer) // pre-filtering of raw samples (band-pass)
|
||||||
|
{
|
||||||
|
// low frequency cutoff parameter - see https://dsp.stackexchange.com/questions/40462/exponential-moving-average-cut-off-frequency
|
||||||
|
//constexpr float alpha = 0.04f; // 150Hz
|
||||||
|
//constexpr float alpha = 0.03f; // 110Hz
|
||||||
|
constexpr float alpha = 0.0225f; // 80hz
|
||||||
|
//constexpr float alpha = 0.01693f;// 60hz
|
||||||
|
// high frequency cutoff parameter
|
||||||
|
//constexpr float beta1 = 0.75f; // 11Khz
|
||||||
|
//constexpr float beta1 = 0.82f; // 15Khz
|
||||||
|
//constexpr float beta1 = 0.8285f; // 18Khz
|
||||||
|
constexpr float beta1 = 0.85f; // 20Khz
|
||||||
|
|
||||||
|
constexpr float beta2 = (1.0f - beta1) / 2.0;
|
||||||
|
static float last_vals[2] = { 0.0f }; // FIR high freq cutoff filter
|
||||||
|
static float lowfilt = 0.0f; // IIR low frequency cutoff filter
|
||||||
|
|
||||||
|
for (int i=0; i < numSamples; i++) {
|
||||||
|
// FIR lowpass, to remove high frequency noise
|
||||||
|
float highFilteredSample;
|
||||||
|
if (i < (numSamples-1)) highFilteredSample = beta1*sampleBuffer[i] + beta2*last_vals[0] + beta2*sampleBuffer[i+1]; // smooth out spikes
|
||||||
|
else highFilteredSample = beta1*sampleBuffer[i] + beta2*last_vals[0] + beta2*last_vals[1]; // spcial handling for last sample in array
|
||||||
|
last_vals[1] = last_vals[0];
|
||||||
|
last_vals[0] = sampleBuffer[i];
|
||||||
|
sampleBuffer[i] = highFilteredSample;
|
||||||
|
// IIR highpass, to remove low frequency noise
|
||||||
|
lowfilt += alpha * (sampleBuffer[i] - lowfilt);
|
||||||
|
sampleBuffer[i] = sampleBuffer[i] - lowfilt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void postProcessFFTResults(bool noiseGateOpen, int numberOfChannels) // post-processing and post-amp of GEQ channels
|
||||||
|
{
|
||||||
|
for (int i=0; i < numberOfChannels; i++) {
|
||||||
|
|
||||||
|
if (noiseGateOpen) { // noise gate open
|
||||||
// Adjustment for frequency curves.
|
// Adjustment for frequency curves.
|
||||||
fftCalc[i] *= fftResultPink[i];
|
fftCalc[i] *= fftResultPink[i];
|
||||||
if (FFTScalingMode > 0) fftCalc[i] *= FFT_DOWNSCALE; // adjustment related to FFT windowing function
|
if (FFTScalingMode > 0) fftCalc[i] *= FFT_DOWNSCALE; // adjustment related to FFT windowing function
|
||||||
@ -401,36 +503,23 @@ void FFTcode(void * parameter)
|
|||||||
}
|
}
|
||||||
fftResult[i] = constrain((int)currentResult, 0, 255);
|
fftResult[i] = constrain((int)currentResult, 0, 255);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
#if defined(WLED_DEBUG) || defined(SR_DEBUG)
|
|
||||||
if (haveDoneFFT && (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
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
// run peak detection
|
|
||||||
autoResetPeak();
|
|
||||||
detectSamplePeak();
|
|
||||||
|
|
||||||
#if !defined(I2S_GRAB_ADC1_COMPLETELY)
|
|
||||||
if ((audioSource == nullptr) || (audioSource->getType() != AudioSource::Type_I2SAdc)) // the "delay trick" does not help for analog ADC
|
|
||||||
#endif
|
|
||||||
vTaskDelayUntil( &xLastWakeTime, xFrequency); // release CPU, and let I2S fill its buffers
|
|
||||||
|
|
||||||
} // for(;;)ever
|
|
||||||
} // FFTcode() task end
|
|
||||||
|
|
||||||
|
|
||||||
////////////////////
|
////////////////////
|
||||||
// Peak detection //
|
// Peak detection //
|
||||||
////////////////////
|
////////////////////
|
||||||
|
|
||||||
// peak detection is called from FFT task when vReal[] contains valid FFT results
|
// peak detection is called from FFT task when vReal[] contains valid FFT results
|
||||||
static void detectSamplePeak(void) {
|
static void detectSamplePeak(void) {
|
||||||
|
bool havePeak = false;
|
||||||
|
|
||||||
// Poor man's beat detection by seeing if sample > Average + some value.
|
// Poor man's beat detection by seeing if sample > Average + some value.
|
||||||
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
|
// 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.
|
// 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)) {
|
||||||
|
havePeak = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (havePeak) {
|
||||||
samplePeak = true;
|
samplePeak = true;
|
||||||
timeOfPeak = millis();
|
timeOfPeak = millis();
|
||||||
udpSamplePeak = true;
|
udpSamplePeak = true;
|
||||||
@ -459,10 +548,11 @@ class AudioReactive : public Usermod {
|
|||||||
#else
|
#else
|
||||||
int8_t audioPin = AUDIOPIN;
|
int8_t audioPin = AUDIOPIN;
|
||||||
#endif
|
#endif
|
||||||
#ifndef DMTYPE // I2S mic type
|
#ifndef SR_DMTYPE // I2S mic type
|
||||||
uint8_t dmType = 1; // 0=none/disabled/analog; 1=generic I2S
|
uint8_t dmType = 1; // 0=none/disabled/analog; 1=generic I2S
|
||||||
|
#define SR_DMTYPE 1 // default type = I2S
|
||||||
#else
|
#else
|
||||||
uint8_t dmType = DMTYPE;
|
uint8_t dmType = SR_DMTYPE;
|
||||||
#endif
|
#endif
|
||||||
#ifndef I2S_SDPIN // aka DOUT
|
#ifndef I2S_SDPIN // aka DOUT
|
||||||
int8_t i2ssdPin = 32;
|
int8_t i2ssdPin = 32;
|
||||||
@ -526,7 +616,6 @@ class AudioReactive : public Usermod {
|
|||||||
|
|
||||||
// variables for UDP sound sync
|
// variables for UDP sound sync
|
||||||
WiFiUDP fftUdp; // UDP object for sound sync (from WiFi UDP, not Async UDP!)
|
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
|
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
|
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
|
uint16_t audioSyncPort= 11988;// default port for UDP sound sync
|
||||||
@ -538,12 +627,11 @@ class AudioReactive : public Usermod {
|
|||||||
// variables used by getSample() and agcAvg()
|
// variables used by getSample() and agcAvg()
|
||||||
int16_t micIn = 0; // Current sample starts with negative values and large values, which is why it's 16 bit signed
|
int16_t micIn = 0; // Current sample starts with negative values and large values, which is why it's 16 bit signed
|
||||||
double sampleMax = 0.0; // Max sample over a few seconds. Needed for AGC controler.
|
double sampleMax = 0.0; // Max sample over a few seconds. Needed for AGC controler.
|
||||||
float micLev = 0.0f; // Used to convert returned value to have '0' as minimum. A leveller
|
double micLev = 0.0; // Used to convert returned value to have '0' as minimum. A leveller
|
||||||
float expAdjF = 0.0f; // Used for exponential filter.
|
float expAdjF = 0.0f; // Used for exponential filter.
|
||||||
float sampleReal = 0.0f; // "sampleRaw" as float, to provide bits that are lost otherwise (before amplification by sampleGain or inputLevel). Needed for AGC.
|
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 sampleRaw = 0; // Current sample. Must only be updated ONCE!!! (amplified mic value by sampleGain and inputLevel)
|
||||||
int16_t rawSampleAgc = 0; // not smoothed AGC sample
|
int16_t rawSampleAgc = 0; // not smoothed AGC sample
|
||||||
float sampleAgc = 0.0f; // Smoothed AGC sample
|
|
||||||
|
|
||||||
// variables used in effects
|
// variables used in effects
|
||||||
float volumeSmth = 0.0f; // either sampleAvg or sampleAgc depending on soundAgc; smoothed sample
|
float volumeSmth = 0.0f; // either sampleAvg or sampleAgc depending on soundAgc; smoothed sample
|
||||||
@ -576,28 +664,28 @@ class AudioReactive : public Usermod {
|
|||||||
if (disableSoundProcessing && (!udpSyncConnected || ((audioSyncEnabled & 0x02) == 0))) return; // no audio availeable
|
if (disableSoundProcessing && (!udpSyncConnected || ((audioSyncEnabled & 0x02) == 0))) return; // no audio availeable
|
||||||
#ifdef MIC_LOGGER
|
#ifdef MIC_LOGGER
|
||||||
// Debugging functions for audio input and sound processing. Comment out the values you want to see
|
// Debugging functions for audio input and sound processing. Comment out the values you want to see
|
||||||
Serial.print("micReal:"); Serial.print(micDataReal); Serial.print("\t");
|
PLOT_PRINT("micReal:"); PLOT_PRINT(micDataReal); PLOT_PRINT("\t");
|
||||||
Serial.print("volumeSmth:"); Serial.print(volumeSmth); Serial.print("\t");
|
PLOT_PRINT("volumeSmth:"); PLOT_PRINT(volumeSmth); PLOT_PRINT("\t");
|
||||||
//Serial.print("volumeRaw:"); Serial.print(volumeRaw); Serial.print("\t");
|
//PLOT_PRINT("volumeRaw:"); PLOT_PRINT(volumeRaw); PLOT_PRINT("\t");
|
||||||
//Serial.print("DC_Level:"); Serial.print(micLev); Serial.print("\t");
|
PLOT_PRINT("DC_Level:"); PLOT_PRINT(micLev); PLOT_PRINT("\t");
|
||||||
//Serial.print("sampleAgc:"); Serial.print(sampleAgc); Serial.print("\t");
|
//PLOT_PRINT("sampleAgc:"); PLOT_PRINT(sampleAgc); PLOT_PRINT("\t");
|
||||||
//Serial.print("sampleAvg:"); Serial.print(sampleAvg); Serial.print("\t");
|
//PLOT_PRINT("sampleAvg:"); PLOT_PRINT(sampleAvg); PLOT_PRINT("\t");
|
||||||
//Serial.print("sampleReal:"); Serial.print(sampleReal); Serial.print("\t");
|
//PLOT_PRINT("sampleReal:"); PLOT_PRINT(sampleReal); PLOT_PRINT("\t");
|
||||||
//Serial.print("micIn:"); Serial.print(micIn); Serial.print("\t");
|
//PLOT_PRINT("micIn:"); PLOT_PRINT(micIn); PLOT_PRINT("\t");
|
||||||
//Serial.print("sample:"); Serial.print(sample); Serial.print("\t");
|
//PLOT_PRINT("sample:"); PLOT_PRINT(sample); PLOT_PRINT("\t");
|
||||||
//Serial.print("sampleMax:"); Serial.print(sampleMax); Serial.print("\t");
|
//PLOT_PRINT("sampleMax:"); PLOT_PRINT(sampleMax); PLOT_PRINT("\t");
|
||||||
//Serial.print("samplePeak:"); Serial.print((samplePeak!=0) ? 128:0); Serial.print("\t");
|
//PLOT_PRINT("samplePeak:"); PLOT_PRINT((samplePeak!=0) ? 128:0); PLOT_PRINT("\t");
|
||||||
//Serial.print("multAgc:"); Serial.print(multAgc, 4); Serial.print("\t");
|
//PLOT_PRINT("multAgc:"); PLOT_PRINT(multAgc, 4); PLOT_PRINT("\t");
|
||||||
Serial.println();
|
PLOT_PRINTLN();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef FFT_SAMPLING_LOG
|
#ifdef FFT_SAMPLING_LOG
|
||||||
#if 0
|
#if 0
|
||||||
for(int i=0; i<NUM_GEQ_CHANNELS; i++) {
|
for(int i=0; i<NUM_GEQ_CHANNELS; i++) {
|
||||||
Serial.print(fftResult[i]);
|
PLOT_PRINT(fftResult[i]);
|
||||||
Serial.print("\t");
|
PLOT_PRINT("\t");
|
||||||
}
|
}
|
||||||
Serial.println();
|
PLOT_PRINTLN();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// OPTIONS are in the following format: Description \n Option
|
// OPTIONS are in the following format: Description \n Option
|
||||||
@ -624,20 +712,21 @@ class AudioReactive : public Usermod {
|
|||||||
if(fftResult[i] < minVal) minVal = fftResult[i];
|
if(fftResult[i] < minVal) minVal = fftResult[i];
|
||||||
}
|
}
|
||||||
for(int i = 0; i < NUM_GEQ_CHANNELS; i++) {
|
for(int i = 0; i < NUM_GEQ_CHANNELS; i++) {
|
||||||
Serial.print(i); Serial.print(":");
|
PLOT_PRINT(i); PLOT_PRINT(":");
|
||||||
Serial.printf("%04ld ", map(fftResult[i], 0, (scaleValuesFromCurrentMaxVal ? maxVal : defaultScalingFromHighValue), (mapValuesToPlotterSpace*i*scalingToHighValue)+0, (mapValuesToPlotterSpace*i*scalingToHighValue)+scalingToHighValue-1));
|
PLOT_PRINTF("%04ld ", map(fftResult[i], 0, (scaleValuesFromCurrentMaxVal ? maxVal : defaultScalingFromHighValue), (mapValuesToPlotterSpace*i*scalingToHighValue)+0, (mapValuesToPlotterSpace*i*scalingToHighValue)+scalingToHighValue-1));
|
||||||
}
|
}
|
||||||
if(printMaxVal) {
|
if(printMaxVal) {
|
||||||
Serial.printf("maxVal:%04d ", maxVal + (mapValuesToPlotterSpace ? 16*256 : 0));
|
PLOT_PRINTF("maxVal:%04d ", maxVal + (mapValuesToPlotterSpace ? 16*256 : 0));
|
||||||
}
|
}
|
||||||
if(printMinVal) {
|
if(printMinVal) {
|
||||||
Serial.printf("%04d:minVal ", minVal); // printed with value first, then label, so negative values can be seen in Serial Monitor but don't throw off y axis in Serial Plotter
|
PLOT_PRINTF("%04d:minVal ", minVal); // printed with value first, then label, so negative values can be seen in Serial Monitor but don't throw off y axis in Serial Plotter
|
||||||
}
|
}
|
||||||
if(mapValuesToPlotterSpace)
|
if(mapValuesToPlotterSpace)
|
||||||
Serial.printf("max:%04d ", (printMaxVal ? 17 : 16)*256); // print line above the maximum value we expect to see on the plotter to avoid autoscaling y axis
|
PLOT_PRINTF("max:%04d ", (printMaxVal ? 17 : 16)*256); // print line above the maximum value we expect to see on the plotter to avoid autoscaling y axis
|
||||||
else
|
else {
|
||||||
Serial.printf("max:%04d ", 256);
|
PLOT_PRINTF("max:%04d ", 256);
|
||||||
Serial.println();
|
}
|
||||||
|
PLOT_PRINTLN();
|
||||||
#endif // FFT_SAMPLING_LOG
|
#endif // FFT_SAMPLING_LOG
|
||||||
} // logAudio()
|
} // logAudio()
|
||||||
|
|
||||||
@ -753,7 +842,7 @@ class AudioReactive : public Usermod {
|
|||||||
micIn = inoise8(millis(), millis()); // Simulated analog read
|
micIn = inoise8(millis(), millis()); // Simulated analog read
|
||||||
micDataReal = micIn;
|
micDataReal = micIn;
|
||||||
#else
|
#else
|
||||||
#ifdef ESP32
|
#ifdef ARDUINO_ARCH_ESP32
|
||||||
micIn = int(micDataReal); // micDataSm = ((micData * 3) + micData)/4;
|
micIn = int(micDataReal); // micDataSm = ((micData * 3) + micData)/4;
|
||||||
#else
|
#else
|
||||||
// this is the minimal code for reading analog mic input on 8266.
|
// this is the minimal code for reading analog mic input on 8266.
|
||||||
@ -770,13 +859,13 @@ class AudioReactive : public Usermod {
|
|||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
micLev = ((micLev * 8191.0f) + micDataReal) / 8192.0f; // takes a few seconds to "catch up" with the Mic Input
|
micLev += (micDataReal-micLev) / 12288.0f;
|
||||||
if(micIn < micLev) micLev = ((micLev * 31.0f) + micDataReal) / 32.0f; // align MicLev to lowest input signal
|
if(micIn < micLev) micLev = ((micLev * 31.0f) + micDataReal) / 32.0f; // align MicLev to lowest input signal
|
||||||
|
|
||||||
micIn -= micLev; // Let's center it to 0 now
|
micIn -= micLev; // Let's center it to 0 now
|
||||||
// Using an exponential filter to smooth out the signal. We'll add controls for this in a future release.
|
// Using an exponential filter to smooth out the signal. We'll add controls for this in a future release.
|
||||||
float micInNoDC = fabsf(micDataReal - micLev);
|
float micInNoDC = fabsf(micDataReal - micLev);
|
||||||
expAdjF = (weighting * micInNoDC + (1.0-weighting) * expAdjF);
|
expAdjF = (weighting * micInNoDC + (1.0f-weighting) * expAdjF);
|
||||||
expAdjF = fabsf(expAdjF); // Now (!) take the absolute value
|
expAdjF = fabsf(expAdjF); // Now (!) take the absolute value
|
||||||
|
|
||||||
expAdjF = (expAdjF <= soundSquelch) ? 0: expAdjF; // simple noise gate
|
expAdjF = (expAdjF <= soundSquelch) ? 0: expAdjF; // simple noise gate
|
||||||
@ -794,6 +883,12 @@ class AudioReactive : public Usermod {
|
|||||||
// keep "peak" sample, but decay value if current sample is below peak
|
// keep "peak" sample, but decay value if current sample is below peak
|
||||||
if ((sampleMax < sampleReal) && (sampleReal > 0.5f)) {
|
if ((sampleMax < sampleReal) && (sampleReal > 0.5f)) {
|
||||||
sampleMax = sampleMax + 0.5f * (sampleReal - sampleMax); // new peak - with some filtering
|
sampleMax = sampleMax + 0.5f * (sampleReal - sampleMax); // new peak - with some filtering
|
||||||
|
// another simple way to detect samplePeak
|
||||||
|
if ((binNum < 10) && (millis() - timeOfPeak > 80) && (sampleAvg > 1)) {
|
||||||
|
samplePeak = true;
|
||||||
|
timeOfPeak = millis();
|
||||||
|
udpSamplePeak = true;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if ((multAgc*sampleMax > agcZoneStop[AGC_preset]) && (soundAgc > 0))
|
if ((multAgc*sampleMax > agcZoneStop[AGC_preset]) && (soundAgc > 0))
|
||||||
sampleMax += 0.5f * (sampleReal - sampleMax); // over AGC Zone - get back quickly
|
sampleMax += 0.5f * (sampleReal - sampleMax); // over AGC Zone - get back quickly
|
||||||
@ -1015,11 +1110,14 @@ class AudioReactive : public Usermod {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Reset I2S peripheral for good measure
|
// Reset I2S peripheral for good measure
|
||||||
i2s_driver_uninstall(I2S_NUM_0);
|
i2s_driver_uninstall(I2S_NUM_0); // E (696) I2S: i2s_driver_uninstall(2006): I2S port 0 has not installed
|
||||||
#if !defined(CONFIG_IDF_TARGET_ESP32C3)
|
#if !defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||||
|
delay(100);
|
||||||
periph_module_reset(PERIPH_I2S0_MODULE); // not possible on -C3
|
periph_module_reset(PERIPH_I2S0_MODULE); // not possible on -C3
|
||||||
#endif
|
#endif
|
||||||
delay(100); // Give that poor microphone some time to setup.
|
delay(100); // Give that poor microphone some time to setup.
|
||||||
|
|
||||||
|
useBandPassFilter = false;
|
||||||
switch (dmType) {
|
switch (dmType) {
|
||||||
#if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S3)
|
#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
|
// stub cases for not-yet-supported I2S modes on other ESP32 chips
|
||||||
@ -1048,14 +1146,15 @@ class AudioReactive : public Usermod {
|
|||||||
break;
|
break;
|
||||||
case 4:
|
case 4:
|
||||||
DEBUGSR_PRINT(F("AR: Generic I2S Microphone with Master Clock - ")); DEBUGSR_PRINTLN(F(I2S_MIC_CHANNEL_TEXT));
|
DEBUGSR_PRINT(F("AR: Generic I2S Microphone with Master Clock - ")); DEBUGSR_PRINTLN(F(I2S_MIC_CHANNEL_TEXT));
|
||||||
audioSource = new I2SSource(SAMPLE_RATE, BLOCK_SIZE);
|
audioSource = new I2SSource(SAMPLE_RATE, BLOCK_SIZE, 1.0f/24.0f);
|
||||||
delay(100);
|
delay(100);
|
||||||
if (audioSource) audioSource->initialize(i2swsPin, i2ssdPin, i2sckPin, mclkPin);
|
if (audioSource) audioSource->initialize(i2swsPin, i2ssdPin, i2sckPin, mclkPin);
|
||||||
break;
|
break;
|
||||||
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3)
|
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||||
case 5:
|
case 5:
|
||||||
DEBUGSR_PRINT(F("AR: I2S PDM Microphone - ")); DEBUGSR_PRINTLN(F(I2S_MIC_CHANNEL_TEXT));
|
DEBUGSR_PRINT(F("AR: I2S PDM Microphone - ")); DEBUGSR_PRINTLN(F(I2S_PDM_MIC_CHANNEL_TEXT));
|
||||||
audioSource = new I2SSource(SAMPLE_RATE, BLOCK_SIZE);
|
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)
|
||||||
delay(100);
|
delay(100);
|
||||||
if (audioSource) audioSource->initialize(i2swsPin, i2ssdPin);
|
if (audioSource) audioSource->initialize(i2swsPin, i2ssdPin);
|
||||||
break;
|
break;
|
||||||
@ -1079,7 +1178,11 @@ class AudioReactive : public Usermod {
|
|||||||
if (enabled) disableSoundProcessing = false; // all good - enable audio processing
|
if (enabled) disableSoundProcessing = false; // all good - enable audio processing
|
||||||
|
|
||||||
if((!audioSource) || (!audioSource->isInitialized())) { // audio source failed to initialize. Still stay "enabled", as there might be input arriving via UDP Sound Sync
|
if((!audioSource) || (!audioSource->isInitialized())) { // audio source failed to initialize. Still stay "enabled", as there might be input arriving via UDP Sound Sync
|
||||||
|
#ifdef WLED_DEBUG
|
||||||
|
DEBUG_PRINTLN(F("AR: Failed to initialize sound input driver. Please check input PIN settings."));
|
||||||
|
#else
|
||||||
DEBUGSR_PRINTLN(F("AR: Failed to initialize sound input driver. Please check input PIN settings."));
|
DEBUGSR_PRINTLN(F("AR: Failed to initialize sound input driver. Please check input PIN settings."));
|
||||||
|
#endif
|
||||||
disableSoundProcessing = true;
|
disableSoundProcessing = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1353,10 +1456,11 @@ class AudioReactive : public Usermod {
|
|||||||
if (enabled) {
|
if (enabled) {
|
||||||
// Input Level Slider
|
// Input Level Slider
|
||||||
if (disableSoundProcessing == false) { // only show slider when audio processing is running
|
if (disableSoundProcessing == false) { // only show slider when audio processing is running
|
||||||
if (soundAgc > 0)
|
if (soundAgc > 0) {
|
||||||
infoArr = user.createNestedArray(F("GEQ Input Level")); // if AGC is on, this slider only affects fftResult[] frequencies
|
infoArr = user.createNestedArray(F("GEQ Input Level")); // if AGC is on, this slider only affects fftResult[] frequencies
|
||||||
else
|
} else {
|
||||||
infoArr = user.createNestedArray(F("Audio Input Level"));
|
infoArr = user.createNestedArray(F("Audio Input Level"));
|
||||||
|
}
|
||||||
uiDomString = F("<div class=\"slider\"><div class=\"sliderwrap il\"><input class=\"noslide\" onchange=\"requestJson({");
|
uiDomString = F("<div class=\"slider\"><div class=\"sliderwrap il\"><input class=\"noslide\" onchange=\"requestJson({");
|
||||||
uiDomString += FPSTR(_name);
|
uiDomString += FPSTR(_name);
|
||||||
uiDomString += F(":{");
|
uiDomString += F(":{");
|
||||||
@ -1541,8 +1645,10 @@ class AudioReactive : public Usermod {
|
|||||||
JsonObject top = root.createNestedObject(FPSTR(_name));
|
JsonObject top = root.createNestedObject(FPSTR(_name));
|
||||||
top[FPSTR(_enabled)] = enabled;
|
top[FPSTR(_enabled)] = enabled;
|
||||||
|
|
||||||
|
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||||
JsonObject amic = top.createNestedObject(FPSTR(_analogmic));
|
JsonObject amic = top.createNestedObject(FPSTR(_analogmic));
|
||||||
amic["pin"] = audioPin;
|
amic["pin"] = audioPin;
|
||||||
|
#endif
|
||||||
|
|
||||||
JsonObject dmic = top.createNestedObject(FPSTR(_digitalmic));
|
JsonObject dmic = top.createNestedObject(FPSTR(_digitalmic));
|
||||||
dmic[F("type")] = dmType;
|
dmic[F("type")] = dmType;
|
||||||
@ -1595,9 +1701,20 @@ class AudioReactive : public Usermod {
|
|||||||
|
|
||||||
configComplete &= getJsonValue(top[FPSTR(_enabled)], enabled);
|
configComplete &= getJsonValue(top[FPSTR(_enabled)], enabled);
|
||||||
|
|
||||||
|
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||||
configComplete &= getJsonValue(top[FPSTR(_analogmic)]["pin"], audioPin);
|
configComplete &= getJsonValue(top[FPSTR(_analogmic)]["pin"], audioPin);
|
||||||
|
#else
|
||||||
|
audioPin = -1; // MCU does not support analog mic
|
||||||
|
#endif
|
||||||
|
|
||||||
configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["type"], dmType);
|
configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["type"], dmType);
|
||||||
|
#if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||||
|
if (dmType == 0) dmType = SR_DMTYPE; // MCU does not support analog
|
||||||
|
#if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||||
|
if (dmType == 5) dmType = SR_DMTYPE; // MCU does not support PDM
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["pin"][0], i2ssdPin);
|
configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["pin"][0], i2ssdPin);
|
||||||
configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["pin"][1], i2swsPin);
|
configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["pin"][1], i2swsPin);
|
||||||
configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["pin"][2], i2sckPin);
|
configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["pin"][2], i2sckPin);
|
||||||
|
@ -23,11 +23,15 @@
|
|||||||
|
|
||||||
// see https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/hw-reference/chip-series-comparison.html#related-documents
|
// see https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/hw-reference/chip-series-comparison.html#related-documents
|
||||||
// and https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/api-reference/peripherals/i2s.html#overview-of-all-modes
|
// and https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/api-reference/peripherals/i2s.html#overview-of-all-modes
|
||||||
#if defined(CONFIG_IDF_TARGET_ESP32C2) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C5) || defined(CONFIG_IDF_TARGET_ESP32C6) || defined(CONFIG_IDF_TARGET_ESP32H2)
|
#if defined(CONFIG_IDF_TARGET_ESP32C2) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C5) || defined(CONFIG_IDF_TARGET_ESP32C6) || defined(CONFIG_IDF_TARGET_ESP32H2) || defined(ESP8266) || defined(ESP8265)
|
||||||
// there are two things in these MCUs that could lead to problems with audio processing:
|
// there are two things in these MCUs that could lead to problems with audio processing:
|
||||||
// * no floating point hardware (FPU) support - FFT uses float calculations. If done in software, a strong slow-down can be expected (between 8x and 20x)
|
// * no floating point hardware (FPU) support - FFT uses float calculations. If done in software, a strong slow-down can be expected (between 8x and 20x)
|
||||||
// * single core, so FFT task might slow down other things like LED updates
|
// * single core, so FFT task might slow down other things like LED updates
|
||||||
|
#if !defined(SOC_I2S_NUM) || (SOC_I2S_NUM < 1)
|
||||||
|
#error This audio reactive usermod does not support ESP32-C2, ESP32-C3 or ESP32-S2.
|
||||||
|
#else
|
||||||
#warning This audio reactive usermod does not support ESP32-C2, ESP32-C3 or ESP32-S2.
|
#warning This audio reactive usermod does not support ESP32-C2, ESP32-C3 or ESP32-S2.
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* ToDo: remove. ES7243 is controlled via compiler defines
|
/* ToDo: remove. ES7243 is controlled via compiler defines
|
||||||
@ -76,11 +80,15 @@
|
|||||||
#ifdef I2S_USE_RIGHT_CHANNEL
|
#ifdef I2S_USE_RIGHT_CHANNEL
|
||||||
#define I2S_MIC_CHANNEL I2S_CHANNEL_FMT_ONLY_LEFT
|
#define I2S_MIC_CHANNEL I2S_CHANNEL_FMT_ONLY_LEFT
|
||||||
#define I2S_MIC_CHANNEL_TEXT "right channel only (work-around swapped channel bug in IDF 4.4)."
|
#define I2S_MIC_CHANNEL_TEXT "right channel only (work-around swapped channel bug in IDF 4.4)."
|
||||||
|
#define I2S_PDM_MIC_CHANNEL I2S_CHANNEL_FMT_ONLY_RIGHT
|
||||||
|
#define I2S_PDM_MIC_CHANNEL_TEXT "right channel only"
|
||||||
#else
|
#else
|
||||||
//#define I2S_MIC_CHANNEL I2S_CHANNEL_FMT_ALL_LEFT
|
//#define I2S_MIC_CHANNEL I2S_CHANNEL_FMT_ALL_LEFT
|
||||||
//#define I2S_MIC_CHANNEL I2S_CHANNEL_FMT_RIGHT_LEFT
|
//#define I2S_MIC_CHANNEL I2S_CHANNEL_FMT_RIGHT_LEFT
|
||||||
#define I2S_MIC_CHANNEL I2S_CHANNEL_FMT_ONLY_RIGHT
|
#define I2S_MIC_CHANNEL I2S_CHANNEL_FMT_ONLY_RIGHT
|
||||||
#define I2S_MIC_CHANNEL_TEXT "left channel only (work-around swapped channel bug in IDF 4.4)."
|
#define I2S_MIC_CHANNEL_TEXT "left channel only (work-around swapped channel bug in IDF 4.4)."
|
||||||
|
#define I2S_PDM_MIC_CHANNEL I2S_CHANNEL_FMT_ONLY_LEFT
|
||||||
|
#define I2S_PDM_MIC_CHANNEL_TEXT "left channel only."
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#else
|
#else
|
||||||
@ -92,6 +100,9 @@
|
|||||||
#define I2S_MIC_CHANNEL I2S_CHANNEL_FMT_ONLY_LEFT
|
#define I2S_MIC_CHANNEL I2S_CHANNEL_FMT_ONLY_LEFT
|
||||||
#define I2S_MIC_CHANNEL_TEXT "left channel only."
|
#define I2S_MIC_CHANNEL_TEXT "left channel only."
|
||||||
#endif
|
#endif
|
||||||
|
#define I2S_PDM_MIC_CHANNEL I2S_MIC_CHANNEL
|
||||||
|
#define I2S_PDM_MIC_CHANNEL_TEXT I2S_MIC_CHANNEL_TEXT
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
@ -138,15 +149,17 @@ class AudioSource {
|
|||||||
virtual I2S_datatype postProcessSample(I2S_datatype sample_in) {return(sample_in);} // default method can be overriden by instances (ADC) that need sample postprocessing
|
virtual I2S_datatype postProcessSample(I2S_datatype sample_in) {return(sample_in);} // default method can be overriden by instances (ADC) that need sample postprocessing
|
||||||
|
|
||||||
// Private constructor, to make sure it is not callable except from derived classes
|
// Private constructor, to make sure it is not callable except from derived classes
|
||||||
AudioSource(SRate_t sampleRate, int blockSize) :
|
AudioSource(SRate_t sampleRate, int blockSize, float sampleScale) :
|
||||||
_sampleRate(sampleRate),
|
_sampleRate(sampleRate),
|
||||||
_blockSize(blockSize),
|
_blockSize(blockSize),
|
||||||
_initialized(false)
|
_initialized(false),
|
||||||
|
_sampleScale(sampleScale)
|
||||||
{};
|
{};
|
||||||
|
|
||||||
SRate_t _sampleRate; // Microphone sampling rate
|
SRate_t _sampleRate; // Microphone sampling rate
|
||||||
int _blockSize; // I2S block size
|
int _blockSize; // I2S block size
|
||||||
bool _initialized; // Gets set to true if initialization is successful
|
bool _initialized; // Gets set to true if initialization is successful
|
||||||
|
float _sampleScale; // pre-scaling factor for I2S samples
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Basic I2S microphone source
|
/* Basic I2S microphone source
|
||||||
@ -154,8 +167,8 @@ class AudioSource {
|
|||||||
*/
|
*/
|
||||||
class I2SSource : public AudioSource {
|
class I2SSource : public AudioSource {
|
||||||
public:
|
public:
|
||||||
I2SSource(SRate_t sampleRate, int blockSize) :
|
I2SSource(SRate_t sampleRate, int blockSize, float sampleScale = 1.0f) :
|
||||||
AudioSource(sampleRate, blockSize) {
|
AudioSource(sampleRate, blockSize, sampleScale) {
|
||||||
_config = {
|
_config = {
|
||||||
.mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX),
|
.mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX),
|
||||||
.sample_rate = _sampleRate,
|
.sample_rate = _sampleRate,
|
||||||
@ -195,18 +208,51 @@ class I2SSource : public AudioSource {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 2, 0)
|
||||||
|
#if !defined(SOC_I2S_SUPPORTS_PDM_RX)
|
||||||
|
#warning this MCU does not support PDM microphones
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3)
|
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||||
// This is an I2S PDM microphone, these microphones only use a clock and
|
// This is an I2S PDM microphone, these microphones only use a clock and
|
||||||
// data line, to make it simpler to debug, use the WS pin as CLK and SD
|
// data line, to make it simpler to debug, use the WS pin as CLK and SD pin as DATA
|
||||||
// pin as DATA
|
// example from espressif: https://github.com/espressif/esp-idf/blob/release/v4.4/examples/peripherals/i2s/i2s_audio_recorder_sdcard/main/i2s_recorder_main.c
|
||||||
|
|
||||||
|
// note to self: PDM has known bugs on S3, and does not work on C3
|
||||||
|
// * S3: PDM sample rate only at 50% of expected rate: https://github.com/espressif/esp-idf/issues/9893
|
||||||
|
// * S3: I2S PDM has very low amplitude: https://github.com/espressif/esp-idf/issues/8660
|
||||||
|
// * C3: does not support PDM to PCM input. SoC would allow PDM RX, but there is no hardware to directly convert to PCM so it will not work. https://github.com/espressif/esp-idf/issues/8796
|
||||||
|
|
||||||
_config.mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_PDM); // Change mode to pdm if clock pin not provided. PDM is not supported on ESP32-S2. PDM RX not supported on ESP32-C3
|
_config.mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_PDM); // Change mode to pdm if clock pin not provided. PDM is not supported on ESP32-S2. PDM RX not supported on ESP32-C3
|
||||||
|
_config.channel_format =I2S_PDM_MIC_CHANNEL; // seems that PDM mono mode always uses left channel.
|
||||||
|
_config.use_apll = true; // experimental - use aPLL clock source to improve sampling quality
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 2, 0)
|
||||||
|
if (mclkPin != I2S_PIN_NO_CHANGE) {
|
||||||
|
_config.use_apll = true; // experimental - use aPLL clock source to improve sampling quality, and to avoid glitches.
|
||||||
|
// //_config.fixed_mclk = 512 * _sampleRate;
|
||||||
|
// //_config.fixed_mclk = 256 * _sampleRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if !defined(SOC_I2S_SUPPORTS_APLL)
|
||||||
|
#warning this MCU does not have an APLL high accuracy clock for audio
|
||||||
|
// S3: not supported; S2: supported; C3: not supported
|
||||||
|
_config.use_apll = false; // APLL not supported on this MCU
|
||||||
|
#endif
|
||||||
|
#if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32S3) && !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||||
|
if (ESP.getChipRevision() == 0) _config.use_apll = false; // APLL is broken on ESP32 revision 0
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
// Reserve the master clock pin if provided
|
// Reserve the master clock pin if provided
|
||||||
_mclkPin = mclkPin;
|
_mclkPin = mclkPin;
|
||||||
if (mclkPin != I2S_PIN_NO_CHANGE) {
|
if (mclkPin != I2S_PIN_NO_CHANGE) {
|
||||||
if(!pinManager.allocatePin(mclkPin, true, PinOwner::UM_Audioreactive)) return;
|
if(!pinManager.allocatePin(mclkPin, true, PinOwner::UM_Audioreactive)) {
|
||||||
|
DEBUGSR_PRINTF("\nAR: Failed to allocate I2S pin: MCLK=%d\n", mclkPin);
|
||||||
|
return;
|
||||||
|
} else
|
||||||
_routeMclk(mclkPin);
|
_routeMclk(mclkPin);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -220,15 +266,25 @@ class I2SSource : public AudioSource {
|
|||||||
.data_in_num = i2ssdPin
|
.data_in_num = i2ssdPin
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//DEBUGSR_PRINTF("[AR] I2S: SD=%d, WS=%d, SCK=%d, MCLK=%d\n", i2ssdPin, i2swsPin, i2sckPin, mclkPin);
|
||||||
|
|
||||||
esp_err_t err = i2s_driver_install(I2S_NUM_0, &_config, 0, nullptr);
|
esp_err_t err = i2s_driver_install(I2S_NUM_0, &_config, 0, nullptr);
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
DEBUGSR_PRINTF("Failed to install i2s driver: %d\n", err);
|
DEBUGSR_PRINTF("AR: Failed to install i2s driver: %d\n", err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DEBUGSR_PRINTF("AR: I2S#0 driver %s aPLL; fixed_mclk=%d.\n", _config.use_apll? "uses":"without", _config.fixed_mclk);
|
||||||
|
DEBUGSR_PRINTF("AR: %d bits, Sample scaling factor = %6.4f\n", _config.bits_per_sample, _sampleScale);
|
||||||
|
if (_config.mode & I2S_MODE_PDM) {
|
||||||
|
DEBUGSR_PRINTLN(F("AR: I2S#0 driver installed in PDM MASTER mode."));
|
||||||
|
} else {
|
||||||
|
DEBUGSR_PRINTLN(F("AR: I2S#0 driver installed in MASTER mode."));
|
||||||
|
}
|
||||||
|
|
||||||
err = i2s_set_pin(I2S_NUM_0, &_pinConfig);
|
err = i2s_set_pin(I2S_NUM_0, &_pinConfig);
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
DEBUGSR_PRINTF("Failed to set i2s pin config: %d\n", err);
|
DEBUGSR_PRINTF("AR: Failed to set i2s pin config: %d\n", err);
|
||||||
i2s_driver_uninstall(I2S_NUM_0); // uninstall already-installed driver
|
i2s_driver_uninstall(I2S_NUM_0); // uninstall already-installed driver
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -236,7 +292,7 @@ class I2SSource : public AudioSource {
|
|||||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 2, 0)
|
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 2, 0)
|
||||||
err = i2s_set_clk(I2S_NUM_0, _sampleRate, I2S_SAMPLE_RESOLUTION, I2S_CHANNEL_MONO); // set bit clocks. Also takes care of MCLK routing if needed.
|
err = i2s_set_clk(I2S_NUM_0, _sampleRate, I2S_SAMPLE_RESOLUTION, I2S_CHANNEL_MONO); // set bit clocks. Also takes care of MCLK routing if needed.
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
DEBUGSR_PRINTF("Failed to configure i2s clocks: %d\n", err);
|
DEBUGSR_PRINTF("AR: Failed to configure i2s clocks: %d\n", err);
|
||||||
i2s_driver_uninstall(I2S_NUM_0); // uninstall already-installed driver
|
i2s_driver_uninstall(I2S_NUM_0); // uninstall already-installed driver
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -288,6 +344,7 @@ class I2SSource : public AudioSource {
|
|||||||
currSample = (float) newSamples[i]; // 16bit input -> use as-is
|
currSample = (float) newSamples[i]; // 16bit input -> use as-is
|
||||||
#endif
|
#endif
|
||||||
buffer[i] = currSample;
|
buffer[i] = currSample;
|
||||||
|
buffer[i] *= _sampleScale; // scale samples
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -328,18 +385,25 @@ class ES7243 : public I2SSource {
|
|||||||
private:
|
private:
|
||||||
// I2C initialization functions for ES7243
|
// I2C initialization functions for ES7243
|
||||||
void _es7243I2cBegin() {
|
void _es7243I2cBegin() {
|
||||||
Wire.begin(pin_ES7243_SDA, pin_ES7243_SCL, 100000U);
|
bool i2c_initialized = Wire.begin(pin_ES7243_SDA, pin_ES7243_SCL, 100000U);
|
||||||
|
if (i2c_initialized == false) {
|
||||||
|
DEBUGSR_PRINTLN(F("AR: ES7243 failed to initialize I2C bus driver."));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _es7243I2cWrite(uint8_t reg, uint8_t val) {
|
void _es7243I2cWrite(uint8_t reg, uint8_t val) {
|
||||||
#ifndef ES7243_ADDR
|
#ifndef ES7243_ADDR
|
||||||
Wire.beginTransmission(0x13);
|
Wire.beginTransmission(0x13);
|
||||||
|
#define ES7243_ADDR 0x13 // default address
|
||||||
#else
|
#else
|
||||||
Wire.beginTransmission(ES7243_ADDR);
|
Wire.beginTransmission(ES7243_ADDR);
|
||||||
#endif
|
#endif
|
||||||
Wire.write((uint8_t)reg);
|
Wire.write((uint8_t)reg);
|
||||||
Wire.write((uint8_t)val);
|
Wire.write((uint8_t)val);
|
||||||
Wire.endTransmission();
|
uint8_t i2cErr = Wire.endTransmission(); // i2cErr == 0 means OK
|
||||||
|
if (i2cErr != 0) {
|
||||||
|
DEBUGSR_PRINTF("AR: ES7243 I2C write failed with error=%d (addr=0x%X, reg 0x%X, val 0x%X).\n", i2cErr, ES7243_ADDR, reg, val);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _es7243InitAdc() {
|
void _es7243InitAdc() {
|
||||||
@ -353,15 +417,28 @@ class ES7243 : public I2SSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ES7243(SRate_t sampleRate, int blockSize) :
|
ES7243(SRate_t sampleRate, int blockSize, float sampleScale = 1.0f) :
|
||||||
I2SSource(sampleRate, blockSize) {
|
I2SSource(sampleRate, blockSize, sampleScale) {
|
||||||
_config.channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT;
|
_config.channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT;
|
||||||
};
|
};
|
||||||
|
|
||||||
void initialize(int8_t sdaPin, int8_t sclPin, int8_t i2swsPin, int8_t i2ssdPin, int8_t i2sckPin, int8_t mclkPin) {
|
void initialize(int8_t sdaPin, int8_t sclPin, int8_t i2swsPin, int8_t i2ssdPin, int8_t i2sckPin, int8_t mclkPin) {
|
||||||
|
// check that pins are valid
|
||||||
|
if ((sdaPin < 0) || (sclPin < 0)) {
|
||||||
|
DEBUGSR_PRINTF("\nAR: invalid ES7243 I2C pins: SDA=%d, SCL=%d\n", sdaPin, sclPin);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((i2sckPin < 0) || (mclkPin < 0)) {
|
||||||
|
DEBUGSR_PRINTF("\nAR: invalid I2S pin: SCK=%d, MCLK=%d\n", i2sckPin, mclkPin);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Reserve SDA and SCL pins of the I2C interface
|
// Reserve SDA and SCL pins of the I2C interface
|
||||||
if (!pinManager.allocatePin(sdaPin, true, PinOwner::HW_I2C) ||
|
PinManagerPinType es7243Pins[2] = { { sdaPin, true }, { sclPin, true } };
|
||||||
!pinManager.allocatePin(sclPin, true, PinOwner::HW_I2C)) {
|
if (!pinManager.allocateMultiplePins(es7243Pins, 2, PinOwner::HW_I2C)) {
|
||||||
|
pinManager.deallocateMultiplePins(es7243Pins, 2, PinOwner::HW_I2C);
|
||||||
|
DEBUGSR_PRINTF("\nAR: Failed to allocate ES7243 I2C pins: SDA=%d, SCL=%d\n", sdaPin, sclPin);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -375,8 +452,8 @@ public:
|
|||||||
|
|
||||||
void deinitialize() {
|
void deinitialize() {
|
||||||
// Release SDA and SCL pins of the I2C interface
|
// Release SDA and SCL pins of the I2C interface
|
||||||
pinManager.deallocatePin(pin_ES7243_SDA, PinOwner::HW_I2C);
|
PinManagerPinType es7243Pins[2] = { { pin_ES7243_SDA, true }, { pin_ES7243_SCL, true } };
|
||||||
pinManager.deallocatePin(pin_ES7243_SCL, PinOwner::HW_I2C);
|
pinManager.deallocateMultiplePins(es7243Pins, 2, PinOwner::HW_I2C);
|
||||||
I2SSource::deinitialize();
|
I2SSource::deinitialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -385,6 +462,13 @@ public:
|
|||||||
int8_t pin_ES7243_SCL;
|
int8_t pin_ES7243_SCL;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 2, 0)
|
||||||
|
#if !defined(SOC_I2S_SUPPORTS_ADC) && !defined(SOC_I2S_SUPPORTS_ADC_DAC)
|
||||||
|
#warning this MCU does not support analog sound input
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3)
|
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3)
|
||||||
// ADC over I2S is only availeable in "classic" ESP32
|
// ADC over I2S is only availeable in "classic" ESP32
|
||||||
|
|
||||||
@ -395,8 +479,8 @@ public:
|
|||||||
*/
|
*/
|
||||||
class I2SAdcSource : public I2SSource {
|
class I2SAdcSource : public I2SSource {
|
||||||
public:
|
public:
|
||||||
I2SAdcSource(SRate_t sampleRate, int blockSize) :
|
I2SAdcSource(SRate_t sampleRate, int blockSize, float sampleScale = 1.0f) :
|
||||||
I2SSource(sampleRate, blockSize) {
|
I2SSource(sampleRate, blockSize, sampleScale) {
|
||||||
_config = {
|
_config = {
|
||||||
.mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_ADC_BUILT_IN),
|
.mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_ADC_BUILT_IN),
|
||||||
.sample_rate = _sampleRate,
|
.sample_rate = _sampleRate,
|
||||||
@ -430,7 +514,7 @@ class I2SAdcSource : public I2SSource {
|
|||||||
// Determine Analog channel. Only Channels on ADC1 are supported
|
// Determine Analog channel. Only Channels on ADC1 are supported
|
||||||
int8_t channel = digitalPinToAnalogChannel(_audioPin);
|
int8_t channel = digitalPinToAnalogChannel(_audioPin);
|
||||||
if (channel > 9) {
|
if (channel > 9) {
|
||||||
DEBUGSR_PRINTF("Incompatible GPIO used for audio in: %d\n", _audioPin);
|
DEBUGSR_PRINTF("Incompatible GPIO used for analog audio input: %d\n", _audioPin);
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
adc_gpio_init(ADC_UNIT_1, adc_channel_t(channel));
|
adc_gpio_init(ADC_UNIT_1, adc_channel_t(channel));
|
||||||
@ -465,11 +549,12 @@ class I2SAdcSource : public I2SSource {
|
|||||||
//return;
|
//return;
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
err = i2s_adc_disable(I2S_NUM_0);
|
// bugfix: do not disable ADC initially - its already disabled after driver install.
|
||||||
//err = i2s_stop(I2S_NUM_0);
|
//err = i2s_adc_disable(I2S_NUM_0);
|
||||||
if (err != ESP_OK) {
|
// //err = i2s_stop(I2S_NUM_0);
|
||||||
DEBUGSR_PRINTF("Failed to initially disable i2s adc: %d\n", err);
|
//if (err != ESP_OK) {
|
||||||
}
|
// DEBUGSR_PRINTF("Failed to initially disable i2s adc: %d\n", err);
|
||||||
|
//}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
_initialized = true;
|
_initialized = true;
|
||||||
@ -585,8 +670,8 @@ class I2SAdcSource : public I2SSource {
|
|||||||
// a user recommended this: Try to set .communication_format to I2S_COMM_FORMAT_STAND_I2S and call i2s_set_clk() after i2s_set_pin().
|
// a user recommended this: Try to set .communication_format to I2S_COMM_FORMAT_STAND_I2S and call i2s_set_clk() after i2s_set_pin().
|
||||||
class SPH0654 : public I2SSource {
|
class SPH0654 : public I2SSource {
|
||||||
public:
|
public:
|
||||||
SPH0654(SRate_t sampleRate, int blockSize) :
|
SPH0654(SRate_t sampleRate, int blockSize, float sampleScale = 1.0f) :
|
||||||
I2SSource(sampleRate, blockSize)
|
I2SSource(sampleRate, blockSize, sampleScale)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
void initialize(uint8_t i2swsPin, uint8_t i2ssdPin, uint8_t i2sckPin, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE) {
|
void initialize(uint8_t i2swsPin, uint8_t i2ssdPin, uint8_t i2sckPin, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE) {
|
||||||
|
@ -1,36 +1,73 @@
|
|||||||
# Audioreactive usermod
|
# Audioreactive usermod
|
||||||
|
|
||||||
This usermod allows controlling LEDs using audio input. Audio input can be either microphone or analog-in (AUX) using appropriate adapter.
|
This usermod allows controlling LEDs using audio input. Audio input can be either microphone or analog-in (AUX) using appropriate adapter.
|
||||||
Supported microphones range from analog (MAX4466, MAX9814, ...) to digital (INMP441, ICS-43434, ...).
|
Supported microphones range from cheap analog (MAX4466, MAX9814, ...) to high quality digital (INMP441, ICS-43434, ...) and dgital Line-In.
|
||||||
|
|
||||||
The usermod does audio processing and provides data structure that specially written effect can use.
|
The usermod does audio processing and provides data structure that specially written effect can use.
|
||||||
|
|
||||||
The usermod **does not** provide effects or draws anything to LED strip/matrix.
|
The usermod **does not** provide effects or draws anything to LED strip/matrix.
|
||||||
|
|
||||||
|
## Additional Documentation
|
||||||
|
This usermod is an evolution of [SR-WLED](https://github.com/atuline/WLED), and a lot of documentation and information can be found in the [SR-WLED wiki](https://github.com/atuline/WLED/wiki):
|
||||||
|
* [getting started with audio](https://github.com/atuline/WLED/wiki/First-Time-Setup#sound)
|
||||||
|
* [Sound settings](https://github.com/atuline/WLED/wiki/Sound-Settings) - similar to options on the usemod settings page in WLED.
|
||||||
|
* [Digital Audio](https://github.com/atuline/WLED/wiki/Digital-Microphone-Hookup)
|
||||||
|
* [Analog Audio](https://github.com/atuline/WLED/wiki/Analog-Audio-Input-Options)
|
||||||
|
* [UDP Sound sync](https://github.com/atuline/WLED/wiki/UDP-Sound-Sync)
|
||||||
|
|
||||||
|
|
||||||
|
## Supported MCUs
|
||||||
|
This audioreactive usermod works best on "classic ESP32" (dual core), and on ESP32-S3 which also has dual core and hardware floating point support.
|
||||||
|
|
||||||
|
It will compile succesfully for ESP32-S2 and ESP32-C3, however might not work well, as other WLED functions will become slow. Audio processing requires a lot of computing power, which can be problematic on smaller MCUs like -S2 and -C3.
|
||||||
|
|
||||||
|
Analog audio is only possible on "classic" ESP32, but not on other MCUs like ESP32-S3.
|
||||||
|
|
||||||
|
Currently ESP8266 is not supported, due to low speed and small RAM of this chip.
|
||||||
|
There are however plans to create a lightweight audioreactive for the 8266, with reduced features.
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
Add `-D USERMOD_AUDIOREACTIVE` to your PlatformIO environment as well as `arduinoFFT` to your `lib_deps`.
|
### using customised _arduinoFFT_ library for use with this usermod
|
||||||
|
Add `-D USERMOD_AUDIOREACTIVE` to your PlatformIO environment `build_flags`, as well as `https://github.com/blazoncek/arduinoFFT.git` to your `lib_deps`.
|
||||||
If you are not using PlatformIO (which you should) try adding `#define USERMOD_AUDIOREACTIVE` to *my_config.h* and make sure you have _arduinoFFT_ library downloaded and installed.
|
If you are not using PlatformIO (which you should) try adding `#define USERMOD_AUDIOREACTIVE` to *my_config.h* and make sure you have _arduinoFFT_ library downloaded and installed.
|
||||||
|
|
||||||
Customised _arduinoFFT_ library for use with this usermod can be found at https://github.com/blazoncek/arduinoFFT.git
|
Customised _arduinoFFT_ library for use with this usermod can be found at https://github.com/blazoncek/arduinoFFT.git
|
||||||
|
|
||||||
|
### using latest (develop) _arduinoFFT_ library
|
||||||
|
Alternatively, you can use the latest arduinoFFT development version.
|
||||||
|
ArduinoFFT `develop` library is slightly more accurate, and slighly faster than our customised library, however also needs additional 2kB RAM.
|
||||||
|
|
||||||
|
* `build_flags` = `-D USERMOD_AUDIOREACTIVE` `-D UM_AUDIOREACTIVE_USE_NEW_FFT`
|
||||||
|
* `lib_deps`= `https://github.com/kosme/arduinoFFT#develop @ 1.9.2`
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
All parameters are runtime configurable though some may require hard boot after change (I2S microphone or selected GPIOs).
|
All parameters are runtime configurable though some may require hard boot after change (I2S microphone or selected GPIOs).
|
||||||
|
|
||||||
If you want to define default GPIOs during compile time use the following (default values in parentheses):
|
If you want to define default GPIOs during compile time use the following addtional build_flags (default values in parentheses):
|
||||||
|
|
||||||
- `DMTYPE=x` : defines digital microphone type: 0=analog, 1=generic I2S, 2=ES7243 I2S, 3=SPH0645 I2S, 4=generic I2S with master clock, 5=PDM I2S
|
- `-D SR_DMTYPE=x` : defines digital microphone type: 0=analog, 1=generic I2S (default), 2=ES7243 I2S, 3=SPH0645 I2S, 4=generic I2S with master clock, 5=PDM I2S
|
||||||
- `AUDIOPIN=x` : GPIO for analog microphone/AUX-in (36)
|
- `-D AUDIOPIN=x` : GPIO for analog microphone/AUX-in (36)
|
||||||
- `I2S_SDPIN=x` : GPIO for SD pin on digital mcrophone (32)
|
- `-D I2S_SDPIN=x` : GPIO for SD pin on digital microphone (32)
|
||||||
- `I2S_WSPIN=x` : GPIO for WS pin on digital mcrophone (15)
|
- `-D I2S_WSPIN=x` : GPIO for WS pin on digital microphone (15)
|
||||||
- `I2S_CKPIN=x` : GPIO for SCK pin on digital mcrophone (14)
|
- `-D I2S_CKPIN=x` : GPIO for SCK pin on digital microphone (14)
|
||||||
- `ES7243_SDAPIN` : GPIO for I2C SDA pin on ES7243 microphone (-1)
|
- `-D MCLK_PIN=x` : GPIO for master clock pin on digital Line-In boards (-1)
|
||||||
- `ES7243_SCLPIN` : GPIO for I2C SCL pin on ES7243 microphone (-1)
|
- `-D ES7243_SDAPIN` : GPIO for I2C SDA pin on ES7243 microphone (-1)
|
||||||
- `MCLK_PIN=x` : GPIO for master clock pin on digital mcrophone (-1)
|
- `-D ES7243_SCLPIN` : GPIO for I2C SCL pin on ES7243 microphone (-1)
|
||||||
|
|
||||||
**NOTE** Due to the fact that usermod uses I2S peripherial for analog audio sampling, use of analog *buttons* (i.e. potentiometers) is disabled while running this usermod with analog microphone.
|
**NOTE** Due to the fact that usermod uses I2S peripherial for analog audio sampling, use of analog *buttons* (i.e. potentiometers) is disabled while running this usermod with analog microphone.
|
||||||
|
|
||||||
|
### Advanced Compile-Time Options
|
||||||
|
You can use the following additional flags in your `build_flags`
|
||||||
|
* `-D SR_SQUELCH=x` : Default "squelch" setting (10)
|
||||||
|
* `-D SR_GAIN=x` : Default "gain" setting (60)
|
||||||
|
* `-D I2S_USE_RIGHT_CHANNEL`: Use RIGHT instead of LEFT channel (not recommended unless you strictly need this).
|
||||||
|
* `-D I2S_USE_16BIT_SAMPLES`: Use 16bit instead of 32bit for internal sample buffers. Reduces sampling quality, but frees some RAM ressources (not recommended unless you absolutely need this).
|
||||||
|
* `-D I2S_GRAB_ADC1_COMPLETELY`: Experimental: continously sample analog ADC microphone. Only effective on ESP32. WARNING this _will_ cause conflicts(lock-up) with any analogRead() call.
|
||||||
|
* `-D MIC_LOGGER` : (debugging) Logs samples from the microphone to serial USB. Use with serial plotter (Arduino IDE)
|
||||||
|
* `-D SR_DEBUG` : (debugging) Additional error diagnostics and debug info on serial USB.
|
||||||
|
|
||||||
## Release notes
|
## Release notes
|
||||||
|
|
||||||
2022-06 Ported from [soundreactive](https://github.com/atuline/WLED) by @blazoncek (AKA Blaz Kristan)
|
* 2022-06 Ported from [soundreactive WLED](https://github.com/atuline/WLED) - by @blazoncek (AKA Blaz Kristan) and the [SR-WLED team](https://github.com/atuline/WLED/wiki#sound-reactive-wled-fork-team).
|
||||||
|
* 2022-11 Updated to align with "[MoonModules/WLED](https://amg.wled.me)" audioreactive usermod - by @softhack007 (AKA Frank Möhle).
|
||||||
|
Loading…
Reference in New Issue
Block a user