Merge branch 'MoonModules:mdev' into downsample4x
This commit is contained in:
@@ -21,13 +21,18 @@
|
||||
* ....
|
||||
*/
|
||||
|
||||
#define FFT_PREFER_EXACT_PEAKS // use different FFT wndowing -> results in "sharper" peaks and less "leaking" into other frequencies
|
||||
#define FFT_PREFER_EXACT_PEAKS // use different FFT windowing -> results in "sharper" peaks and less "leaking" into other frequencies
|
||||
//#define SR_STATS
|
||||
|
||||
#if !defined(FFTTASK_PRIORITY)
|
||||
#if defined(WLEDMM_FASTPATH) && !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && defined(ARDUINO_ARCH_ESP32)
|
||||
// FASTPATH: use higher priority, to avoid that webserver (ws, json, etc) delays sample processing
|
||||
//#define FFTTASK_PRIORITY 3 // competing with async_tcp
|
||||
#define FFTTASK_PRIORITY 4 // above async_tcp
|
||||
#else
|
||||
#define FFTTASK_PRIORITY 1 // standard: looptask prio
|
||||
//#define FFTTASK_PRIORITY 2 // above looptask, below asyc_tcp
|
||||
//#define FFTTASK_PRIORITY 4 // above asyc_tcp
|
||||
//#define FFTTASK_PRIORITY 2 // above looptask, below async_tcp
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
@@ -86,8 +91,27 @@
|
||||
#define PLOT_FLUSH()
|
||||
#endif
|
||||
|
||||
// sanity checks
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
// we need more space in for oappend() stack buffer -> SETTINGS_STACK_BUF_SIZE and CONFIG_ASYNC_TCP_TASK_STACK_SIZE
|
||||
#if SETTINGS_STACK_BUF_SIZE < 3904 // 3904 is required for WLEDMM-0.14.0-b28
|
||||
#warning please increase SETTINGS_STACK_BUF_SIZE >= 3904
|
||||
#endif
|
||||
#if (CONFIG_ASYNC_TCP_TASK_STACK_SIZE - SETTINGS_STACK_BUF_SIZE) < 4352 // at least 4096+256 words of free task stack is needed by async_tcp alone
|
||||
#error remaining async_tcp stack will be too low - please increase CONFIG_ASYNC_TCP_TASK_STACK_SIZE
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// audiosync constants
|
||||
#define AUDIOSYNC_NONE 0x00 // UDP sound sync off
|
||||
#define AUDIOSYNC_SEND 0x01 // UDP sound sync - send mode
|
||||
#define AUDIOSYNC_REC 0x02 // UDP sound sync - receiver mode
|
||||
#define AUDIOSYNC_REC_PLUS 0x06 // UDP sound sync - receiver + local mode (uses local input if no receiving udp sound)
|
||||
#define AUDIOSYNC_IDLE_MS 2500 // timeout for "receiver idle" (milliseconds)
|
||||
|
||||
static volatile bool disableSoundProcessing = false; // if true, sound processing (FFT, filters, AGC) will be suspended. "volatile" as its shared between tasks.
|
||||
static uint8_t audioSyncEnabled = 0; // bit field: bit 0 - send, bit 1 - receive (config value)
|
||||
static uint8_t audioSyncEnabled = AUDIOSYNC_NONE; // bit field: bit 0 - send, bit 1 - receive, bit 2 - use local if not receiving
|
||||
static bool audioSyncSequence = true; // if true, the receiver will drop out-of-sequence packets
|
||||
static bool udpSyncConnected = false; // UDP connection status -> true if connected to multicast group
|
||||
|
||||
#define NUM_GEQ_CHANNELS 16 // number of frequency channels. Don't change !!
|
||||
@@ -109,9 +133,13 @@ static float volumeSmth = 0.0f; // either sampleAvg or sampleAgc
|
||||
static float FFT_MajorPeak = 1.0f; // FFT: strongest (peak) frequency
|
||||
static float FFT_Magnitude = 0.0f; // FFT: volume (magnitude) of peak frequency
|
||||
static bool samplePeak = false; // Boolean flag for peak - used in effects. Responding routine may reset this flag. Auto-reset after strip.getMinShowDelay()
|
||||
static bool udpSamplePeak = false; // Boolean flag for peak. Set at the same tiem as samplePeak, but reset by transmitAudioData
|
||||
static bool udpSamplePeak = false; // Boolean flag for peak. Set at the same time as samplePeak, but reset by transmitAudioData
|
||||
static unsigned long timeOfPeak = 0; // time of last sample peak detection.
|
||||
static uint8_t fftResult[NUM_GEQ_CHANNELS]= {0};// Our calculated freq. channel result table to be used by effects
|
||||
volatile bool haveNewFFTResult = false; // flag to directly inform UDP sound sender when new FFT results are available (to reduce latency). Flag is reset at next UDP send
|
||||
|
||||
static uint8_t fftResult[NUM_GEQ_CHANNELS]= {0}; // Our calculated freq. channel result table to be used by effects
|
||||
static float fftCalc[NUM_GEQ_CHANNELS] = {0.0f}; // Try and normalize fftBin values to a max of 4096, so that 4096/16 = 256. (also used by dynamics limiter)
|
||||
static float fftAvg[NUM_GEQ_CHANNELS] = {0.0f}; // Calculated frequency channel results, with smoothing (used if dynamics limiter is ON)
|
||||
|
||||
// TODO: probably best not used by receive nodes
|
||||
static float agcSensitivity = 128; // AGC sensitivity estimation, based on agc gain (multAgc). calculated by getSensitivity(). range 0..255
|
||||
@@ -123,7 +151,7 @@ static uint16_t decayTime = 300; // int: decay time in milliseconds
|
||||
|
||||
// peak detection
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
static void detectSamplePeak(void); // peak detection function (needs scaled FFT reasults in vReal[]) - no used for 8266 receive-only mode
|
||||
static void detectSamplePeak(void); // peak detection function (needs scaled FFT results in vReal[]) - no used for 8266 receive-only mode
|
||||
#endif
|
||||
static void autoResetPeak(void); // peak auto-reset function
|
||||
static uint8_t maxVol = 31; // (was 10) Reasonable value for constant volume for 'peak detector', as it won't always trigger (deprecated)
|
||||
@@ -150,7 +178,7 @@ static uint8_t inputLevel = 128; // UI slider value
|
||||
#endif
|
||||
|
||||
// user settable options for FFTResult scaling
|
||||
static uint8_t FFTScalingMode = 3; // 0 none; 1 optimized logarithmic; 2 optimized linear; 3 optimized sqare root
|
||||
static uint8_t FFTScalingMode = 3; // 0 none; 1 optimized logarithmic; 2 optimized linear; 3 optimized square root
|
||||
#ifndef SR_FREQ_PROF
|
||||
static uint8_t pinkIndex = 0; // 0: default; 1: line-in; 2: IMNP441
|
||||
#else
|
||||
@@ -220,7 +248,7 @@ static void postProcessFFTResults(bool noiseGateOpen, int numberOfChannels); //
|
||||
static TaskHandle_t FFT_Task = nullptr;
|
||||
|
||||
// Table of multiplication factors so that we can even out the frequency response.
|
||||
#define MAX_PINK 10 // 0 = standard, 1= line-in (pink moise only), 2..4 = IMNP441, 5..6 = ICS-43434, ,7=SPM1423, 8..9 = userdef, 10= flat (no pink noise adjustment)
|
||||
#define MAX_PINK 10 // 0 = standard, 1= line-in (pink noise only), 2..4 = IMNP441, 5..6 = ICS-43434, ,7=SPM1423, 8..9 = userdef, 10= flat (no pink noise adjustment)
|
||||
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
|
||||
@@ -244,7 +272,7 @@ static const float fftResultPink[MAX_PINK+1][NUM_GEQ_CHANNELS] = {
|
||||
/* how to make your own profile:
|
||||
* ===============================
|
||||
* preparation: make sure your microphone has direct line-of-sigh with the speaker, 1-2meter distance is best
|
||||
* Prepare your HiFi equipment: disable all "Sound enhancements" - like Loudness, Equalizer, Bass Boost. Bass/Trebble controls set to middle.
|
||||
* Prepare your HiFi equipment: disable all "Sound enhancements" - like Loudness, Equalizer, Bass Boost. Bass/Treble controls set to middle.
|
||||
* Your HiFi equipment should receive its audio input from Line-In, SPDIF, HDMI, or another "undistorted" connection (like CDROM).
|
||||
* Try not to use Bluetooth or MP3 when playing the "pink noise" audio. BT-audio and MP3 both perform "acoustic adjustments" that we don't want now.
|
||||
|
||||
@@ -258,7 +286,7 @@ static const float fftResultPink[MAX_PINK+1][NUM_GEQ_CHANNELS] = {
|
||||
|
||||
* Your own profile:
|
||||
* - Target for each LED bar is 50% to 75% of the max height --> 8(high) x 16(wide) panel means target = 5. 32 x 16 means target = 22.
|
||||
* - From left to right - count the LEDs in each of the 16 frequency colums (that's why you need the photo). This is the barheight for each channel.
|
||||
* - From left to right - count the LEDs in each of the 16 frequency columns (that's why you need the photo). This is the barheight for each channel.
|
||||
* - math time! Find the multiplier that will bring each bar to to target.
|
||||
* * in case of square root scale: multiplier = (target * target) / (barheight * barheight)
|
||||
* * in case of linear scale: multiplier = target / barheight
|
||||
@@ -280,8 +308,6 @@ static float sampleTime = 0; // avg (blocked) time for reading I2S sample
|
||||
|
||||
// FFT Task variables (filtering and post-processing)
|
||||
static float lastFftCalc[NUM_GEQ_CHANNELS] = {0.0f}; // backup of last FFT channels (before postprocessing)
|
||||
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)
|
||||
|
||||
#if !defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
// audio source parameters and constant
|
||||
@@ -360,7 +386,7 @@ static float mapf(float x, float in_min, float in_max, float out_min, float out_
|
||||
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
|
||||
}
|
||||
|
||||
// compute average of several FFT resut bins
|
||||
// compute average of several FFT result bins
|
||||
// linear average
|
||||
static float fftAddAvgLin(int from, int to) {
|
||||
float result = 0.0f;
|
||||
@@ -430,7 +456,7 @@ void FFTcode(void * parameter)
|
||||
for(uint_fast16_t binInd = 0; binInd < samplesFFT; binInd++) {
|
||||
float binFreq = binInd * binWidth + binWidth/2.0f;
|
||||
if (binFreq > (SAMPLE_RATE * 0.42f))
|
||||
binFreq = (SAMPLE_RATE * 0.42f) - 0.25 * (binFreq - (SAMPLE_RATE * 0.42f)); // supress noise and aliasing
|
||||
binFreq = (SAMPLE_RATE * 0.42f) - 0.25 * (binFreq - (SAMPLE_RATE * 0.42f)); // suppress noise and aliasing
|
||||
pinkFactors[binInd] = sqrtf(binFreq) / pinkcenter;
|
||||
}
|
||||
pinkFactors[0] *= 0.5; // suppress 0-42hz bin
|
||||
@@ -442,7 +468,7 @@ void FFTcode(void * parameter)
|
||||
// taskYIELD(), yield(), vTaskDelay() and esp_task_wdt_feed() didn't seem to work.
|
||||
|
||||
// Don't run FFT computing code if we're in Receive mode or in realtime mode
|
||||
if (disableSoundProcessing || (audioSyncEnabled & 0x02)) {
|
||||
if (disableSoundProcessing || (audioSyncEnabled == AUDIOSYNC_REC)) {
|
||||
isFirstRun = false;
|
||||
vTaskDelayUntil( &xLastWakeTime, xFrequency); // release CPU, and let I2S fill its buffers
|
||||
continue;
|
||||
@@ -482,7 +508,7 @@ void FFTcode(void * parameter)
|
||||
#endif
|
||||
|
||||
xLastWakeTime = xTaskGetTickCount(); // update "last unblocked time" for vTaskDelay
|
||||
isFirstRun = !isFirstRun; // toggle throtte
|
||||
isFirstRun = !isFirstRun; // toggle throttle
|
||||
|
||||
#ifdef MIC_LOGGER
|
||||
float datMin = 0.0f;
|
||||
@@ -499,6 +525,11 @@ void FFTcode(void * parameter)
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(WLEDMM_FASTPATH) && !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && defined(ARDUINO_ARCH_ESP32)
|
||||
// experimental - be nice to LED update task (trying to avoid flickering) - dual core only
|
||||
if (strip.isServicing()) delay(2);
|
||||
#endif
|
||||
|
||||
// band pass filter - can reduce noise floor by a factor of 50
|
||||
// downside: frequencies below 100Hz will be ignored
|
||||
if ((useInputFilter > 0) && (useInputFilter < 99)) {
|
||||
@@ -619,7 +650,7 @@ void FFTcode(void * parameter)
|
||||
*
|
||||
* Andrew's updated mapping of 256 bins down to the 16 result bins with Sample Freq = 10240, samplesFFT = 512 and some overlap.
|
||||
* Based on testing, the lowest/Start frequency is 60 Hz (with bin 3) and a highest/End frequency of 5120 Hz in bin 255.
|
||||
* Now, Take the 60Hz and multiply by 1.320367784 to get the next frequency and so on until the end. Then detetermine the bins.
|
||||
* Now, Take the 60Hz and multiply by 1.320367784 to get the next frequency and so on until the end. Then determine the bins.
|
||||
* End frequency = Start frequency * multiplier ^ 16
|
||||
* Multiplier = (End frequency/ Start frequency) ^ 1/16
|
||||
* Multiplier = 1.320367784
|
||||
@@ -673,7 +704,7 @@ void FFTcode(void * parameter)
|
||||
fftCalc[13] = fftAddAvg(86,103); // 18 3704 - 4479 high mid
|
||||
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
|
||||
else if (freqDist == 1) { //WLEDMM: Rightshift: note ewowi: frequencies in comments are not correct
|
||||
if (useInputFilter==1) {
|
||||
// skip frequencies below 100hz
|
||||
fftCalc[ 0] = 0.8f * fftAddAvg(1,1);
|
||||
@@ -717,7 +748,7 @@ void FFTcode(void * parameter)
|
||||
memcpy(fftCalc, lastFftCalc, sizeof(fftCalc)); // restore last "good" channels
|
||||
}
|
||||
|
||||
// post-processing of frequency channels (pink noise adjustment, AGC, smooting, scaling)
|
||||
// post-processing of frequency channels (pink noise adjustment, AGC, smoothing, scaling)
|
||||
if (pinkIndex > MAX_PINK) pinkIndex = MAX_PINK;
|
||||
//postProcessFFTResults((fabsf(sampleAvg) > 0.25f)? true : false , NUM_GEQ_CHANNELS);
|
||||
postProcessFFTResults((fabsf(volumeSmth)>0.25f)? true : false , NUM_GEQ_CHANNELS); // this function modifies fftCalc, fftAvg and fftResult
|
||||
@@ -736,6 +767,9 @@ void FFTcode(void * parameter)
|
||||
autoResetPeak();
|
||||
detectSamplePeak();
|
||||
|
||||
// we have new results - notify UDP sound send
|
||||
haveNewFFTResult = true;
|
||||
|
||||
#if !defined(I2S_GRAB_ADC1_COMPLETELY)
|
||||
if ((audioSource == nullptr) || (audioSource->getType() != AudioSource::Type_I2SAdc)) // the "delay trick" does not help for analog ADC
|
||||
#endif
|
||||
@@ -775,7 +809,7 @@ static void runMicFilter(uint16_t numSamples, float *sampleBuffer) // p
|
||||
// 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
|
||||
else highFilteredSample = beta1*sampleBuffer[i] + beta2*last_vals[0] + beta2*last_vals[1]; // special handling for last sample in array
|
||||
last_vals[1] = last_vals[0];
|
||||
last_vals[0] = sampleBuffer[i];
|
||||
sampleBuffer[i] = highFilteredSample;
|
||||
@@ -904,7 +938,7 @@ static void autoResetPeak(void) {
|
||||
uint16_t MinShowDelay = MAX(50, strip.getMinShowDelay()); // Fixes private class variable compiler error. Unsure if this is the correct way of fixing the root problem. -THATDONFC
|
||||
if (millis() - timeOfPeak > MinShowDelay) { // Auto-reset of samplePeak after a complete frame has passed.
|
||||
samplePeak = false;
|
||||
if (audioSyncEnabled == 0) udpSamplePeak = false; // this is normally reset by transmitAudioData
|
||||
if (audioSyncEnabled == AUDIOSYNC_NONE) udpSamplePeak = false; // this is normally reset by transmitAudioData
|
||||
}
|
||||
}
|
||||
|
||||
@@ -966,7 +1000,7 @@ class AudioReactive : public Usermod {
|
||||
float sampleRaw; // 04 Bytes - either "sampleRaw" or "rawSampleAgc" depending on soundAgc setting
|
||||
float sampleSmth; // 04 Bytes - either "sampleAvg" or "sampleAgc" depending on soundAgc setting
|
||||
uint8_t samplePeak; // 01 Bytes - 0 no peak; >=1 peak detected. In future, this will also provide peak Magnitude
|
||||
uint8_t reserved1; // 01 Bytes - for future extensions - not used yet
|
||||
uint8_t frameCounter; // 01 Bytes - track duplicate/out of order packets
|
||||
uint8_t fftResult[16]; // 16 Bytes
|
||||
float FFT_Magnitude; // 04 Bytes
|
||||
float FFT_MajorPeak; // 04 Bytes
|
||||
@@ -998,7 +1032,11 @@ class AudioReactive : public Usermod {
|
||||
// variables for UDP sound sync
|
||||
WiFiUDP fftUdp; // UDP object for sound sync (from WiFi UDP, not Async UDP!)
|
||||
unsigned long lastTime = 0; // last time of running UDP Microphone Sync
|
||||
#if defined(WLEDMM_FASTPATH)
|
||||
const uint16_t delayMs = 5; // I don't want to sample too often and overload WLED
|
||||
#else
|
||||
const uint16_t delayMs = 10; // I don't want to sample too often and overload WLED
|
||||
#endif
|
||||
uint16_t audioSyncPort= 11988;// default port for UDP sound sync
|
||||
|
||||
bool updateIsRunning = false; // true during OTA.
|
||||
@@ -1010,7 +1048,7 @@ class AudioReactive : public Usermod {
|
||||
|
||||
// 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
|
||||
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 controller.
|
||||
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 sampleReal = 0.0f; // "sampleRaw" as float, to provide bits that are lost otherwise (before amplification by sampleGain or inputLevel). Needed for AGC.
|
||||
@@ -1048,7 +1086,7 @@ class AudioReactive : public Usermod {
|
||||
////////////////////
|
||||
void logAudio()
|
||||
{
|
||||
if (disableSoundProcessing && (!udpSyncConnected || ((audioSyncEnabled & 0x02) == 0))) return; // no audio availeable
|
||||
if (disableSoundProcessing && (!udpSyncConnected || ((audioSyncEnabled & AUDIOSYNC_REC) == 0))) return; // no audio available
|
||||
#ifdef MIC_LOGGER
|
||||
// Debugging functions for audio input and sound processing. Comment out the values you want to see
|
||||
PLOT_PRINT("volumeSmth:"); PLOT_PRINT(volumeSmth + 256.0f); PLOT_PRINT("\t"); // +256 to move above other lines
|
||||
@@ -1141,13 +1179,13 @@ class AudioReactive : public Usermod {
|
||||
* 2. we use two setpoints, one at ~60%, and one at ~80% of the maximum signal
|
||||
* 3. the amplification depends on signal level:
|
||||
* a) normal zone - very slow adjustment
|
||||
* b) emergency zome (<10% or >90%) - very fast adjustment
|
||||
* b) emergency zone (<10% or >90%) - very fast adjustment
|
||||
*/
|
||||
void agcAvg(unsigned long the_time)
|
||||
{
|
||||
const int AGC_preset = (soundAgc > 0)? (soundAgc-1): 0; // make sure the _compiler_ knows this value will not change while we are inside the function
|
||||
|
||||
float lastMultAgc = multAgc; // last muliplier used
|
||||
float lastMultAgc = multAgc; // last multiplier used
|
||||
float multAgcTemp = multAgc; // new multiplier
|
||||
float tmpAgc = sampleReal * multAgc; // what-if amplified signal
|
||||
|
||||
@@ -1187,13 +1225,13 @@ class AudioReactive : public Usermod {
|
||||
|
||||
if (((multAgcTemp > 0.085f) && (multAgcTemp < 6.5f)) //integrator anti-windup by clamping
|
||||
&& (multAgc*sampleMax < agcZoneStop[AGC_preset])) //integrator ceiling (>140% of max)
|
||||
control_integrated += control_error * 0.002 * 0.25; // 2ms = intgration time; 0.25 for damping
|
||||
control_integrated += control_error * 0.002 * 0.25; // 2ms = integration time; 0.25 for damping
|
||||
else
|
||||
control_integrated *= 0.9; // spin down that beasty integrator
|
||||
control_integrated *= 0.9; // spin down that integrator beast
|
||||
|
||||
// apply PI Control
|
||||
tmpAgc = sampleReal * lastMultAgc; // check "zone" of the signal using previous gain
|
||||
if ((tmpAgc > agcZoneHigh[AGC_preset]) || (tmpAgc < soundSquelch + agcZoneLow[AGC_preset])) { // upper/lower emergy zone
|
||||
if ((tmpAgc > agcZoneHigh[AGC_preset]) || (tmpAgc < soundSquelch + agcZoneLow[AGC_preset])) { // upper/lower emergency zone
|
||||
multAgcTemp = lastMultAgc + agcFollowFast[AGC_preset] * agcControlKp[AGC_preset] * control_error;
|
||||
multAgcTemp += agcFollowFast[AGC_preset] * agcControlKi[AGC_preset] * control_integrated;
|
||||
} else { // "normal zone"
|
||||
@@ -1201,7 +1239,7 @@ class AudioReactive : public Usermod {
|
||||
multAgcTemp += agcFollowSlow[AGC_preset] * agcControlKi[AGC_preset] * control_integrated;
|
||||
}
|
||||
|
||||
// limit amplification again - PI controler sometimes "overshoots"
|
||||
// limit amplification again - PI controller sometimes "overshoots"
|
||||
//multAgcTemp = constrain(multAgcTemp, 0.015625f, 32.0f); // 1/64 < multAgcTemp < 32
|
||||
if (multAgcTemp > 32.0f) multAgcTemp = 32.0f;
|
||||
if (multAgcTemp < 1.0f/64.0f) multAgcTemp = 1.0f/64.0f;
|
||||
@@ -1231,7 +1269,7 @@ class AudioReactive : public Usermod {
|
||||
void getSample()
|
||||
{
|
||||
float sampleAdj; // Gain adjusted sample value
|
||||
float tmpSample; // An interim sample variable used for calculatioins.
|
||||
float tmpSample; // An interim sample variable used for calculations.
|
||||
#ifdef WLEDMM_FASTPATH
|
||||
constexpr float weighting = 0.35f; // slightly reduced filter strength, to reduce audio latency
|
||||
constexpr float weighting2 = 0.25f;
|
||||
@@ -1407,7 +1445,7 @@ class AudioReactive : public Usermod {
|
||||
if (limiterOn == false) return;
|
||||
|
||||
long delta_time = millis() - last_time;
|
||||
delta_time = constrain(delta_time , 1, 1000); // below 1ms -> 1ms; above 1sec -> sily lil hick-up
|
||||
delta_time = constrain(delta_time , 1, 1000); // below 1ms -> 1ms; above 1sec -> silly lil hick-up
|
||||
float deltaSample = volumeSmth - last_volumeSmth;
|
||||
|
||||
if (attackTime > 0) { // user has defined attack time > 0
|
||||
@@ -1425,6 +1463,39 @@ class AudioReactive : public Usermod {
|
||||
last_time = millis();
|
||||
}
|
||||
|
||||
// MM experimental: limiter to smooth GEQ samples (only for UDP sound receiver mode)
|
||||
// target value (if gotNewSample) : fftCalc
|
||||
// last filtered value: fftAvg
|
||||
void limitGEQDynamics(bool gotNewSample) {
|
||||
constexpr float bigChange = 202; // just a representative number - a large, expected sample value
|
||||
constexpr float smooth = 0.8f; // a bit of filtering
|
||||
static unsigned long last_time = 0;
|
||||
|
||||
if (limiterOn == false) return;
|
||||
|
||||
if (gotNewSample) { // take new FFT samples as target values
|
||||
for(unsigned i=0; i < NUM_GEQ_CHANNELS; i++) {
|
||||
fftCalc[i] = fftResult[i];
|
||||
fftResult[i] = fftAvg[i];
|
||||
}
|
||||
}
|
||||
|
||||
long delta_time = millis() - last_time;
|
||||
delta_time = constrain(delta_time , 1, 1000); // below 1ms -> 1ms; above 1sec -> silly lil hick-up
|
||||
float maxAttack = (attackTime <= 0) ? 255.0f : (bigChange * float(delta_time) / float(attackTime));
|
||||
float maxDecay = (decayTime <= 0) ? -255.0f : (-bigChange * float(delta_time) / float(decayTime));
|
||||
|
||||
for(unsigned i=0; i < NUM_GEQ_CHANNELS; i++) {
|
||||
float deltaSample = fftCalc[i] - fftAvg[i];
|
||||
if (deltaSample > maxAttack) deltaSample = maxAttack;
|
||||
if (deltaSample < maxDecay) deltaSample = maxDecay;
|
||||
deltaSample = deltaSample * smooth;
|
||||
fftAvg[i] = fmaxf(0.0f, fminf(255.0f, fftAvg[i] + deltaSample));
|
||||
fftResult[i] = fftAvg[i];
|
||||
}
|
||||
last_time = millis();
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
// UDP Sound Sync //
|
||||
//////////////////////
|
||||
@@ -1435,7 +1506,7 @@ class AudioReactive : public Usermod {
|
||||
// necessary as we also want to transmit in "AP Mode", but the standard "connected()" callback only reacts on STA connection
|
||||
static unsigned long last_connection_attempt = 0;
|
||||
|
||||
if ((audioSyncPort <= 0) || ((audioSyncEnabled & 0x03) == 0)) return; // Sound Sync not enabled
|
||||
if ((audioSyncPort <= 0) || (audioSyncEnabled == AUDIOSYNC_NONE)) return; // Sound Sync not enabled
|
||||
if (!(apActive || WLED_CONNECTED || interfacesInited)) {
|
||||
if (udpSyncConnected) {
|
||||
udpSyncConnected = false;
|
||||
@@ -1443,11 +1514,11 @@ class AudioReactive : public Usermod {
|
||||
receivedFormat = 0;
|
||||
DEBUGSR_PRINTLN(F("AR connectUDPSoundSync(): connection lost, UDP closed."));
|
||||
}
|
||||
return; // neither AP nor other connections availeable
|
||||
return; // neither AP nor other connections available
|
||||
}
|
||||
if (udpSyncConnected) return; // already connected
|
||||
if (millis() - last_connection_attempt < 15000) return; // only try once in 15 seconds
|
||||
if (updateIsRunning) return; // don't reconect during OTA
|
||||
if (updateIsRunning) return; // don't reconnect during OTA
|
||||
|
||||
// if we arrive here, we need a UDP connection but don't have one
|
||||
last_connection_attempt = millis();
|
||||
@@ -1457,16 +1528,19 @@ class AudioReactive : public Usermod {
|
||||
void transmitAudioData()
|
||||
{
|
||||
if (!udpSyncConnected) return;
|
||||
static uint8_t frameCounter = 0;
|
||||
//DEBUGSR_PRINTLN("Transmitting UDP Mic Packet");
|
||||
|
||||
audioSyncPacket transmitData;
|
||||
memset(reinterpret_cast<void *>(&transmitData), 0, sizeof(transmitData)); // make sure that the packet - including "invisible" padding bytes added by the compiler - is fully initialized
|
||||
|
||||
strncpy_P(transmitData.header, PSTR(UDP_SYNC_HEADER), 6);
|
||||
// transmit samples that were not modified by limitSampleDynamics()
|
||||
transmitData.sampleRaw = (soundAgc) ? rawSampleAgc: sampleRaw;
|
||||
transmitData.sampleSmth = (soundAgc) ? sampleAgc : sampleAvg;
|
||||
transmitData.samplePeak = udpSamplePeak ? 1:0;
|
||||
udpSamplePeak = false; // Reset udpSamplePeak after we've transmitted it
|
||||
transmitData.reserved1 = 0;
|
||||
transmitData.frameCounter = frameCounter;
|
||||
|
||||
for (int i = 0; i < NUM_GEQ_CHANNELS; i++) {
|
||||
transmitData.fftResult[i] = (uint8_t)constrain(fftResult[i], 0, 254);
|
||||
@@ -1479,7 +1553,8 @@ class AudioReactive : public Usermod {
|
||||
fftUdp.write(reinterpret_cast<uint8_t *>(&transmitData), sizeof(transmitData));
|
||||
fftUdp.endPacket();
|
||||
}
|
||||
return;
|
||||
|
||||
frameCounter++;
|
||||
} // transmitAudioData()
|
||||
#endif
|
||||
static bool isValidUdpSyncVersion(const char *header) {
|
||||
@@ -1489,8 +1564,29 @@ class AudioReactive : public Usermod {
|
||||
return strncmp_P(header, UDP_SYNC_HEADER_v1, 6) == 0;
|
||||
}
|
||||
|
||||
void decodeAudioData(int packetSize, uint8_t *fftBuff) {
|
||||
bool decodeAudioData(int packetSize, uint8_t *fftBuff) {
|
||||
if((0 == packetSize) || (nullptr == fftBuff)) return false; // sanity check
|
||||
audioSyncPacket *receivedPacket = reinterpret_cast<audioSyncPacket*>(fftBuff);
|
||||
|
||||
// validate sequence, discard out-of-sequence packets
|
||||
static uint8_t lastFrameCounter = 0;
|
||||
// add info for UI
|
||||
if ((receivedPacket->frameCounter > 0) && (lastFrameCounter > 0)) receivedFormat = 3; // v2+
|
||||
else receivedFormat = 2; // v2
|
||||
// check sequence
|
||||
bool sequenceOK = false;
|
||||
if(receivedPacket->frameCounter > lastFrameCounter) sequenceOK = true; // sequence OK
|
||||
if((lastFrameCounter < 12) && (receivedPacket->frameCounter > 248)) sequenceOK = false; // prevent sequence "roll-back" due to late packets (1->254)
|
||||
if((lastFrameCounter > 248) && (receivedPacket->frameCounter < 12)) sequenceOK = true; // handle roll-over (255 -> 0)
|
||||
if(audioSyncSequence == false) sequenceOK = true; // sequence checking disabled by user
|
||||
if((sequenceOK == false) && (receivedPacket->frameCounter != 0)) { // always accept "0" - its the legacy value
|
||||
DEBUGSR_PRINTF("Skipping audio frame out of order or duplicated - %u vs %u\n", lastFrameCounter, receivedPacket->frameCounter);
|
||||
return false; // reject out-of sequence frame
|
||||
}
|
||||
else {
|
||||
lastFrameCounter = receivedPacket->frameCounter;
|
||||
}
|
||||
|
||||
// update samples for effects
|
||||
volumeSmth = fmaxf(receivedPacket->sampleSmth, 0.0f);
|
||||
volumeRaw = fmaxf(receivedPacket->sampleRaw, 0.0f);
|
||||
@@ -1515,6 +1611,7 @@ class AudioReactive : public Usermod {
|
||||
my_magnitude = fmaxf(receivedPacket->FFT_Magnitude, 0.0f);
|
||||
FFT_Magnitude = my_magnitude;
|
||||
FFT_MajorPeak = constrain(receivedPacket->FFT_MajorPeak, 1.0f, 11025.0f); // restrict value to range expected by effects
|
||||
return true;
|
||||
}
|
||||
|
||||
void decodeAudioData_v1(int packetSize, uint8_t *fftBuff) {
|
||||
@@ -1577,16 +1674,15 @@ class AudioReactive : public Usermod {
|
||||
|
||||
// VERIFY THAT THIS IS A COMPATIBLE PACKET
|
||||
if (packetSize == sizeof(audioSyncPacket) && (isValidUdpSyncVersion((const char *)fftUdpBuffer))) {
|
||||
decodeAudioData(packetSize, fftUdpBuffer);
|
||||
//DEBUGSR_PRINTLN("Finished parsing UDP Sync Packet v2");
|
||||
haveFreshData = true;
|
||||
receivedFormat = 2;
|
||||
haveFreshData = decodeAudioData(packetSize, fftUdpBuffer);
|
||||
//DEBUGSR_PRINTLN("Finished parsing UDP Sync Packet v2");
|
||||
} else {
|
||||
if (packetSize == sizeof(audioSyncPacket_v1) && (isValidUdpSyncVersion_v1((const char *)fftUdpBuffer))) {
|
||||
decodeAudioData_v1(packetSize, fftUdpBuffer);
|
||||
receivedFormat = 1;
|
||||
//DEBUGSR_PRINTLN("Finished parsing UDP Sync Packet v1");
|
||||
haveFreshData = true;
|
||||
receivedFormat = 1;
|
||||
} else receivedFormat = 0; // unknown format
|
||||
}
|
||||
}
|
||||
@@ -1641,7 +1737,7 @@ class AudioReactive : public Usermod {
|
||||
um_data->u_type[10] = UMT_FLOAT;
|
||||
#else
|
||||
// ESP8266
|
||||
// See https://github.com/MoonModules/WLED/pull/60#issuecomment-1666972133 for explaination of these alternative sources of data
|
||||
// See https://github.com/MoonModules/WLED/pull/60#issuecomment-1666972133 for explanation of these alternative sources of data
|
||||
|
||||
um_data->u_data[6] = &maxVol; // assigned in effect function from UI element!!! (Puddlepeak, Ripplepeak, Waterfall)
|
||||
um_data->u_type[6] = UMT_BYTE;
|
||||
@@ -1739,6 +1835,23 @@ class AudioReactive : public Usermod {
|
||||
if (i2c_sda >= 0) sdaPin = -1; // -1 = use global
|
||||
if (i2c_scl >= 0) sclPin = -1;
|
||||
|
||||
if (audioSource) audioSource->initialize(i2swsPin, i2ssdPin, i2sckPin, mclkPin);
|
||||
break;
|
||||
case 7:
|
||||
#ifdef use_wm8978_mic
|
||||
DEBUGSR_PRINTLN(F("AR: WM8978 Source (Mic)"));
|
||||
#else
|
||||
DEBUGSR_PRINTLN(F("AR: WM8978 Source (Line-In)"));
|
||||
#endif
|
||||
audioSource = new WM8978Source(SAMPLE_RATE, BLOCK_SIZE, 1.0f);
|
||||
//useInputFilter = 0; // to disable low-cut software filtering and restore previous behaviour
|
||||
delay(100);
|
||||
// WLEDMM align global pins
|
||||
if ((sdaPin >= 0) && (i2c_sda < 0)) i2c_sda = sdaPin; // copy usermod prefs into globals (if globals not defined)
|
||||
if ((sclPin >= 0) && (i2c_scl < 0)) i2c_scl = sclPin;
|
||||
if (i2c_sda >= 0) sdaPin = -1; // -1 = use global
|
||||
if (i2c_scl >= 0) sclPin = -1;
|
||||
|
||||
if (audioSource) audioSource->initialize(i2swsPin, i2ssdPin, i2sckPin, mclkPin);
|
||||
break;
|
||||
|
||||
@@ -1758,7 +1871,7 @@ class AudioReactive : public Usermod {
|
||||
|
||||
if (!audioSource) enabled = false; // audio failed to initialise
|
||||
#endif
|
||||
if (enabled) onUpdateBegin(false); // create FFT task, and initailize network
|
||||
if (enabled) onUpdateBegin(false); // create FFT task, and initialize network
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
if (FFT_Task == nullptr) enabled = false; // FFT task creation failed
|
||||
@@ -1803,7 +1916,7 @@ class AudioReactive : public Usermod {
|
||||
DEBUGSR_PRINTLN(F("AR connected(): old UDP connection closed."));
|
||||
}
|
||||
|
||||
if (audioSyncPort > 0 && (audioSyncEnabled & 0x03)) {
|
||||
if ((audioSyncPort > 0) && (audioSyncEnabled > AUDIOSYNC_NONE)) {
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
udpSyncConnected = fftUdp.beginMulticast(IPAddress(239, 0, 0, 1), audioSyncPort);
|
||||
#else
|
||||
@@ -1844,7 +1957,18 @@ class AudioReactive : public Usermod {
|
||||
return;
|
||||
}
|
||||
// We cannot wait indefinitely before processing audio data
|
||||
if (strip.isUpdating() && (millis() - lastUMRun < 2)) return; // be nice, but not too nice
|
||||
if (strip.isServicing() && (millis() - lastUMRun < 2)) return; // WLEDMM isServicing() is the critical part (be nice, but not too nice)
|
||||
|
||||
// sound sync "receive or local"
|
||||
bool useNetworkAudio = false;
|
||||
if (audioSyncEnabled > AUDIOSYNC_SEND) { // we are in "receive" or "receive+local" mode
|
||||
if (udpSyncConnected && ((millis() - last_UDPTime) <= AUDIOSYNC_IDLE_MS))
|
||||
useNetworkAudio = true;
|
||||
else
|
||||
useNetworkAudio = false;
|
||||
if (audioSyncEnabled == AUDIOSYNC_REC)
|
||||
useNetworkAudio = true; // don't fall back to local audio in standard "receive mode"
|
||||
}
|
||||
|
||||
// suspend local sound processing when "real time mode" is active (E131, UDP, ADALIGHT, ARTNET)
|
||||
if ( (realtimeOverride == REALTIME_OVERRIDE_NONE) // please add other overrides here if needed
|
||||
@@ -1855,27 +1979,32 @@ class AudioReactive : public Usermod {
|
||||
||(realtimeMode == REALTIME_MODE_ARTNET) ) ) // please add other modes here if needed
|
||||
{
|
||||
#ifdef WLED_DEBUG
|
||||
if ((disableSoundProcessing == false) && (audioSyncEnabled == 0)) { // we just switched to "disabled"
|
||||
if ((disableSoundProcessing == false) && (audioSyncEnabled < AUDIOSYNC_REC)) { // we just switched to "disabled"
|
||||
DEBUG_PRINTLN("[AR userLoop] realtime mode active - audio processing suspended.");
|
||||
DEBUG_PRINTF( " RealtimeMode = %d; RealtimeOverride = %d\n", int(realtimeMode), int(realtimeOverride));
|
||||
}
|
||||
#endif
|
||||
disableSoundProcessing = true;
|
||||
useNetworkAudio = false;
|
||||
} else {
|
||||
#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_DEBUG)
|
||||
if ((disableSoundProcessing == true) && (audioSyncEnabled == 0) && audioSource->isInitialized()) { // we just switched to "enabled"
|
||||
if ((disableSoundProcessing == true) && (audioSyncEnabled < AUDIOSYNC_REC) && audioSource->isInitialized()) { // we just switched to "enabled"
|
||||
DEBUG_PRINTLN("[AR userLoop] realtime mode ended - audio processing resumed.");
|
||||
DEBUG_PRINTF( " RealtimeMode = %d; RealtimeOverride = %d\n", int(realtimeMode), int(realtimeOverride));
|
||||
}
|
||||
#endif
|
||||
if ((disableSoundProcessing == true) && (audioSyncEnabled == 0)) lastUMRun = millis(); // just left "realtime mode" - update timekeeping
|
||||
if ((disableSoundProcessing == true) && (audioSyncEnabled < AUDIOSYNC_REC)) lastUMRun = millis(); // just left "realtime mode" - update timekeeping
|
||||
disableSoundProcessing = false;
|
||||
}
|
||||
|
||||
if (audioSyncEnabled & 0x02) disableSoundProcessing = true; // make sure everything is disabled IF in audio Receive mode
|
||||
if (audioSyncEnabled & 0x01) disableSoundProcessing = false; // keep running audio IF we're in audio Transmit mode
|
||||
if (audioSyncEnabled == AUDIOSYNC_REC) disableSoundProcessing = true; // make sure everything is disabled IF in audio Receive mode
|
||||
if (audioSyncEnabled & AUDIOSYNC_SEND) disableSoundProcessing = false; // keep running audio IF we're in audio Transmit mode
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
if (!audioSource->isInitialized()) disableSoundProcessing = true; // no audio source
|
||||
if (!audioSource->isInitialized()) { // no audio source
|
||||
disableSoundProcessing = true;
|
||||
if (audioSyncEnabled > AUDIOSYNC_SEND) useNetworkAudio = true;
|
||||
}
|
||||
if ((audioSyncEnabled == AUDIOSYNC_REC_PLUS) && useNetworkAudio) disableSoundProcessing = true; // UDP sound receiving - disable local audio
|
||||
|
||||
#ifdef SR_DEBUG
|
||||
// debug info in case that task stack usage changes
|
||||
@@ -1888,7 +2017,7 @@ class AudioReactive : public Usermod {
|
||||
#endif
|
||||
|
||||
// Only run the sampling code IF we're not in Receive mode or realtime mode
|
||||
if (!(audioSyncEnabled & 0x02) && !disableSoundProcessing) {
|
||||
if ((audioSyncEnabled != AUDIOSYNC_REC) && !disableSoundProcessing && !useNetworkAudio) {
|
||||
if (soundAgc > AGC_NUM_PRESETS) soundAgc = 0; // make sure that AGC preset is valid (to avoid array bounds violation)
|
||||
|
||||
unsigned long t_now = millis(); // remember current time
|
||||
@@ -1897,9 +2026,9 @@ class AudioReactive : public Usermod {
|
||||
|
||||
#if defined(SR_DEBUG)
|
||||
// complain when audio userloop has been delayed for long time. Currently we need userloop running between 500 and 1500 times per second.
|
||||
// softhack007 disabled temporarily - avoid serial console spam with MANY leds and low FPS
|
||||
//if ((userloopDelay > /*23*/ 65) && !disableSoundProcessing && (audioSyncEnabled == 0)) {
|
||||
//DEBUG_PRINTF("[AR userLoop] hickup detected -> was inactive for last %d millis!\n", userloopDelay);
|
||||
// softhack007 disabled temporarily - avoid serial console spam with MANY LEDs and low FPS
|
||||
//if ((userloopDelay > /*23*/ 65) && !disableSoundProcessing && (audioSyncEnabled == AUDIOSYNC_NONE)) {
|
||||
//DEBUG_PRINTF("[AR userLoop] hiccup detected -> was inactive for last %d millis!\n", userloopDelay);
|
||||
//}
|
||||
#endif
|
||||
|
||||
@@ -1945,27 +2074,33 @@ class AudioReactive : public Usermod {
|
||||
connectUDPSoundSync(); // ensure we have a connection - if needed
|
||||
|
||||
// UDP Microphone Sync - receive mode
|
||||
if ((audioSyncEnabled & 0x02) && udpSyncConnected) {
|
||||
if ((audioSyncEnabled & AUDIOSYNC_REC) && udpSyncConnected) {
|
||||
// Only run the audio listener code if we're in Receive mode
|
||||
static float syncVolumeSmth = 0;
|
||||
bool have_new_sample = false;
|
||||
if (millis() - lastTime > delayMs) {
|
||||
have_new_sample = receiveAudioData();
|
||||
if (have_new_sample) last_UDPTime = millis();
|
||||
if (have_new_sample) {
|
||||
last_UDPTime = millis();
|
||||
useNetworkAudio = true; // UDP input arrived - use it
|
||||
}
|
||||
lastTime = millis();
|
||||
} else {
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
fftUdp.flush(); // WLEDMM: Flush this if we haven't read it. Does not work on 8266.
|
||||
#endif
|
||||
}
|
||||
if (have_new_sample) syncVolumeSmth = volumeSmth; // remember received sample
|
||||
else volumeSmth = syncVolumeSmth; // restore originally received sample for next run of dynamics limiter
|
||||
limitSampleDynamics(); // run dynamics limiter on received volumeSmth, to hide jumps and hickups
|
||||
if (useNetworkAudio) {
|
||||
if (have_new_sample) syncVolumeSmth = volumeSmth; // remember received sample
|
||||
else volumeSmth = syncVolumeSmth; // restore originally received sample for next run of dynamics limiter
|
||||
limitSampleDynamics(); // run dynamics limiter on received volumeSmth, to hide jumps and hickups
|
||||
limitGEQDynamics(have_new_sample); // WLEDMM experimental: smooth FFT (GEQ) samples
|
||||
}
|
||||
} else {
|
||||
receivedFormat = 0;
|
||||
}
|
||||
|
||||
if ( (audioSyncEnabled & 0x02) // receive mode
|
||||
if ( (audioSyncEnabled & AUDIOSYNC_REC) // receive mode
|
||||
&& udpSyncConnected // connected
|
||||
&& (receivedFormat > 0) // we actually received something in the past
|
||||
&& ((millis() - last_UDPTime) > 25000)) { // close connection after 25sec idle
|
||||
@@ -2011,7 +2146,12 @@ class AudioReactive : public Usermod {
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
//UDP Microphone Sync - transmit mode
|
||||
if ((audioSyncEnabled & 0x01) && (millis() - lastTime > 20)) {
|
||||
#if defined(WLEDMM_FASTPATH)
|
||||
if ((audioSyncEnabled & AUDIOSYNC_SEND) && (haveNewFFTResult || (millis() - lastTime > 24))) { // fastpath: send data once results are ready, or each 25ms as fallback (max sampling time is 23ms)
|
||||
#else
|
||||
if ((audioSyncEnabled & AUDIOSYNC_SEND) && (millis() - lastTime > 20)) { // standard: send data each 20ms
|
||||
#endif
|
||||
haveNewFFTResult = false; // reset notification
|
||||
// Only run the transmit code IF we're in Transmit mode
|
||||
transmitAudioData();
|
||||
lastTime = millis();
|
||||
@@ -2161,7 +2301,15 @@ class AudioReactive : public Usermod {
|
||||
infoArr.add(uiDomString);
|
||||
|
||||
if (enabled) {
|
||||
bool audioSyncIDLE = false; // true if sound sync is not receiving
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
// audio sync status
|
||||
if ((audioSyncEnabled & AUDIOSYNC_REC) && (!udpSyncConnected || (millis() - last_UDPTime > AUDIOSYNC_IDLE_MS))) // connected and nothing received in 2.5sec
|
||||
audioSyncIDLE = true;
|
||||
if ((audioSource == nullptr) || (!audioSource->isInitialized())) // local audio not configured
|
||||
audioSyncIDLE = false;
|
||||
|
||||
// Input Level Slider
|
||||
if (disableSoundProcessing == false) { // only show slider when audio processing is running
|
||||
if (soundAgc > 0) {
|
||||
@@ -2188,11 +2336,11 @@ class AudioReactive : public Usermod {
|
||||
// The following can be used for troubleshooting user errors and is so not enclosed in #ifdef WLED_DEBUG
|
||||
// current Audio input
|
||||
infoArr = user.createNestedArray(F("Audio Source"));
|
||||
if (audioSyncEnabled & 0x02) {
|
||||
if ((audioSyncEnabled == AUDIOSYNC_REC) || (!audioSyncIDLE && (audioSyncEnabled == AUDIOSYNC_REC_PLUS))){
|
||||
// UDP sound sync - receive mode
|
||||
infoArr.add(F("UDP sound sync"));
|
||||
if (udpSyncConnected) {
|
||||
if (millis() - last_UDPTime < 2500)
|
||||
if (millis() - last_UDPTime < AUDIOSYNC_IDLE_MS)
|
||||
infoArr.add(F(" - receiving"));
|
||||
else
|
||||
infoArr.add(F(" - idle"));
|
||||
@@ -2207,7 +2355,7 @@ class AudioReactive : public Usermod {
|
||||
} else {
|
||||
// Analog or I2S digital input
|
||||
if (audioSource && (audioSource->isInitialized())) {
|
||||
// audio source sucessfully configured
|
||||
// audio source successfully configured
|
||||
if (audioSource->getType() == AudioSource::Type_I2SAdc) {
|
||||
infoArr.add(F("ADC analog"));
|
||||
} else {
|
||||
@@ -2240,13 +2388,13 @@ class AudioReactive : public Usermod {
|
||||
}
|
||||
|
||||
// AGC or manual Gain
|
||||
if ((soundAgc==0) && (disableSoundProcessing == false) && !(audioSyncEnabled & 0x02)) {
|
||||
if ((soundAgc == 0) && (disableSoundProcessing == false) && !(audioSyncEnabled == AUDIOSYNC_REC)) {
|
||||
infoArr = user.createNestedArray(F("Manual Gain"));
|
||||
float myGain = ((float)sampleGain/40.0f * (float)inputLevel/128.0f) + 1.0f/16.0f; // non-AGC gain from presets
|
||||
infoArr.add(roundf(myGain*100.0f) / 100.0f);
|
||||
infoArr.add("x");
|
||||
}
|
||||
if (soundAgc && (disableSoundProcessing == false) && !(audioSyncEnabled & 0x02)) {
|
||||
if ((soundAgc > 0) && (disableSoundProcessing == false) && !(audioSyncEnabled == AUDIOSYNC_REC)) {
|
||||
infoArr = user.createNestedArray(F("AGC Gain"));
|
||||
infoArr.add(roundf(multAgc*100.0f) / 100.0f);
|
||||
infoArr.add("x");
|
||||
@@ -2255,18 +2403,24 @@ class AudioReactive : public Usermod {
|
||||
// UDP Sound Sync status
|
||||
infoArr = user.createNestedArray(F("UDP Sound Sync"));
|
||||
if (audioSyncEnabled) {
|
||||
if (audioSyncEnabled & 0x01) {
|
||||
if (audioSyncEnabled & AUDIOSYNC_SEND) {
|
||||
infoArr.add(F("send mode"));
|
||||
if ((udpSyncConnected) && (millis() - lastTime < 2500)) infoArr.add(F(" v2"));
|
||||
} else if (audioSyncEnabled & 0x02) {
|
||||
if ((udpSyncConnected) && (millis() - lastTime < AUDIOSYNC_IDLE_MS)) infoArr.add(F(" v2+"));
|
||||
} else if (audioSyncEnabled == AUDIOSYNC_REC) {
|
||||
infoArr.add(F("receive mode"));
|
||||
} else if (audioSyncEnabled == AUDIOSYNC_REC_PLUS) {
|
||||
infoArr.add(F("receive+local mode"));
|
||||
}
|
||||
} else
|
||||
infoArr.add("off");
|
||||
if (audioSyncEnabled && !udpSyncConnected) infoArr.add(" <i>(unconnected)</i>");
|
||||
if (audioSyncEnabled && udpSyncConnected && (millis() - last_UDPTime < 2500)) {
|
||||
if (audioSyncEnabled && udpSyncConnected && (millis() - last_UDPTime < AUDIOSYNC_IDLE_MS)) {
|
||||
if (receivedFormat == 1) infoArr.add(F(" v1"));
|
||||
if (receivedFormat == 2) infoArr.add(F(" v2"));
|
||||
if (receivedFormat == 3) {
|
||||
if (audioSyncSequence) infoArr.add(F(" v2+")); // Sequence checking enabled
|
||||
else infoArr.add(F(" v2"));
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(WLED_DEBUG) || defined(SR_DEBUG) || defined(SR_STATS)
|
||||
@@ -2418,6 +2572,7 @@ class AudioReactive : public Usermod {
|
||||
JsonObject sync = top.createNestedObject("sync");
|
||||
sync[F("port")] = audioSyncPort;
|
||||
sync[F("mode")] = audioSyncEnabled;
|
||||
sync[F("check_sequence")] = audioSyncSequence;
|
||||
}
|
||||
|
||||
|
||||
@@ -2486,6 +2641,7 @@ class AudioReactive : public Usermod {
|
||||
|
||||
configComplete &= getJsonValue(top["sync"][F("port")], audioSyncPort);
|
||||
configComplete &= getJsonValue(top["sync"][F("mode")], audioSyncEnabled);
|
||||
configComplete &= getJsonValue(top["sync"][F("check_sequence")], audioSyncSequence);
|
||||
|
||||
return configComplete;
|
||||
}
|
||||
@@ -2548,7 +2704,11 @@ class AudioReactive : public Usermod {
|
||||
#else
|
||||
oappend(SET_F("addOption(dd,'ES8388 ☾',6);"));
|
||||
#endif
|
||||
|
||||
#if SR_DMTYPE==7
|
||||
oappend(SET_F("addOption(dd,'WM8978 ☾ (⎌)',7);"));
|
||||
#else
|
||||
oappend(SET_F("addOption(dd,'WM8978 ☾',7);"));
|
||||
#endif
|
||||
#ifdef SR_SQUELCH
|
||||
oappend(SET_F("addInfo('AudioReactive:config:squelch',1,'<i>⎌ ")); oappendi(SR_SQUELCH); oappend("</i>');"); // 0 is field type, 1 is actual field
|
||||
#endif
|
||||
@@ -2652,12 +2812,20 @@ class AudioReactive : public Usermod {
|
||||
oappend(SET_F("addInfo('AudioReactive:frequency:profile',1,'☾');"));
|
||||
#endif
|
||||
oappend(SET_F("dd=addDropdown('AudioReactive','sync:mode');"));
|
||||
oappend(SET_F("addOption(dd,'Off',0);"));
|
||||
oappend(SET_F("addOption(dd,'Off',0);")); // AUDIOSYNC_NONE
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
oappend(SET_F("addOption(dd,'Send',1);"));
|
||||
oappend(SET_F("addOption(dd,'Send',1);")); // AUDIOSYNC_SEND
|
||||
#endif
|
||||
oappend(SET_F("addOption(dd,'Receive',2);"));
|
||||
oappend(SET_F("addInfo('AudioReactive:sync:mode',1,'<br> Sync audio data with other WLEDs');"));
|
||||
oappend(SET_F("addOption(dd,'Receive',2);")); // AUDIOSYNC_REC
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
oappend(SET_F("addOption(dd,'Receive or Local',6);")); // AUDIOSYNC_REC_PLUS
|
||||
#endif
|
||||
// check_sequence: Receiver skips out-of-sequence packets when enabled
|
||||
oappend(SET_F("dd=addDropdown('AudioReactive','sync:check_sequence');"));
|
||||
oappend(SET_F("addOption(dd,'Off',0);"));
|
||||
oappend(SET_F("addOption(dd,'On',1);"));
|
||||
|
||||
oappend(SET_F("addInfo('AudioReactive:sync:check_sequence',1,'<i>when receiving</i> ☾<br> Sync audio data with other WLEDs');")); // must append this to the last field of 'sync'
|
||||
|
||||
oappend(SET_F("addInfo('AudioReactive:digitalmic:type',1,'<i>requires reboot!</i>');")); // 0 is field type, 1 is actual field
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
@@ -2700,7 +2868,7 @@ class AudioReactive : public Usermod {
|
||||
oappend(SET_F("xOpt('AudioReactive:digitalmic:pin[]',5,' ⎌',")); oappendi(ES7243_SCLPIN); oappend(");");
|
||||
#endif
|
||||
oappend(SET_F("dRO('AudioReactive:digitalmic:pin[]',5);")); // disable read only pins
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
// benefit: analog mic inputs will be sampled contiously -> better response times and less "glitches"
|
||||
// WARNING: this option WILL lock-up your device in case that any other analogRead() operation is performed;
|
||||
// for example if you want to read "analog buttons"
|
||||
//#define I2S_GRAB_ADC1_COMPLETELY // (experimental) continously sample analog ADC microphone. WARNING will cause analogRead() lock-up
|
||||
//#define I2S_GRAB_ADC1_COMPLETELY // (experimental) continuously sample analog ADC microphone. WARNING will cause analogRead() lock-up
|
||||
|
||||
// data type requested from the I2S driver - currently we always use 32bit
|
||||
//#define I2S_USE_16BIT_SAMPLES // (experimental) define this to request 16bit - more efficient but possibly less compatible
|
||||
@@ -72,7 +72,7 @@
|
||||
* if you want to receive two channels, one is the actual data from microphone and another channel is suppose to receive 0, it's different data in two channels, you need to choose I2S_CHANNEL_FMT_RIGHT_LEFT in this case.
|
||||
*/
|
||||
|
||||
#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0)) && (ESP_IDF_VERSION <= ESP_IDF_VERSION_VAL(4, 4, 4))
|
||||
#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0)) && (ESP_IDF_VERSION <= ESP_IDF_VERSION_VAL(4, 4, 6)) // fixed in IDF 4.4.5, however arduino-esp32 2.0.14 did an "I2S rollback" to 4.4.4
|
||||
// espressif bug: only_left has no sound, left and right are swapped
|
||||
// https://github.com/espressif/esp-idf/issues/9635 I2S mic not working since 4.4 (IDFGH-8138)
|
||||
// https://github.com/espressif/esp-idf/issues/8538 I2S channel selection issue? (IDFGH-6918)
|
||||
@@ -163,7 +163,7 @@ class AudioSource {
|
||||
SRate_t _sampleRate; // Microphone sampling rate
|
||||
int _blockSize; // I2S block size
|
||||
bool _initialized; // Gets set to true if initialization is successful
|
||||
bool _i2sMaster; // when false, ESP32 will be in I2S SLAVE mode (for devices that only operate in MASTER mode). Only workds in newer IDF >= 4.4.x
|
||||
bool _i2sMaster; // when false, ESP32 will be in I2S SLAVE mode (for devices that only operate in MASTER mode). Only works in newer IDF >= 4.4.x
|
||||
float _sampleScale; // pre-scaling factor for I2S samples
|
||||
I2S_datatype newSampleBuffer[I2S_SAMPLES_MAX+4] = { 0 }; // global buffer for i2s_read
|
||||
I2S_datatype newSampleBuffer4x[(I2S_SAMPLES_MAX*4)+4] = { 0 }; // global buffer for i2s_read
|
||||
@@ -438,7 +438,7 @@ class I2SSource : public AudioSource {
|
||||
};
|
||||
|
||||
/* ES7243 Microphone
|
||||
This is an I2S microphone that requires ininitialization over
|
||||
This is an I2S microphone that requires initialization over
|
||||
I2C before I2S data can be received
|
||||
*/
|
||||
class ES7243 : public I2SSource {
|
||||
@@ -503,8 +503,8 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
/* ES8388 Sound Modude
|
||||
This is an I2S sound processing unit that requires ininitialization over
|
||||
/* ES8388 Sound Module
|
||||
This is an I2S sound processing unit that requires initialization over
|
||||
I2C before I2S data can be received.
|
||||
*/
|
||||
class ES8388Source : public I2SSource {
|
||||
@@ -552,7 +552,7 @@ class ES8388Source : public I2SSource {
|
||||
// The mics *and* line-in are BOTH connected to LIN2/RIN2 on the AudioKit
|
||||
// so there's no way to completely eliminate the mics. It's also hella noisy.
|
||||
// Line-in works OK on the AudioKit, generally speaking, as the mics really need
|
||||
// amplification to be noticable in a quiet room. If you're in a very loud room,
|
||||
// amplification to be noticeable in a quiet room. If you're in a very loud room,
|
||||
// the mics on the AudioKit WILL pick up sound even in line-in mode.
|
||||
// TL;DR: Don't use the AudioKit for anything, use the LyraT.
|
||||
//
|
||||
@@ -635,6 +635,97 @@ class ES8388Source : public I2SSource {
|
||||
|
||||
};
|
||||
|
||||
class WM8978Source : public I2SSource {
|
||||
private:
|
||||
// I2C initialization functions for WM8978
|
||||
void _wm8978I2cBegin() {
|
||||
Wire.setClock(400000);
|
||||
}
|
||||
|
||||
void _wm8978I2cWrite(uint8_t reg, uint16_t val) {
|
||||
#ifndef WM8978_ADDR
|
||||
#define WM8978_ADDR 0x1A
|
||||
#endif
|
||||
char buf[2];
|
||||
buf[0] = (reg << 1) | ((val >> 8) & 0X01);
|
||||
buf[1] = val & 0XFF;
|
||||
Wire.beginTransmission(WM8978_ADDR);
|
||||
Wire.write((const uint8_t*)buf, 2);
|
||||
uint8_t i2cErr = Wire.endTransmission(); // i2cErr == 0 means OK
|
||||
if (i2cErr != 0) {
|
||||
DEBUGSR_PRINTF("AR: WM8978 I2C write failed with error=%d (addr=0x%X, reg 0x%X, val 0x%X).\n", i2cErr, WM8978_ADDR, reg, val);
|
||||
}
|
||||
}
|
||||
|
||||
void _wm8978InitAdc() {
|
||||
// https://www.mouser.com/datasheet/2/76/WM8978_v4.5-1141768.pdf
|
||||
// Sets ADC to around what AudioReactive expects, and loops line-in to line-out/headphone for monitoring.
|
||||
// Registries are decimal, settings are 9-bit binary as that's how everything is listed in the docs
|
||||
// ...which makes it easier to reference the docs.
|
||||
//
|
||||
_wm8978I2cBegin();
|
||||
|
||||
_wm8978I2cWrite( 0,0b000000000); // Reset all settings
|
||||
_wm8978I2cWrite( 1,0b000001011); // Power Management 1 - power off most things
|
||||
_wm8978I2cWrite( 2,0b110110011); // Power Management 2 - enable output and amp stages (amps may lift signal but it works better on the ADCs)
|
||||
_wm8978I2cWrite( 3,0b000001100); // Power Management 3 - enable L&R output mixers
|
||||
_wm8978I2cWrite( 4,0b001010000); // Audio Interface - standard I2S, 24-bit
|
||||
_wm8978I2cWrite( 5,0b000000001); // Loopback Enable
|
||||
_wm8978I2cWrite( 6,0b000000000); // Clock generation control - use external mclk
|
||||
_wm8978I2cWrite( 7,0b000000100); // Sets sample rate to ~24kHz (only used for internal calculations, not I2S)
|
||||
_wm8978I2cWrite(14,0b010001000); // 128x ADC oversampling - high pass filter disabled as it kills the bass response
|
||||
_wm8978I2cWrite(43,0b000110000); // Mute signal paths we don't use
|
||||
_wm8978I2cWrite(44,0b000000000); // Disconnect microphones
|
||||
_wm8978I2cWrite(45,0b111000000); // Mute signal paths we don't use
|
||||
_wm8978I2cWrite(46,0b111000000); // Mute signal paths we don't use
|
||||
_wm8978I2cWrite(47,0b001000000); // 0dB gain on left line-in
|
||||
_wm8978I2cWrite(48,0b001000000); // 0dB gain on right line-in
|
||||
_wm8978I2cWrite(49,0b000000010); // Mixer thermal shutdown enable
|
||||
_wm8978I2cWrite(50,0b000010110); // Output mixer enable only left bypass at 0dB gain
|
||||
_wm8978I2cWrite(51,0b000010110); // Output mixer enable only right bypass at 0dB gain
|
||||
_wm8978I2cWrite(52,0b110111001); // Left line-out enabled at 0dB gain
|
||||
_wm8978I2cWrite(53,0b110111001); // Right line-out enabled at 0db gain
|
||||
_wm8978I2cWrite(54,0b001000000); // Mute left speaker output
|
||||
_wm8978I2cWrite(55,0b101000000); // Mute right speaker output
|
||||
|
||||
}
|
||||
|
||||
public:
|
||||
WM8978Source(SRate_t sampleRate, int blockSize, float sampleScale = 1.0f, bool i2sMaster=true) :
|
||||
I2SSource(sampleRate, blockSize, sampleScale, i2sMaster) {
|
||||
_config.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT;
|
||||
};
|
||||
|
||||
void initialize(int8_t i2swsPin, int8_t i2ssdPin, int8_t i2sckPin, int8_t mclkPin) {
|
||||
DEBUGSR_PRINTLN("WM8978Source:: initialize();");
|
||||
|
||||
// if ((i2sckPin < 0) || (mclkPin < 0)) { // WLEDMM not sure if this check is needed here, too
|
||||
// ERRORSR_PRINTF("\nAR: invalid I2S WM8978 pin: SCK=%d, MCLK=%d\n", i2sckPin, mclkPin);
|
||||
// return;
|
||||
// }
|
||||
// BUG: "use global I2C pins" are valid as -1, and -1 is seen as invalid here.
|
||||
// Workaround: Set I2C pins here, which will also set them globally.
|
||||
// Bug also exists in ES7243.
|
||||
if ((i2c_sda < 0) || (i2c_scl < 0)) { // check that global I2C pins are not "undefined"
|
||||
ERRORSR_PRINTF("\nAR: invalid WM8978 global I2C pins: SDA=%d, SCL=%d\n", i2c_sda, i2c_scl);
|
||||
return;
|
||||
}
|
||||
if (!pinManager.joinWire(i2c_sda, i2c_scl)) { // WLEDMM specific: start I2C with globally defined pins
|
||||
ERRORSR_PRINTF("\nAR: failed to join I2C bus with SDA=%d, SCL=%d\n", i2c_sda, i2c_scl);
|
||||
return;
|
||||
}
|
||||
|
||||
// First route mclk, then configure ADC over I2C, then configure I2S
|
||||
_wm8978InitAdc();
|
||||
I2SSource::initialize(i2swsPin, i2ssdPin, i2sckPin, mclkPin);
|
||||
}
|
||||
|
||||
void deinitialize() {
|
||||
I2SSource::deinitialize();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
#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
|
||||
@@ -642,7 +733,7 @@ class ES8388Source : public I2SSource {
|
||||
#endif
|
||||
|
||||
#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 available in "classic" ESP32
|
||||
|
||||
/* ADC over I2S Microphone
|
||||
This microphone is an ADC pin sampled via the I2S interval
|
||||
@@ -686,7 +777,7 @@ class I2SAdcSource : public I2SSource {
|
||||
|
||||
// Determine Analog channel. Only Channels on ADC1 are supported
|
||||
int8_t channel = digitalPinToAnalogChannel(_audioPin);
|
||||
if (channel > 9) {
|
||||
if ((channel < 0) || (channel > 9)) { // channel == -1 means "not an ADC pin"
|
||||
USER_PRINTF("AR: Incompatible GPIO used for analog audio input: %d\n", _audioPin);
|
||||
return;
|
||||
} else {
|
||||
@@ -701,7 +792,7 @@ class I2SAdcSource : public I2SSource {
|
||||
return;
|
||||
}
|
||||
|
||||
adc1_config_width(ADC_WIDTH_BIT_12); // ensure that ADC runs with 12bit resolution
|
||||
// adc1_config_width(ADC_WIDTH_BIT_12); // ensure that ADC runs with 12bit resolution - should not be needed, because i2s_set_adc_mode does that any way
|
||||
|
||||
// Enable I2S mode of ADC
|
||||
err = i2s_set_adc_mode(ADC_UNIT_1, adc1_channel_t(channel));
|
||||
@@ -859,4 +950,4 @@ class SPH0654 : public I2SSource {
|
||||
#endif
|
||||
}
|
||||
};
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Audioreactive usermod
|
||||
|
||||
Enabless controlling LEDs via audio input. Audio source can be a microphone or analog-in (AUX) using an appropriate adapter.
|
||||
Enables controlling LEDs via audio input. Audio source can be a microphone or analog-in (AUX) using an appropriate adapter.
|
||||
Supported microphones range from analog (MAX4466, MAX9814, ...) to digital (INMP441, ICS-43434, ...).
|
||||
|
||||
Does audio processing and provides data structure that specially written effects can use.
|
||||
@@ -19,7 +19,7 @@ This usermod is an evolution of [SR-WLED](https://github.com/atuline/WLED), and
|
||||
## 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.
|
||||
It will compile successfully 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.
|
||||
|
||||
@@ -35,7 +35,7 @@ Customised _arduinoFFT_ library for use with this usermod can be found at https:
|
||||
|
||||
### 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.
|
||||
ArduinoFFT `develop` library is slightly more accurate, and slightly 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`
|
||||
@@ -63,7 +63,7 @@ You can use the following additional flags in your `build_flags`
|
||||
* `-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 I2S_GRAB_ADC1_COMPLETELY`: Experimental: continuously 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.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user