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
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user