From 96d497a5cd8bd8739eb5fc405571aa053c173ec6 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Sat, 6 Aug 2022 16:48:26 +0200 Subject: [PATCH] AR: optimize sound sync, and code improvements UDP audio sync: introduced new header version, because the new struct (without myvals[]) is not compatible with the previous struct. Also optimized structure size. UDP audio sync: sender decides is AGC or non-AGC samples are transmitted. getsamples: move volumeSmth/volumeRaw code out of AGC core function. --- usermods/audioreactive/audio_reactive.h | 131 +++++++++++++++--------- 1 file changed, 83 insertions(+), 48 deletions(-) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index 23737a418..3db5d4f2a 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -58,12 +58,12 @@ static uint8_t audioSyncEnabled = 0; // bit field: bit 0 - send, bit 1 // #define AGC_NUM_PRESETS 3 // AGC presets: normal, vivid, lazy const double agcSampleDecay[AGC_NUM_PRESETS] = { 0.9994f, 0.9985f, 0.9997f}; // decay factor for sampleMax, in case the current sample is below sampleMax -const float agcZoneLow[AGC_NUM_PRESETS] = { 32, 28, 36}; // low volume emergency zone -const float agcZoneHigh[AGC_NUM_PRESETS] = { 240, 240, 248}; // high volume emergency zone -const float agcZoneStop[AGC_NUM_PRESETS] = { 336, 448, 304}; // disable AGC integrator if we get above this level -const float agcTarget0[AGC_NUM_PRESETS] = { 112, 144, 164}; // first AGC setPoint -> between 40% and 65% -const float agcTarget0Up[AGC_NUM_PRESETS] = { 88, 64, 116}; // setpoint switching value (a poor man's bang-bang) -const float agcTarget1[AGC_NUM_PRESETS] = { 220, 224, 216}; // second AGC setPoint -> around 85% +const float agcZoneLow[AGC_NUM_PRESETS] = { 32, 28, 36}; // low volume emergency zone +const float agcZoneHigh[AGC_NUM_PRESETS] = { 240, 240, 248}; // high volume emergency zone +const float agcZoneStop[AGC_NUM_PRESETS] = { 336, 448, 304}; // disable AGC integrator if we get above this level +const float agcTarget0[AGC_NUM_PRESETS] = { 112, 144, 164}; // first AGC setPoint -> between 40% and 65% +const float agcTarget0Up[AGC_NUM_PRESETS] = { 88, 64, 116}; // setpoint switching value (a poor man's bang-bang) +const float agcTarget1[AGC_NUM_PRESETS] = { 220, 224, 216}; // second AGC setPoint -> around 85% const double agcFollowFast[AGC_NUM_PRESETS] = { 1/192.f, 1/128.f, 1/256.f}; // quickly follow setpoint - ~0.15 sec const double agcFollowSlow[AGC_NUM_PRESETS] = {1/6144.f,1/4096.f,1/8192.f}; // slowly follow setpoint - ~2-15 secs const double agcControlKp[AGC_NUM_PRESETS] = { 0.6f, 1.5f, 0.65f}; // AGC - PI control, proportional gain parameter @@ -308,7 +308,7 @@ void FFTcode(void * parameter) fftCalc[ 4] = fftAddAvg(9,12); // 180 - 260 fftCalc[ 5] = fftAddAvg(12,16); // 240 - 340 fftCalc[ 6] = fftAddAvg(16,21); // 320 - 440 - fftCalc[ 7] = fftAddAvg(21,28); // 420 - 600 + fftCalc[ 7] = fftAddAvg(21,29); // 420 - 600 fftCalc[ 8] = fftAddAvg(29,37); // 580 - 760 fftCalc[ 9] = fftAddAvg(37,48); // 740 - 980 fftCalc[10] = fftAddAvg(48,64); // 960 - 1300 @@ -401,15 +401,29 @@ class AudioReactive : public Usermod { int8_t mclkPin = MLCK_PIN; #endif + // new "V2" audiosync struct - 40 Bytes struct audioSyncPacket { - char header[6]; - int sampleAgc; // 04 Bytes - int sampleRaw; // 04 Bytes - float sampleAvg; // 04 Bytes - bool samplePeak; // 01 Bytes + char header[6]; // 06 Bytes + 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 fftResult[16]; // 16 Bytes - double FFT_Magnitude; // 08 Bytes - double FFT_MajorPeak; // 08 Bytes + float FFT_Magnitude; // 04 Bytes + float FFT_MajorPeak; // 04 Bytes + }; + + // old "V1" audiosync struct - 83 Bytes - for backwards compatibility + struct audioSyncPacket_v1 { + char header[6]; // 06 Bytes + uint8_t myVals[32]; // 32 Bytes + int sampleAgc; // 04 Bytes + int sampleRaw; // 04 Bytes + float sampleAvg; // 04 Bytes + bool samplePeak; // 01 Bytes + uint8_t fftResult[16]; // 16 Bytes + double FFT_Magnitude; // 08 Bytes + double FFT_MajorPeak; // 08 Bytes }; WiFiUDP fftUdp; @@ -460,6 +474,7 @@ class AudioReactive : public Usermod { static const char _analogmic[]; static const char _digitalmic[]; static const char UDP_SYNC_HEADER[]; + static const char UDP_SYNC_HEADER_v1[]; float my_magnitude; @@ -642,14 +657,6 @@ class AudioReactive : public Usermod { //if (userVar0 > 255) userVar0 = 255; last_soundAgc = soundAgc; - - volumeSmth = (soundAgc) ? sampleAgc:sampleAvg; - volumeRaw = (soundAgc) ? rawSampleAgc : sampleRaw; - - my_magnitude = FFT_Magnitude; // / 16.0f, 8.0f, 4.0f done in effects - if (soundAgc) my_magnitude *= multAgc; - if (volumeSmth < 1 ) my_magnitude = 0.001f; // noise gate closed - mute - } // agcAvg() @@ -749,17 +756,17 @@ class AudioReactive : public Usermod { audioSyncPacket transmitData; strncpy_P(transmitData.header, PSTR(UDP_SYNC_HEADER), 6); - transmitData.sampleAgc = sampleAgc; - transmitData.sampleRaw = sampleRaw; - transmitData.sampleAvg = sampleAvg; - transmitData.samplePeak = udpSamplePeak; - udpSamplePeak = 0; // Reset udpSamplePeak after we've transmitted it + transmitData.sampleRaw = volumeRaw; + transmitData.sampleSmth = volumeSmth; + transmitData.samplePeak = udpSamplePeak ? 1:0; + udpSamplePeak = false; // Reset udpSamplePeak after we've transmitted it + transmitData.reserved1 = 0; for (int i = 0; i < 16; i++) { transmitData.fftResult[i] = (uint8_t)constrain(fftResult[i], 0, 254); } - transmitData.FFT_Magnitude = FFT_Magnitude; + transmitData.FFT_Magnitude = my_magnitude; transmitData.FFT_MajorPeak = FFT_MajorPeak; fftUdp.beginMulticastPacket(); @@ -780,7 +787,7 @@ class AudioReactive : public Usermod { //DEBUGSR_PRINTLN("Checking for UDP Microphone Packet"); size_t packetSize = fftUdp.parsePacket(); - if (packetSize) { + if (packetSize > 5) { //DEBUGSR_PRINTLN("Received UDP Sync Packet"); uint8_t fftBuff[packetSize]; fftUdp.read(fftBuff, packetSize); @@ -789,19 +796,35 @@ class AudioReactive : public Usermod { if (packetSize == sizeof(audioSyncPacket) && !(isValidUdpSyncVersion((const char *)fftBuff))) { audioSyncPacket *receivedPacket = reinterpret_cast(fftBuff); - sampleAgc = receivedPacket->sampleAgc; - rawSampleAgc = receivedPacket->sampleAgc; - sampleRaw = receivedPacket->sampleRaw; - sampleAvg = receivedPacket->sampleAvg; + volumeSmth = receivedPacket->sampleSmth; + volumeRaw = receivedPacket->sampleRaw; + sampleRaw = volumeRaw; + sampleAvg = volumeSmth; + rawSampleAgc = volumeRaw; + 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; // Only change samplePeak IF it's currently false. // If it's true already, then the animation still needs to respond. - if (!samplePeak) samplePeak = receivedPacket->samplePeak; + if (!samplePeak) { + samplePeak = receivedPacket->samplePeak >0 ? true:false; + if (samplePeak) timeOfPeak = millis(); + //userVar1 = samplePeak; + } //These values are only available on the ESP32 for (int i = 0; i < 16; i++) fftResult[i] = receivedPacket->fftResult[i]; - FFT_Magnitude = receivedPacket->FFT_Magnitude; + my_magnitude = receivedPacket->FFT_Magnitude; + FFT_Magnitude = my_magnitude; FFT_MajorPeak = receivedPacket->FFT_MajorPeak; //DEBUGSR_PRINTLN("Finished parsing UDP Sync Packet"); } @@ -938,7 +961,7 @@ class AudioReactive : public Usermod { } // We cannot wait indefinitely before processing audio data //if (!enabled || strip.isUpdating()) return; - if (strip.isUpdating() && (millis() - lastUMRun < 12)) return; // be nice, but not too nice + 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 @@ -992,6 +1015,17 @@ class AudioReactive : public Usermod { getSample(); // Sample the microphone agcAvg(); // Calculated the PI adjusted value as sampleAvg + + // update samples for effects (raw, smooth) + volumeSmth = (soundAgc) ? sampleAgc : sampleAvg; + volumeRaw = (soundAgc) ? rawSampleAgc: sampleRaw; + // update FFTMagnitude, taking into account AGC amplification + my_magnitude = FFT_Magnitude; // / 16.0f, 8.0f, 4.0f done in effects + if (soundAgc) my_magnitude *= multAgc; + if (volumeSmth < 1 ) my_magnitude = 0.001f; // noise gate closed - mute + + + // update UI uint8_t knownMode = strip.getFirstSelectedSeg().mode; // 1st selected segment is more appropriate than main segment if (lastMode != knownMode) { // only execute if mode changes @@ -1032,22 +1066,22 @@ class AudioReactive : public Usermod { last_user_inputLevel = new_user_inputLevel; } } - - #if defined(MIC_LOGGER) || defined(MIC_SAMPLING_LOG) || defined(FFT_SAMPLING_LOG) - EVERY_N_MILLIS(20) { - logAudio(); - } - #endif } // Begin UDP Microphone Sync - if ((audioSyncEnabled & 0x02) && millis() - lastTime > delayMs) // Only run the audio listener code if we're in Receive mode + if ((audioSyncEnabled & 0x02) && millis() - lastTime > delayMs) { // Only run the audio listener code if we're in Receive mode receiveAudioData(); + lastTime = millis(); + } - if (millis() - lastTime > 20) { - if (audioSyncEnabled & 0x01) { // Only run the transmit code IF we're in Transmit mode - transmitAudioData(); - } + #if defined(MIC_LOGGER) || defined(MIC_SAMPLING_LOG) || defined(FFT_SAMPLING_LOG) + EVERY_N_MILLIS(20) { + logAudio(); + } + #endif + + if ((audioSyncEnabled & 0x01) && millis() - lastTime > 20) { // Only run the transmit code IF we're in Transmit mode + transmitAudioData(); lastTime = millis(); } } @@ -1350,4 +1384,5 @@ const char AudioReactive::_enabled[] PROGMEM = "enabled"; const char AudioReactive::_inputLvl[] PROGMEM = "inputLevel"; const char AudioReactive::_analogmic[] PROGMEM = "analogmic"; const char AudioReactive::_digitalmic[] PROGMEM = "digitalmic"; -const char AudioReactive::UDP_SYNC_HEADER[] PROGMEM = "00001"; +const char AudioReactive::UDP_SYNC_HEADER[] PROGMEM = "00002"; // new sync header version, as format no longer compatible with previous structure +const char AudioReactive::UDP_SYNC_HEADER_v1[] PROGMEM = "00001"; // old sync header version - need to add backwards-compatibility feature