From d2fc1f7bf9c3b6d4ebaf9736ed96e4b733b371a8 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 5 Dec 2023 23:46:01 +0100 Subject: [PATCH] experimental: Souns Sync "receive or local" mode new "Receive or Local" mode: if UDP sound is missing or interrupted for too long, switch back to local audio input. UDP sound resumes when a fresh packet is received again. --> still needs testing, and even more regression testing. --- usermods/audioreactive/audio_reactive.h | 82 +++++++++++++++++-------- wled00/wled.h | 2 +- 2 files changed, 58 insertions(+), 26 deletions(-) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index 72ab6c47..acef3adc 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -91,11 +91,12 @@ #define PLOT_FLUSH() #endif -// audiosync modes -#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) +// 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 = AUDIOSYNC_NONE; // bit field: bit 0 - send, bit 1 - receive, bit 2 - use local if not receiving @@ -925,7 +926,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 } } @@ -1493,7 +1494,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; @@ -1898,7 +1899,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 @@ -1941,6 +1942,17 @@ class AudioReactive : public Usermod { // We cannot wait indefinitely before processing audio data 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 &&( (realtimeMode == REALTIME_MODE_GENERIC) @@ -1950,27 +1962,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 == AUDIOSYNC_REC) disableSoundProcessing = true; // make sure everything is disabled IF in audio Receive 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 @@ -1983,7 +2000,7 @@ class AudioReactive : public Usermod { #endif // Only run the sampling code IF we're not in Receive mode or realtime mode - if (!(audioSyncEnabled & AUDIOSYNC_REC) && !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 @@ -1993,7 +2010,7 @@ 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)) { + //if ((userloopDelay > /*23*/ 65) && !disableSoundProcessing && (audioSyncEnabled == AUDIOSYNC_NONE)) { //DEBUG_PRINTF("[AR userLoop] hiccup detected -> was inactive for last %d millis!\n", userloopDelay); //} #endif @@ -2046,17 +2063,22 @@ class AudioReactive : public Usermod { 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 - limitGEQDynamics(have_new_sample); // WLEDMM experimental: smooth FFT (GEQ) samples + 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; } @@ -2262,7 +2284,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) { @@ -2289,11 +2319,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 & AUDIOSYNC_REC) { + 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")); @@ -2358,14 +2388,16 @@ class AudioReactive : public Usermod { if (audioSyncEnabled) { if (audioSyncEnabled & AUDIOSYNC_SEND) { infoArr.add(F("send mode")); - if ((udpSyncConnected) && (millis() - lastTime < 2500)) infoArr.add(F(" v2")); - } else if (audioSyncEnabled & AUDIOSYNC_REC) { + 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(" (unconnected)"); - 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")); } diff --git a/wled00/wled.h b/wled00/wled.h index 3b0cffe6..032a3a4f 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -8,7 +8,7 @@ */ // version code in format yymmddb (b = daily build) -#define VERSION 2311290 +#define VERSION 2312050 //WLEDMM + Moustachauve/Wled-Native // You can define custom product info from build flags.