Merge remote-tracking branch 'origin/dev' into custom-effects
This commit is contained in:
@@ -24,7 +24,7 @@
|
||||
// #define MIC_LOGGER // MIC sampling & sound input debugging (serial plotter)
|
||||
// #define FFT_SAMPLING_LOG // FFT result debugging
|
||||
// #define SR_DEBUG // generic SR DEBUG messages
|
||||
// #define NO_MIC_LOGGER // exclude MIC_LOGGER from SR_DEBUG
|
||||
|
||||
|
||||
#ifdef SR_DEBUG
|
||||
#define DEBUGSR_PRINT(x) Serial.print(x)
|
||||
@@ -85,41 +85,51 @@ const float agcSampleSmooth[AGC_NUM_PRESETS] = { 1/12.f, 1/6.f, 1/16.f}; //
|
||||
static AudioSource *audioSource = nullptr;
|
||||
static volatile bool disableSoundProcessing = false; // if true, sound processing (FFT, filters, AGC) will be suspended. "volatile" as its shared between tasks.
|
||||
|
||||
// audioreactive variables shared with FFT task
|
||||
static float micDataReal = 0.0f; // MicIn data with full 24bit resolution - lowest 8bit after decimal point
|
||||
static float sampleReal = 0.0f; // "sampleRaw" as float, to provide bits that are lost otherwise (before amplification by sampleGain or inputLevel). Needed for AGC.
|
||||
static float multAgc = 1.0f; // sample * multAgc = sampleAgc. Our AGC multiplier
|
||||
static float sampleAvg = 0.0f; // Smoothed Average sample - sampleAvg < 1 means "quiet" (simple noise gate)
|
||||
|
||||
// peak detection
|
||||
static bool samplePeak = false; // Boolean flag for peak - used in effects. Responding routine may reset this flag. Auto-reset after strip.getMinShowDelay()
|
||||
static uint8_t maxVol = 10; // Reasonable value for constant volume for 'peak detector', as it won't always trigger (deprecated)
|
||||
static uint8_t binNum = 8; // Used to select the bin for FFT based beat detection (deprecated)
|
||||
static bool udpSamplePeak = false; // Boolean flag for peak. Set at the same tiem as samplePeak, but reset by transmitAudioData
|
||||
static unsigned long timeOfPeak = 0; // time of last sample peak detection.
|
||||
static void detectSamplePeak(void); // peak detection function (needs scaled FFT reasults in vReal[])
|
||||
static void autoResetPeak(void); // peak auto-reset function
|
||||
|
||||
static int16_t sampleRaw = 0; // Current sample. Must only be updated ONCE!!! (amplified mic value by sampleGain and inputLevel)
|
||||
static int16_t rawSampleAgc = 0; // not smoothed AGC sample
|
||||
static float sampleAvg = 0.0f; // Smoothed Average sampleRaw
|
||||
static float sampleAgc = 0.0f; // Smoothed AGC sample
|
||||
|
||||
////////////////////
|
||||
// Begin FFT Code //
|
||||
////////////////////
|
||||
|
||||
#ifdef UM_AUDIOREACTIVE_USE_NEW_FFT
|
||||
// lib_deps += https://github.com/kosme/arduinoFFT#develop @ 1.9.2
|
||||
#define FFT_SPEED_OVER_PRECISION // enables use of reciprocals (1/x etc), and an a few other speedups
|
||||
#define FFT_SQRT_APPROXIMATION // enables "quake3" style inverse sqrt
|
||||
#define sqrt(x) sqrtf(x) // little hack that reduces FFT time by 50% on ESP32 (as alternative to FFT_SQRT_APPROXIMATION)
|
||||
#else
|
||||
// lib_deps += https://github.com/blazoncek/arduinoFFT.git
|
||||
#endif
|
||||
#include "arduinoFFT.h"
|
||||
|
||||
// FFT Variables
|
||||
// FFT Output variables shared with animations
|
||||
#define NUM_GEQ_CHANNELS 16 // number of frequency channels. Don't change !!
|
||||
static float FFT_MajorPeak = 1.0f; // FFT: strongest (peak) frequency
|
||||
static float FFT_Magnitude = 0.0f; // FFT: volume (magnitude) of peak frequency
|
||||
static uint8_t fftResult[NUM_GEQ_CHANNELS]= {0};// Our calculated freq. channel result table to be used by effects
|
||||
|
||||
// FFT Constants
|
||||
constexpr uint16_t samplesFFT = 512; // Samples in an FFT batch - This value MUST ALWAYS be a power of 2
|
||||
constexpr uint16_t samplesFFT_2 = 256; // meaningfull part of FFT results - only the "lower half" contains useful information.
|
||||
|
||||
static float FFT_MajorPeak = 1.0f;
|
||||
static float FFT_Magnitude = 0.0f;
|
||||
|
||||
// These are the input and output vectors. Input vectors receive computed results from FFT.
|
||||
static float vReal[samplesFFT] = {0.0f};
|
||||
static float vImag[samplesFFT] = {0.0f};
|
||||
static float fftBin[samplesFFT_2] = {0.0f};
|
||||
static float vReal[samplesFFT] = {0.0f}; // FFT sample inputs / freq output - these are our raw result bins
|
||||
static float vImag[samplesFFT] = {0.0f}; // imaginary parts
|
||||
|
||||
// the following are observed values, supported by a bit of "educated guessing"
|
||||
//#define FFT_DOWNSCALE 0.65f // 20kHz - downscaling factor for FFT results - "Flat-Top" window @20Khz, old freq channels
|
||||
|
||||
#define FFT_DOWNSCALE 0.46f // downscaling factor for FFT results - for "Flat-Top" window @22Khz, new freq channels
|
||||
#define LOG_256 5.54517744
|
||||
|
||||
@@ -128,13 +138,11 @@ static float windowWeighingFactors[samplesFFT] = {0.0f};
|
||||
#endif
|
||||
|
||||
// Try and normalize fftBin values to a max of 4096, so that 4096/16 = 256.
|
||||
// Oh, and bins 0,1,2 are no good, so we'll zero them out.
|
||||
static float fftCalc[16] = {0.0f};
|
||||
static uint8_t fftResult[16] = {0}; // Our calculated result table, which we feed to the animations.
|
||||
static float fftCalc[NUM_GEQ_CHANNELS] = {0.0f};
|
||||
static float fftAvg[NUM_GEQ_CHANNELS] = {0.0f}; // Calculated frequency channel results, with smoothing (used if dynamics limiter is ON)
|
||||
#ifdef SR_DEBUG
|
||||
static float fftResultMax[16] = {0.0f}; // A table used for testing to determine how our post-processing is working.
|
||||
static float fftResultMax[NUM_GEQ_CHANNELS] = {0.0f}; // A table used for testing to determine how our post-processing is working.
|
||||
#endif
|
||||
static float fftAvg[16] = {0.0f};
|
||||
|
||||
#ifdef WLED_DEBUG
|
||||
static unsigned long fftTime = 0;
|
||||
@@ -142,7 +150,7 @@ static unsigned long sampleTime = 0;
|
||||
#endif
|
||||
|
||||
// Table of multiplication factors so that we can even out the frequency response.
|
||||
static float fftResultPink[16] = { 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 };
|
||||
static float fftResultPink[NUM_GEQ_CHANNELS] = { 1.70f, 1.71f, 1.73f, 1.78f, 1.68f, 1.56f, 1.55f, 1.63f, 1.79f, 1.62f, 1.80f, 2.06f, 2.47f, 3.35f, 6.83f, 9.55f };
|
||||
|
||||
// Create FFT object
|
||||
#ifdef UM_AUDIOREACTIVE_USE_NEW_FFT
|
||||
@@ -161,12 +169,12 @@ static float mapf(float x, float in_min, float in_max, float out_min, float out_
|
||||
static float fftAddAvg(int from, int to) {
|
||||
float result = 0.0f;
|
||||
for (int i = from; i <= to; i++) {
|
||||
result += fftBin[i];
|
||||
result += vReal[i];
|
||||
}
|
||||
return result / float(to - from + 1);
|
||||
}
|
||||
|
||||
// FFT main code
|
||||
// FFT main task
|
||||
void FFTcode(void * parameter)
|
||||
{
|
||||
DEBUGSR_PRINT("FFT started on core: "); DEBUGSR_PRINTLN(xPortGetCoreID());
|
||||
@@ -237,9 +245,9 @@ void FFTcode(void * parameter)
|
||||
#endif
|
||||
FFT_MajorPeak = constrain(FFT_MajorPeak, 1.0f, 11025.0f); // restrict value to range expected by effects
|
||||
|
||||
for (int i = 0; i < samplesFFT_2; i++) { // Values for bins 0 and 1 are WAY too large. Might as well start at 3.
|
||||
for (int i = 0; i < samplesFFT; i++) {
|
||||
float t = fabsf(vReal[i]); // just to be sure - values in fft bins should be positive any way
|
||||
fftBin[i] = t / 16.0f; // Reduce magnitude. Want end result to be linear and ~4096 max.
|
||||
vReal[i] = t / 16.0f; // Reduce magnitude. Want end result to be scaled linear and ~4096 max.
|
||||
} // for()
|
||||
|
||||
// mapping of FFT result bins to frequency channels
|
||||
@@ -292,14 +300,14 @@ void FFTcode(void * parameter)
|
||||
// don't use the last bins from 216 to 255. They are usually contaminated by aliasing (aka noise)
|
||||
#endif
|
||||
} else { // noise gate closed - just decay old values
|
||||
for (int i=0; i < 16; i++) {
|
||||
for (int i=0; i < NUM_GEQ_CHANNELS; i++) {
|
||||
fftCalc[i] *= 0.85f; // decay to zero
|
||||
if (fftCalc[i] < 4.0f) fftCalc[i] = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
// post-processing of frequency channels (pink noise adjustment, AGC, smooting, scaling)
|
||||
for (int i=0; i < 16; i++) {
|
||||
for (int i=0; i < NUM_GEQ_CHANNELS; i++) {
|
||||
|
||||
if (sampleAvg > 1) { // noise gate open
|
||||
// Adjustment for frequency curves.
|
||||
@@ -378,11 +386,43 @@ void FFTcode(void * parameter)
|
||||
fftTime = (fftTimeInMillis*3 + fftTime*7)/10; // smooth
|
||||
}
|
||||
#endif
|
||||
// run peak detection
|
||||
autoResetPeak();
|
||||
detectSamplePeak();
|
||||
|
||||
} // for(;;)
|
||||
} // FFTcode()
|
||||
} // for(;;)ever
|
||||
} // FFTcode() task end
|
||||
|
||||
|
||||
////////////////////
|
||||
// Peak detection //
|
||||
////////////////////
|
||||
|
||||
// peak detection is called from FFT task when vReal[] contains valid FFT results
|
||||
static void detectSamplePeak(void) {
|
||||
// Poor man's beat detection by seeing if sample > Average + some value.
|
||||
if ((sampleAvg > 1) && (maxVol > 0) && (binNum > 1) && (vReal[binNum] > maxVol) && ((millis() - timeOfPeak) > 100)) {
|
||||
// This goes through ALL of the 255 bins - but ignores stupid settings
|
||||
// Then we got a peak, else we don't. The peak has to time out on its own in order to support UDP sound sync.
|
||||
samplePeak = true;
|
||||
timeOfPeak = millis();
|
||||
udpSamplePeak = true;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
////////////////////
|
||||
// usermod class //
|
||||
////////////////////
|
||||
|
||||
//class name. Use something descriptive and leave the ": public Usermod" part :)
|
||||
class AudioReactive : public Usermod {
|
||||
|
||||
@@ -453,40 +493,36 @@ class AudioReactive : public Usermod {
|
||||
double FFT_MajorPeak; // 08 Bytes
|
||||
};
|
||||
|
||||
WiFiUDP fftUdp;
|
||||
|
||||
// set your config variables to their boot default value (this can also be done in readFromConfig() or a constructor if you prefer)
|
||||
bool enabled = false;
|
||||
bool initDone = false;
|
||||
|
||||
const uint16_t delayMs = 10; // I don't want to sample too often and overload WLED
|
||||
// variables for UDP sound sync
|
||||
WiFiUDP fftUdp; // UDP object for sound sync (from WiFi UDP, not Async UDP!)
|
||||
bool udpSyncConnected = false;// UDP connection status -> true if connected to multicast group
|
||||
unsigned long lastTime = 0; // last time of running UDP Microphone Sync
|
||||
const uint16_t delayMs = 10; // I don't want to sample too often and overload WLED
|
||||
uint16_t audioSyncPort= 11988;// default port for UDP sound sync
|
||||
|
||||
// used for AGC
|
||||
int last_soundAgc = -1; // used to detect AGC mode change (for resetting AGC internal error buffers)
|
||||
double control_integrated = 0.0; // persistent across calls to agcAvg(); "integrator control" = accumulated error
|
||||
|
||||
// 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.
|
||||
float micLev = 0.0f; // 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.
|
||||
int16_t sampleRaw = 0; // Current sample. Must only be updated ONCE!!! (amplified mic value by sampleGain and inputLevel)
|
||||
int16_t rawSampleAgc = 0; // not smoothed AGC sample
|
||||
float sampleAgc = 0.0f; // Smoothed AGC sample
|
||||
|
||||
// variables used in effects
|
||||
uint8_t maxVol = 10; // Reasonable value for constant volume for 'peak detector', as it won't always trigger (deprecated)
|
||||
uint8_t binNum = 8; // Used to select the bin for FFT based beat detection (deprecated)
|
||||
bool samplePeak = 0; // Boolean flag for peak. Responding routine must reset this flag
|
||||
float volumeSmth = 0.0f; // either sampleAvg or sampleAgc depending on soundAgc; smoothed sample
|
||||
int16_t volumeRaw = 0; // either sampleRaw or rawSampleAgc depending on soundAgc
|
||||
float my_magnitude =0.0f; // FFT_Magnitude, scaled by multAgc
|
||||
|
||||
bool udpSamplePeak = 0; // Boolean flag for peak. Set at the same tiem as samplePeak, but reset by transmitAudioData
|
||||
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.
|
||||
uint32_t timeOfPeak = 0;
|
||||
unsigned long lastTime = 0; // last time of running UDP Microphone Sync
|
||||
float micLev = 0.0f; // Used to convert returned value to have '0' as minimum. A leveller
|
||||
float expAdjF = 0.0f; // Used for exponential filter.
|
||||
|
||||
bool udpSyncConnected = false;
|
||||
uint16_t audioSyncPort = 11988;
|
||||
|
||||
// used for AGC
|
||||
uint8_t lastMode = 0; // last known effect mode
|
||||
int last_soundAgc = -1;
|
||||
double control_integrated = 0.0; // persistent across calls to agcAvg(); "integrator control" = accumulated error
|
||||
unsigned long last_update_time = 0;
|
||||
unsigned long last_kick_time = 0;
|
||||
uint8_t last_user_inputLevel = 0;
|
||||
|
||||
// used to feed "Info" Page
|
||||
unsigned long last_UDPTime = 0; // time of last valid UDP sound sync datapacket
|
||||
float maxSample5sec = 0.0f; // max sample (after AGC) in last 5 seconds
|
||||
@@ -503,6 +539,10 @@ class AudioReactive : public Usermod {
|
||||
static const char UDP_SYNC_HEADER_v1[];
|
||||
|
||||
// private methods
|
||||
|
||||
////////////////////
|
||||
// Debug support //
|
||||
////////////////////
|
||||
void logAudio()
|
||||
{
|
||||
#ifdef MIC_LOGGER
|
||||
@@ -525,7 +565,7 @@ class AudioReactive : public Usermod {
|
||||
|
||||
#ifdef FFT_SAMPLING_LOG
|
||||
#if 0
|
||||
for(int i=0; i<16; i++) {
|
||||
for(int i=0; i<NUM_GEQ_CHANNELS; i++) {
|
||||
Serial.print(fftResult[i]);
|
||||
Serial.print("\t");
|
||||
}
|
||||
@@ -551,11 +591,11 @@ class AudioReactive : public Usermod {
|
||||
|
||||
int maxVal = minimumMaxVal;
|
||||
int minVal = 0;
|
||||
for(int i = 0; i < 16; i++) {
|
||||
for(int i = 0; i < NUM_GEQ_CHANNELS; i++) {
|
||||
if(fftResult[i] > maxVal) maxVal = fftResult[i];
|
||||
if(fftResult[i] < minVal) minVal = fftResult[i];
|
||||
}
|
||||
for(int i = 0; i < 16; i++) {
|
||||
for(int i = 0; i < NUM_GEQ_CHANNELS; i++) {
|
||||
Serial.print(i); Serial.print(":");
|
||||
Serial.printf("%04ld ", map(fftResult[i], 0, (scaleValuesFromCurrentMaxVal ? maxVal : defaultScalingFromHighValue), (mapValuesToPlotterSpace*i*scalingToHighValue)+0, (mapValuesToPlotterSpace*i*scalingToHighValue)+scalingToHighValue-1));
|
||||
}
|
||||
@@ -574,6 +614,10 @@ class AudioReactive : public Usermod {
|
||||
} // logAudio()
|
||||
|
||||
|
||||
//////////////////////
|
||||
// Audio Processing //
|
||||
//////////////////////
|
||||
|
||||
/*
|
||||
* A "PI controller" multiplier to automatically adjust sound sensitivity.
|
||||
*
|
||||
@@ -668,7 +712,7 @@ class AudioReactive : public Usermod {
|
||||
last_soundAgc = soundAgc;
|
||||
} // agcAvg()
|
||||
|
||||
|
||||
// post-processing and filtering of MIC sample (micDataReal) from FFTcode()
|
||||
void getSample()
|
||||
{
|
||||
float sampleAdj; // Gain adjusted sample value
|
||||
@@ -729,24 +773,6 @@ class AudioReactive : public Usermod {
|
||||
if (sampleMax < 0.5f) sampleMax = 0.0f;
|
||||
|
||||
sampleAvg = ((sampleAvg * 15.0f) + sampleAdj) / 16.0f; // Smooth it out over the last 16 samples.
|
||||
|
||||
// Fixes private class variable compiler error. Unsure if this is the correct way of fixing the root problem. -THATDONFC
|
||||
uint16_t MinShowDelay = strip.getMinShowDelay();
|
||||
|
||||
if (millis() - timeOfPeak > MinShowDelay) { // Auto-reset of samplePeak after a complete frame has passed.
|
||||
samplePeak = false;
|
||||
udpSamplePeak = false;
|
||||
}
|
||||
//if (userVar1 == 0) samplePeak = 0;
|
||||
|
||||
// Poor man's beat detection by seeing if sample > Average + some value.
|
||||
if ((maxVol > 0) && (binNum > 1) && (fftBin[binNum] > maxVol) && (millis() > (timeOfPeak + 100))) {
|
||||
// This goes through ALL of the 255 bins - but ignores stupid settings
|
||||
// Then we got a peak, else we don't. The peak has to time out on its own in order to support UDP sound sync.
|
||||
samplePeak = true;
|
||||
timeOfPeak = millis();
|
||||
udpSamplePeak = true;
|
||||
}
|
||||
} // getSample()
|
||||
|
||||
|
||||
@@ -781,6 +807,26 @@ class AudioReactive : public Usermod {
|
||||
}
|
||||
|
||||
|
||||
//////////////////////
|
||||
// UDP Sound Sync //
|
||||
//////////////////////
|
||||
|
||||
// try to establish UDP sound sync connection
|
||||
void connectUDPSoundSync(void) {
|
||||
// This function tries to establish a UDP sync connection if needed
|
||||
// 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 (udpSyncConnected) return; // already connected
|
||||
if (!(apActive || interfacesInited)) return; // neither AP nor other connections availeable
|
||||
if (millis() - last_connection_attempt < 15000) return; // only try once in 15 seconds
|
||||
|
||||
// if we arrive here, we need a UDP connection but don't have one
|
||||
last_connection_attempt = millis();
|
||||
connected(); // try to start UDP
|
||||
}
|
||||
|
||||
void transmitAudioData()
|
||||
{
|
||||
if (!udpSyncConnected) return;
|
||||
@@ -795,7 +841,7 @@ class AudioReactive : public Usermod {
|
||||
udpSamplePeak = false; // Reset udpSamplePeak after we've transmitted it
|
||||
transmitData.reserved1 = 0;
|
||||
|
||||
for (int i = 0; i < 16; i++) {
|
||||
for (int i = 0; i < NUM_GEQ_CHANNELS; i++) {
|
||||
transmitData.fftResult[i] = (uint8_t)constrain(fftResult[i], 0, 254);
|
||||
}
|
||||
|
||||
@@ -808,12 +854,10 @@ class AudioReactive : public Usermod {
|
||||
return;
|
||||
} // transmitAudioData()
|
||||
|
||||
|
||||
static bool isValidUdpSyncVersion(const char *header) {
|
||||
return strncmp_P(header, PSTR(UDP_SYNC_HEADER), 6) == 0;
|
||||
}
|
||||
|
||||
|
||||
bool receiveAudioData() // check & process new data. return TRUE in case that new audio data was received.
|
||||
{
|
||||
if (!udpSyncConnected) return false;
|
||||
@@ -839,13 +883,7 @@ class AudioReactive : public Usermod {
|
||||
sampleAgc = volumeSmth;
|
||||
multAgc = 1.0f;
|
||||
|
||||
// auto-reset sample peak. Need to do it here, because getSample() is not running
|
||||
uint16_t MinShowDelay = strip.getMinShowDelay();
|
||||
if (millis() - timeOfPeak > MinShowDelay) { // Auto-reset of samplePeak after a complete frame has passed.
|
||||
samplePeak = false;
|
||||
udpSamplePeak = false;
|
||||
}
|
||||
//if (userVar1 == 0) samplePeak = 0;
|
||||
autoResetPeak();
|
||||
// Only change samplePeak IF it's currently false.
|
||||
// If it's true already, then the animation still needs to respond.
|
||||
if (!samplePeak) {
|
||||
@@ -855,7 +893,7 @@ class AudioReactive : public Usermod {
|
||||
}
|
||||
|
||||
//These values are only available on the ESP32
|
||||
for (int i = 0; i < 16; i++) fftResult[i] = receivedPacket->fftResult[i];
|
||||
for (int i = 0; i < NUM_GEQ_CHANNELS; i++) fftResult[i] = receivedPacket->fftResult[i];
|
||||
|
||||
my_magnitude = fmaxf(receivedPacket->FFT_Magnitude, 0.0f);
|
||||
FFT_Magnitude = my_magnitude;
|
||||
@@ -869,6 +907,10 @@ class AudioReactive : public Usermod {
|
||||
}
|
||||
|
||||
|
||||
//////////////////////
|
||||
// usermod functions//
|
||||
//////////////////////
|
||||
|
||||
public:
|
||||
//Functions called by WLED or other usermods
|
||||
|
||||
@@ -961,6 +1003,7 @@ class AudioReactive : public Usermod {
|
||||
disableSoundProcessing = true;
|
||||
}
|
||||
|
||||
if (enabled) connectUDPSoundSync();
|
||||
initDone = true;
|
||||
}
|
||||
|
||||
@@ -971,6 +1014,11 @@ class AudioReactive : public Usermod {
|
||||
*/
|
||||
void connected()
|
||||
{
|
||||
if (udpSyncConnected) { // clean-up: if open, close old UDP sync connection
|
||||
udpSyncConnected = false;
|
||||
fftUdp.stop();
|
||||
}
|
||||
|
||||
if (audioSyncPort > 0 && (audioSyncEnabled & 0x03)) {
|
||||
#ifndef ESP8266
|
||||
udpSyncConnected = fftUdp.beginMulticast(IPAddress(239, 0, 0, 1), audioSyncPort);
|
||||
@@ -1004,7 +1052,7 @@ class AudioReactive : public Usermod {
|
||||
if (strip.isUpdating() && (millis() - lastUMRun < 2)) return; // be nice, but not too nice
|
||||
|
||||
// suspend local sound processing when "real time mode" is active (E131, UDP, ADALIGHT, ARTNET)
|
||||
if ( (realtimeOverride == REALTIME_OVERRIDE_NONE) // please odd other orrides here if needed
|
||||
if ( (realtimeOverride == REALTIME_OVERRIDE_NONE) // please add other overrides here if needed
|
||||
&&( (realtimeMode == REALTIME_MODE_GENERIC)
|
||||
||(realtimeMode == REALTIME_MODE_E131)
|
||||
||(realtimeMode == REALTIME_MODE_UDP)
|
||||
@@ -1020,7 +1068,7 @@ class AudioReactive : public Usermod {
|
||||
disableSoundProcessing = true;
|
||||
} else {
|
||||
#ifdef WLED_DEBUG
|
||||
if ((disableSoundProcessing == true) && (audioSyncEnabled == 0)) { // we just switched to "disabled"
|
||||
if ((disableSoundProcessing == true) && (audioSyncEnabled == 0) && 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));
|
||||
}
|
||||
@@ -1067,9 +1115,13 @@ class AudioReactive : public Usermod {
|
||||
if (soundAgc) my_magnitude *= multAgc;
|
||||
if (volumeSmth < 1 ) my_magnitude = 0.001f; // noise gate closed - mute
|
||||
|
||||
limitSampleDynamics(); // optional - makes volumeSmth very smooth and fluent
|
||||
}
|
||||
limitSampleDynamics();
|
||||
} // if (!disableSoundProcessing)
|
||||
|
||||
autoResetPeak(); // auto-reset sample peak after strip minShowDelay
|
||||
if (!udpSyncConnected) udpSamplePeak = false; // reset UDP samplePeak while UDP is unconnected
|
||||
|
||||
connectUDPSoundSync(); // ensure we have a connection - if needed
|
||||
|
||||
// UDP Microphone Sync - receive mode
|
||||
if ((audioSyncEnabled & 0x02) && udpSyncConnected) {
|
||||
@@ -1092,7 +1144,7 @@ class AudioReactive : public Usermod {
|
||||
}
|
||||
#endif
|
||||
|
||||
// peak sample from last 5 seconds
|
||||
// Info Page: keep max sample from last 5 seconds
|
||||
if ((millis() - sampleMaxTimer) > CYCLE_SAMPLEMAX) {
|
||||
sampleMaxTimer = millis();
|
||||
maxSample5sec = (0.15 * maxSample5sec) + 0.85 *((soundAgc) ? sampleAgc : sampleAvg); // reset, and start with some smoothing
|
||||
@@ -1100,6 +1152,7 @@ class AudioReactive : public Usermod {
|
||||
} else {
|
||||
if ((sampleAvg >= 1)) maxSample5sec = fmaxf(maxSample5sec, (soundAgc) ? rawSampleAgc : sampleRaw); // follow maximum volume
|
||||
}
|
||||
|
||||
//UDP Microphone Sync - transmit mode
|
||||
if ((audioSyncEnabled & 0x01) && (millis() - lastTime > 20)) {
|
||||
// Only run the transmit code IF we're in Transmit mode
|
||||
@@ -1137,8 +1190,9 @@ class AudioReactive : public Usermod {
|
||||
memset(fftCalc, 0, sizeof(fftCalc));
|
||||
memset(fftAvg, 0, sizeof(fftAvg));
|
||||
memset(fftResult, 0, sizeof(fftResult));
|
||||
for(int i=(init?0:1); i<16; i+=2) fftResult[i] = 16; // make a tiny pattern
|
||||
for(int i=(init?0:1); i<NUM_GEQ_CHANNELS; i+=2) fftResult[i] = 16; // make a tiny pattern
|
||||
inputLevel = 128; // resset level slider to default
|
||||
autoResetPeak();
|
||||
|
||||
if (init && FFT_Task) {
|
||||
vTaskSuspend(FFT_Task); // update is about to begin, disable task to prevent crash
|
||||
@@ -1186,6 +1240,10 @@ class AudioReactive : public Usermod {
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////
|
||||
// Settings and Info Page //
|
||||
////////////////////////////
|
||||
|
||||
/*
|
||||
* addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API.
|
||||
* Creating an "u" object allows you to add custom key/value pairs to the Info section of the WLED web UI.
|
||||
|
||||
@@ -80,7 +80,7 @@ class MultiRelay : public Usermod {
|
||||
void handleOffTimer() {
|
||||
unsigned long now = millis();
|
||||
bool activeRelays = false;
|
||||
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
if (_relay[i].active && _switchTimerStart > 0 && now - _switchTimerStart > (_relay[i].delay*1000)) {
|
||||
if (!_relay[i].external) toggleRelay(i);
|
||||
_relay[i].active = false;
|
||||
@@ -182,7 +182,7 @@ class MultiRelay : public Usermod {
|
||||
*/
|
||||
MultiRelay() {
|
||||
const int8_t defPins[] = {MULTI_RELAY_PINS};
|
||||
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
_relay[i].pin = i<sizeof(defPins) ? defPins[i] : -1;
|
||||
_relay[i].delay = 0;
|
||||
_relay[i].mode = false;
|
||||
@@ -226,7 +226,7 @@ class MultiRelay : public Usermod {
|
||||
|
||||
uint8_t getActiveRelayCount() {
|
||||
uint8_t count = 0;
|
||||
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) if (_relay[i].pin>=0) count++;
|
||||
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++) if (_relay[i].pin>=0) count++;
|
||||
return count;
|
||||
}
|
||||
|
||||
@@ -268,7 +268,7 @@ class MultiRelay : public Usermod {
|
||||
strcat_P(subuf, PSTR("/relay/#"));
|
||||
mqtt->subscribe(subuf, 0);
|
||||
if (HAautodiscovery) publishHomeAssistantAutodiscovery();
|
||||
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
if (_relay[i].pin<0) continue;
|
||||
publishMqtt(i); //publish current state
|
||||
}
|
||||
@@ -276,7 +276,7 @@ class MultiRelay : public Usermod {
|
||||
}
|
||||
|
||||
void publishHomeAssistantAutodiscovery() {
|
||||
for (uint8_t i = 0; i < MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
for (int i = 0; i < MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
char uid[24], json_str[1024], buf[128];
|
||||
size_t payload_size;
|
||||
sprintf_P(uid, PSTR("%s_sw%d"), escapedMac.c_str(), i);
|
||||
@@ -320,7 +320,7 @@ class MultiRelay : public Usermod {
|
||||
*/
|
||||
void setup() {
|
||||
// pins retrieved from cfg.json (readFromConfig()) prior to running setup()
|
||||
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
if (_relay[i].pin<0) continue;
|
||||
if (!pinManager.allocatePin(_relay[i].pin,true, PinOwner::UM_MultiRelay)) {
|
||||
_relay[i].pin = -1; // allocation failed
|
||||
@@ -357,7 +357,7 @@ class MultiRelay : public Usermod {
|
||||
if (_oldMode != offMode) {
|
||||
_oldMode = offMode;
|
||||
_switchTimerStart = millis();
|
||||
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
if (_relay[i].pin>=0 && !_relay[i].external) _relay[i].active = true;
|
||||
}
|
||||
}
|
||||
@@ -382,7 +382,7 @@ class MultiRelay : public Usermod {
|
||||
}
|
||||
|
||||
bool handled = false;
|
||||
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
if (_relay[i].button == b && _relay[i].external) {
|
||||
handled = true;
|
||||
}
|
||||
@@ -402,8 +402,8 @@ class MultiRelay : public Usermod {
|
||||
if (buttonLongPressed[b] == buttonPressedBefore[b]) return handled;
|
||||
|
||||
if (now - buttonPressedTime[b] > WLED_DEBOUNCE_THRESHOLD) { //fire edge event only after 50ms without change (debounce)
|
||||
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
if (_relay[i].pin>=0 && _relay[i].button == b) {
|
||||
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
if (_relay[i].button == b) {
|
||||
switchRelay(i, buttonPressedBefore[b]);
|
||||
buttonLongPressed[b] = buttonPressedBefore[b]; //save the last "long term" switch state
|
||||
}
|
||||
@@ -450,8 +450,8 @@ class MultiRelay : public Usermod {
|
||||
if (buttonWaitTime[b] && now - buttonWaitTime[b] > 350 && !buttonPressedBefore[b]) {
|
||||
buttonWaitTime[b] = 0;
|
||||
//shortPressAction(b); //not exposed
|
||||
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
if (_relay[i].pin>=0 && _relay[i].button == b) {
|
||||
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
if (_relay[i].button == b) {
|
||||
toggleRelay(i);
|
||||
}
|
||||
}
|
||||
@@ -473,7 +473,7 @@ class MultiRelay : public Usermod {
|
||||
infoArr.add(F(" relays"));
|
||||
|
||||
String uiDomString;
|
||||
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
if (_relay[i].pin<0 || !_relay[i].external) continue;
|
||||
uiDomString = F("Relay "); uiDomString += i;
|
||||
JsonArray infoArr = user.createNestedArray(uiDomString); // timer value
|
||||
@@ -507,7 +507,7 @@ class MultiRelay : public Usermod {
|
||||
}
|
||||
#if MULTI_RELAY_MAX_RELAYS > 1
|
||||
JsonArray rel_arr = multiRelay.createNestedArray(F("relays"));
|
||||
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
if (_relay[i].pin < 0) continue;
|
||||
JsonObject relay = rel_arr.createNestedObject();
|
||||
relay[FPSTR(_relay_str)] = i;
|
||||
@@ -527,14 +527,24 @@ class MultiRelay : public Usermod {
|
||||
if (!initDone || !enabled) return; // prevent crash on boot applyPreset()
|
||||
JsonObject usermod = root[FPSTR(_name)];
|
||||
if (!usermod.isNull()) {
|
||||
if (usermod["on"].is<bool>() && usermod[FPSTR(_relay_str)].is<int>() && usermod[FPSTR(_relay_str)].as<int>()>=0) {
|
||||
switchRelay(usermod[FPSTR(_relay_str)].as<int>(), usermod["on"].as<bool>());
|
||||
if (usermod[FPSTR(_relay_str)].is<int>() && usermod[FPSTR(_relay_str)].as<int>()>=0) {
|
||||
int rly = usermod[FPSTR(_relay_str)].as<int>();
|
||||
if (usermod["on"].is<bool>()) {
|
||||
switchRelay(rly, usermod["on"].as<bool>());
|
||||
} else if (usermod["on"].is<const char*>() && usermod["on"].as<const char*>()[0] == 't') {
|
||||
toggleRelay(rly);
|
||||
}
|
||||
}
|
||||
} else if (root[FPSTR(_name)].is<JsonArray>()) {
|
||||
JsonArray relays = root[FPSTR(_name)].as<JsonArray>();
|
||||
for (JsonVariant r : relays) {
|
||||
if (r["on"].is<bool>() && r[FPSTR(_relay_str)].is<int>() && r[FPSTR(_relay_str)].as<int>()>=0) {
|
||||
switchRelay(r[FPSTR(_relay_str)].as<int>(), r["on"].as<bool>());
|
||||
if (r[FPSTR(_relay_str)].is<int>() && r[FPSTR(_relay_str)].as<int>()>=0) {
|
||||
int rly = r[FPSTR(_relay_str)].as<int>();
|
||||
if (r["on"].is<bool>()) {
|
||||
switchRelay(rly, r["on"].as<bool>());
|
||||
} else if (r["on"].is<const char*>() && r["on"].as<const char*>()[0] == 't') {
|
||||
toggleRelay(rly);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -548,7 +558,7 @@ class MultiRelay : public Usermod {
|
||||
|
||||
top[FPSTR(_enabled)] = enabled;
|
||||
top[FPSTR(_broadcast)] = periodicBroadcastSec;
|
||||
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
String parName = FPSTR(_relay_str); parName += '-'; parName += i;
|
||||
JsonObject relay = top.createNestedObject(parName);
|
||||
relay["pin"] = _relay[i].pin;
|
||||
@@ -582,7 +592,7 @@ class MultiRelay : public Usermod {
|
||||
periodicBroadcastSec = min(900,max(0,(int)periodicBroadcastSec));
|
||||
HAautodiscovery = top[FPSTR(_HAautodiscovery)] | HAautodiscovery;
|
||||
|
||||
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
String parName = FPSTR(_relay_str); parName += '-'; parName += i;
|
||||
oldPin[i] = _relay[i].pin;
|
||||
_relay[i].pin = top[parName]["pin"] | _relay[i].pin;
|
||||
@@ -606,12 +616,12 @@ class MultiRelay : public Usermod {
|
||||
DEBUG_PRINTLN(F(" config loaded."));
|
||||
} else {
|
||||
// deallocate all pins 1st
|
||||
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++)
|
||||
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++)
|
||||
if (oldPin[i]>=0) {
|
||||
pinManager.deallocatePin(oldPin[i], PinOwner::UM_MultiRelay);
|
||||
}
|
||||
// allocate new pins
|
||||
for (uint8_t i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
for (int i=0; i<MULTI_RELAY_MAX_RELAYS; i++) {
|
||||
if (_relay[i].pin>=0 && pinManager.allocatePin(_relay[i].pin, true, PinOwner::UM_MultiRelay)) {
|
||||
if (!_relay[i].external) {
|
||||
_relay[i].state = !offMode;
|
||||
|
||||
@@ -402,10 +402,12 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
else u8x8 = (U8X8 *) new U8X8_SSD1305_128X64_ADAFRUIT_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA
|
||||
break;
|
||||
case SSD1306_SPI:
|
||||
// u8x8 uses global SPI variable that is attached to VSPI bus on ESP32
|
||||
if (!isHW) u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_4W_SW_SPI(ioPin[0], ioPin[1], ioPin[2], ioPin[3], ioPin[4]);
|
||||
else u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_4W_HW_SPI(ioPin[2], ioPin[3], ioPin[4]); // Pins are cs, dc, reset
|
||||
break;
|
||||
case SSD1306_SPI64:
|
||||
// u8x8 uses global SPI variable that is attached to VSPI bus on ESP32
|
||||
if (!isHW) u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_4W_SW_SPI(ioPin[0], ioPin[1], ioPin[2], ioPin[3], ioPin[4]);
|
||||
else u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_4W_HW_SPI(ioPin[2], ioPin[3], ioPin[4]); // Pins are cs, dc, reset
|
||||
break;
|
||||
@@ -1052,10 +1054,25 @@ class FourLineDisplayUsermod : public Usermod {
|
||||
* I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings!
|
||||
*/
|
||||
void addToConfig(JsonObject& root) {
|
||||
// determine if we are using global HW pins (data & clock)
|
||||
int8_t hw_dta, hw_clk;
|
||||
if ((type == SSD1306_SPI || type == SSD1306_SPI64)) {
|
||||
hw_clk = spi_sclk<0 ? HW_PIN_CLOCKSPI : spi_sclk;
|
||||
hw_dta = spi_mosi<0 ? HW_PIN_DATASPI : spi_mosi;
|
||||
} else {
|
||||
hw_clk = i2c_scl<0 ? HW_PIN_SCL : i2c_scl;
|
||||
hw_dta = i2c_sda<0 ? HW_PIN_SDA : i2c_sda;
|
||||
}
|
||||
|
||||
JsonObject top = root.createNestedObject(FPSTR(_name));
|
||||
top[FPSTR(_enabled)] = enabled;
|
||||
|
||||
JsonArray io_pin = top.createNestedArray("pin");
|
||||
for (byte i=0; i<5; i++) io_pin.add(ioPin[i]);
|
||||
for (int i=0; i<5; i++) {
|
||||
if (i==0 && ioPin[i]==hw_clk) io_pin.add(-1); // do not store global HW pin
|
||||
else if (i==1 && ioPin[i]==hw_dta) io_pin.add(-1); // do not store global HW pin
|
||||
else io_pin.add(ioPin[i]);
|
||||
}
|
||||
top["type"] = type;
|
||||
top[FPSTR(_flip)] = (bool) flip;
|
||||
top[FPSTR(_contrast)] = contrast;
|
||||
|
||||
Reference in New Issue
Block a user