├── README.md ├── WLED-sync.cpp ├── WLED-sync.h ├── examples ├── E131DMX-WLED-Sync │ └── E131DMX-WLED-Sync.ino ├── WLED-MSGEQ7-Sender │ ├── README.md │ └── WLED-MSGEQ7-Sender.ino └── WLED-Sync-Receive │ └── WLED-Sync-Receive.ino ├── library.json └── library.properties /README.md: -------------------------------------------------------------------------------- 1 | # WLED-sync 2 | Library to create WLED compatible projects that sync their audio 3 | 4 | Got an existing WLED 0.14 with AudioRective UserMod or WLED-SR setup, but want to pass the audio data to your own projects? 5 | Have an existing project with FFT audio analysis that you want to add WLED devices to? 6 | 7 | Then this is the library for you, see two examples 8 | 9 | If you do use this libray in your project, I would love to hear from you, please email will - at - netmindz.net 10 | 11 | ## See Also 12 | 13 | Stream WLED audio data from your PC with https://github.com/Victoare/SR-WLED-audio-server-win 14 | 15 | Use WLED audio data with [Chataigne](http://benjamin.kuperberg.fr/chataigne/en#home) (Artist-friendly Modular Machine for Art and Technology) using https://github.com/zak-45/WLEDAudioSync-Chataigne-Module 16 | -------------------------------------------------------------------------------- /WLED-sync.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @brief WLED Audio Sync 3 | * Author: Will Tatam 4 | * https://creativecommons.org/licenses/by/4.0/ 5 | */ 6 | 7 | #include "WLED-sync.h" 8 | #if defined(ESP8266) // ESP8266 9 | #include 10 | #endif 11 | 12 | #define UDPSOUND_MAX_PACKET 96 // max packet size for audiosync, with a bit of "headroom" 13 | uint8_t fftUdpBuffer[UDPSOUND_MAX_PACKET+1] = { 0 }; // static buffer for receiving 14 | 15 | static bool isValidUdpSyncVersion(const char *header) { 16 | return strncmp_P(header, PSTR(UDP_SYNC_HEADER), 6) == 0; 17 | } 18 | static bool isValidUdpSyncVersion_v1(const char *header) { 19 | return strncmp_P(header, PSTR(UDP_SYNC_HEADER_v1), 6) == 0; 20 | } 21 | 22 | WLEDSync::WLEDSync() { 23 | } 24 | 25 | void WLEDSync::begin() { 26 | #ifndef ESP8266 27 | udpSyncConnected = fftUdp.beginMulticast(IPAddress(239, 0, 0, 1), UDP_SYNC_PORT); 28 | #else 29 | udpSyncConnected = fftUdp.beginMulticast(WiFi.localIP(), IPAddress(239, 0, 0, 1), UDP_SYNC_PORT); 30 | #endif 31 | lastPacketTime = 0; 32 | } 33 | 34 | void WLEDSync::send(audioSyncPacket transmitData) { 35 | #ifndef ESP8266 36 | fftUdp.beginMulticastPacket(); 37 | #else 38 | fftUdp.beginPacketMulticast(IPAddress(239, 0, 0, 1), UDP_SYNC_PORT, WiFi.localIP()); 39 | #endif 40 | strncpy_P(transmitData.header, UDP_SYNC_HEADER, 6); 41 | fftUdp.write(reinterpret_cast(&transmitData), sizeof(transmitData)); 42 | fftUdp.endPacket(); 43 | lastPacketTime = millis(); 44 | } 45 | 46 | bool WLEDSync::read() // check & process new data. return TRUE in case that new audio data was received. 47 | { 48 | if (!udpSyncConnected) return false; 49 | bool haveFreshData = false; 50 | 51 | size_t packetSize = fftUdp.parsePacket(); 52 | if ((packetSize > 0) && ((packetSize < 5) || (packetSize > UDPSOUND_MAX_PACKET))) fftUdp.flush(); // discard invalid packets (too small or too big) 53 | if ((packetSize > 5) && (packetSize <= UDPSOUND_MAX_PACKET)) { 54 | //DEBUGSR_PRINTLN("Received UDP Sync Packet"); 55 | fftUdp.read(fftUdpBuffer, packetSize); 56 | sourceIP = fftUdp.remoteIP(); 57 | 58 | // VERIFY THAT THIS IS A COMPATIBLE PACKET 59 | if (packetSize == sizeof(audioSyncPacket) && (isValidUdpSyncVersion((const char *)fftUdpBuffer))) { 60 | decodeAudioData(packetSize, fftUdpBuffer); 61 | //DEBUGSR_PRINTLN("Finished parsing UDP Sync Packet v2"); 62 | haveFreshData = true; 63 | receivedFormat = 2; 64 | lastPacketTime = millis(); 65 | } else { 66 | if (packetSize == sizeof(audioSyncPacket_v1) && (isValidUdpSyncVersion_v1((const char *)fftUdpBuffer))) { 67 | decodeAudioData_v1(packetSize, fftUdpBuffer); 68 | //DEBUGSR_PRINTLN("Finished parsing UDP Sync Packet v1"); 69 | haveFreshData = true; 70 | receivedFormat = 1; 71 | lastPacketTime = millis(); 72 | } 73 | else { 74 | receivedFormat = 0; // unknown format 75 | } 76 | } 77 | } 78 | return haveFreshData; 79 | } 80 | 81 | void WLEDSync::decodeAudioData_v1(int packetSize, uint8_t *fftBuff) { 82 | audioSyncPacket_v1 *receivedPacket = reinterpret_cast(fftBuff); 83 | // update samples for effects 84 | volumeSmth = fmaxf(receivedPacket->sampleAgc, 0.0f); 85 | volumeRaw = volumeSmth; // V1 format does not have "raw" AGC sample 86 | // update internal samples 87 | sampleRaw = fmaxf(receivedPacket->sampleRaw, 0.0f); 88 | sampleAvg = fmaxf(receivedPacket->sampleAvg, 0.0f);; 89 | sampleAgc = volumeSmth; 90 | rawSampleAgc = volumeRaw; 91 | multAgc = 1.0f; 92 | // Only change samplePeak IF it's currently false. 93 | // If it's true already, then the animation still needs to respond. 94 | autoResetPeak(); 95 | if (!samplePeak) { 96 | samplePeak = receivedPacket->samplePeak >0 ? true:false; 97 | if (samplePeak) timeOfPeak = millis(); 98 | //userVar1 = samplePeak; 99 | } 100 | //These values are only available on the ESP32 101 | for (int i = 0; i < NUM_GEQ_CHANNELS; i++) fftResult[i] = receivedPacket->fftResult[i]; 102 | my_magnitude = fmaxf(receivedPacket->FFT_Magnitude, 0.0); 103 | FFT_Magnitude = my_magnitude; 104 | FFT_MajorPeak = constrain(receivedPacket->FFT_MajorPeak, 1.0, 11025.0); // restrict value to range expected by effects 105 | } 106 | 107 | void WLEDSync::decodeAudioData(int packetSize, uint8_t *fftBuff) { 108 | audioSyncPacket *receivedPacket = reinterpret_cast(fftBuff); 109 | // update samples for effects 110 | volumeSmth = fmaxf(receivedPacket->sampleSmth, 0.0f); 111 | volumeRaw = fmaxf(receivedPacket->sampleRaw, 0.0f); 112 | // update internal samples 113 | sampleRaw = volumeRaw; 114 | sampleAvg = volumeSmth; 115 | rawSampleAgc = volumeRaw; 116 | sampleAgc = volumeSmth; 117 | multAgc = 1.0f; 118 | // Only change samplePeak IF it's currently false. 119 | // If it's true already, then the animation still needs to respond. 120 | autoResetPeak(); 121 | if (!samplePeak) { 122 | samplePeak = receivedPacket->samplePeak >0 ? true:false; 123 | if (samplePeak) timeOfPeak = millis(); 124 | //userVar1 = samplePeak; 125 | } 126 | //These values are only available on the ESP32 127 | for (int i = 0; i < NUM_GEQ_CHANNELS; i++) { 128 | fftResult[i] = receivedPacket->fftResult[i]; 129 | // Serial.printf("%u ", fftResult[i]); 130 | } 131 | // Serial.println(" 100"); 132 | 133 | my_magnitude = fmaxf(receivedPacket->FFT_Magnitude, 0.0f); 134 | FFT_Magnitude = my_magnitude; 135 | FFT_MajorPeak = constrain(receivedPacket->FFT_MajorPeak, 1.0f, 11025.0f); // restrict value to range expected by effects 136 | } 137 | 138 | void WLEDSync::autoResetPeak(void) { 139 | uint16_t MinShowDelay = 50; // MAX(50, strip.getMinShowDelay()); // Fixes private class variable compiler error. Unsure if this is the correct way of fixing the root problem. -THATDONFC 140 | if (millis() - timeOfPeak > MinShowDelay) { // Auto-reset of samplePeak after a complete frame has passed. 141 | samplePeak = false; 142 | if (audioSyncEnabled == 0) udpSamplePeak = false; // this is normally reset by transmitAudioData 143 | } 144 | } 145 | 146 | -------------------------------------------------------------------------------- /WLED-sync.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @brief WLED Audio Sync 3 | * Author: Will Tatam 4 | * https://creativecommons.org/licenses/by/4.0/ 5 | */ 6 | #ifndef WLEDSYNC_H_ 7 | #define WLEDSYNC_H_ 8 | 9 | #include "Arduino.h" 10 | #include 11 | 12 | #define UDP_SYNC_HEADER_v1 "00001" 13 | #define UDP_SYNC_HEADER "00002" 14 | #define UDP_SYNC_PORT 11988 15 | #define NUM_GEQ_CHANNELS 16 // number of frequency channels. Don't change !! 16 | 17 | struct __attribute__ ((packed)) audioSyncPacket { // WLEDMM "packed" ensures that there are no additional gaps 18 | char header[6]; // 06 Bytes offset 0 19 | uint8_t gap1[2]; // gap added by compiler: 02 Bytes, offset 6 20 | float sampleRaw; // 04 Bytes offset 8 - either "sampleRaw" or "rawSampleAgc" depending on soundAgc setting 21 | float sampleSmth; // 04 Bytes offset 12 - either "sampleAvg" or "sampleAgc" depending on soundAgc setting 22 | uint8_t samplePeak; // 01 Bytes offset 16 - 0 no peak; >=1 peak detected. In future, this will also provide peak Magnitude 23 | uint8_t frameCounter; // 01 Bytes offset 17 - track duplicate/out of order packets 24 | uint8_t fftResult[16]; // 16 Bytes offset 18 25 | uint8_t gap2[2]; // gap added by compiler: 02 Bytes, offset 34 26 | float FFT_Magnitude; // 04 Bytes offset 36 27 | float FFT_MajorPeak; // 04 Bytes offset 40 28 | }; 29 | 30 | // old "V1" audiosync struct - 83 Bytes - for backwards compatibility 31 | struct audioSyncPacket_v1 { 32 | char header[6]; // 06 Bytes 33 | uint8_t myVals[32]; // 32 Bytes 34 | int sampleAgc; // 04 Bytes 35 | int sampleRaw; // 04 Bytes 36 | float sampleAvg; // 04 Bytes 37 | bool samplePeak; // 01 Bytes 38 | uint8_t fftResult[16]; // 16 Bytes 39 | double FFT_Magnitude; // 08 Bytes 40 | double FFT_MajorPeak; // 08 Bytes 41 | }; 42 | 43 | class WLEDSync { 44 | 45 | private: 46 | WiFiUDP fftUdp; 47 | bool udpSyncConnected; 48 | 49 | bool audioSyncEnabled = true; // not actually an option, but kept here to keep code in sync with UserMod 50 | 51 | // peak detection 52 | bool samplePeak = false; // Boolean flag for peak - used in effects. Responding routine may reset this flag. Auto-reset after strip.getMinShowDelay() 53 | unsigned long timeOfPeak = 0; // time of last sample peak detection. 54 | bool udpSamplePeak = false; // Boolean flag for peak. Set at the same tiem as samplePeak, but reset by transmitAudioData 55 | 56 | 57 | void decodeAudioData(int packetSize, uint8_t *fftBuff); 58 | void decodeAudioData_v1(int packetSize, uint8_t *fftBuff); 59 | void autoResetPeak(void); 60 | 61 | public: 62 | int receivedFormat = -1; // last received UDP sound sync format - 0=none, 1=v1 (0.13.x), 2=v2 (0.14.x) 63 | unsigned long lastPacketTime = 0; 64 | IPAddress sourceIP; 65 | uint8_t fftResult[NUM_GEQ_CHANNELS]= {0};// Our calculated freq. channel result table to be used by effects 66 | float FFT_MajorPeak = 1.0f; // FFT: strongest (peak) frequency 67 | float FFT_Magnitude = 0.0f; // FFT: volume (magnitude) of peak frequency 68 | // variables used in effects 69 | float volumeSmth = 0.0f; // either sampleAvg or sampleAgc depending on soundAgc; smoothed sample 70 | int16_t volumeRaw = 0; // either sampleRaw or rawSampleAgc depending on soundAgc 71 | int16_t sampleRaw = 0; // Current sample. Must only be updated ONCE!!! (amplified mic value by sampleGain and inputLevel) 72 | float my_magnitude =0.0f; // FFT_Magnitude, scaled by multAgc 73 | int16_t rawSampleAgc = 0; // not smoothed AGC sample 74 | float sampleAgc = 0.0f; // Smoothed AGC sample 75 | float multAgc = 1.0f; // sample * multAgc = sampleAgc. Our AGC multiplier 76 | float sampleAvg = 0.0f; // Smoothed Average sample - sampleAvg < 1 means "quiet" (simple noise gate) 77 | 78 | WLEDSync(); 79 | 80 | void begin(); 81 | void send(audioSyncPacket transmitData); 82 | bool read(); 83 | }; 84 | #endif 85 | -------------------------------------------------------------------------------- /examples/E131DMX-WLED-Sync/E131DMX-WLED-Sync.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | #include // https://github.com/netmindz/WLED-sync 7 | 8 | 9 | #define UNIVERSE 1 // First DMX Universe to listen for 10 | #define UNIVERSE_COUNT 1 // Total number of Universes to listen for, starting at UNIVERSE 11 | #define CHANNELS 400 12 | 13 | ESPAsyncE131 e131(UNIVERSE_COUNT); 14 | 15 | WLEDSync sync; 16 | uint8_t fftResult[NUM_GEQ_CHANNELS]= {0}; 17 | 18 | 19 | #include "wifi.h" 20 | const char ssid[] = SECRET_SSID; 21 | const char passphrase[] = SECRET_PSK; 22 | 23 | DMXESPSerial dmx; 24 | 25 | void setup() { 26 | Serial.begin(115200); 27 | 28 | // Make sure you're in station mode 29 | WiFi.mode(WIFI_STA); 30 | 31 | Serial.println(""); 32 | Serial.print(F("Connecting to ")); 33 | Serial.println(ssid); 34 | 35 | if (passphrase != NULL) 36 | WiFi.begin(ssid, passphrase); 37 | else 38 | WiFi.begin(ssid); 39 | 40 | Serial.print("Waiting on wifi "); 41 | while (WiFi.status() != WL_CONNECTED) { 42 | delay(500); 43 | Serial.print("w"); 44 | } 45 | Serial.println("\nDone"); 46 | Serial.print("IP address: "); 47 | Serial.println(WiFi.localIP()); 48 | delay(2000); 49 | // Choose one to begin listening for E1.31 data 50 | //if (e131.begin(E131_UNICAST)) { 51 | if (e131.begin(E131_MULTICAST, UNIVERSE, UNIVERSE_COUNT)) { // Listen via Multicast 52 | Serial.println(F("Listening for data...")); 53 | } 54 | else { 55 | Serial.println(F("*** e131.begin failed ***")); 56 | } 57 | dmx.init(512); 58 | 59 | sync.begin(); 60 | 61 | pinMode(LED_BUILTIN, OUTPUT); // Initialize the LED_BUILTIN pin as an output 62 | } 63 | 64 | int led = 0; 65 | void loop() { 66 | if (sync.read()) { 67 | dmx.write(400, 255); 68 | for (int b = 0; b < NUM_GEQ_CHANNELS; b++) { 69 | uint8_t val = sync.fftResult[b]; 70 | dmx.write((CHANNELS + 1 + b), val); 71 | } 72 | // Serial.println("T"); 73 | } 74 | else { 75 | // Serial.println("F"); 76 | dmx.write(400, 0); 77 | } 78 | if (!e131.isEmpty()) { 79 | led = !led; 80 | digitalWrite(LED_BUILTIN, led); 81 | 82 | e131_packet_t packet; 83 | e131.pull(&packet); // Pull packet from ring buffer 84 | 85 | // EVERY_N_SECONDS( 2 ) { 86 | // Serial.printf("Universe %u / %u Channels | Packet#: %u / Errors: %u / CH1: %u\n", 87 | // htons(packet.universe), // The Universe for this packet 88 | // htons(packet.property_value_count) - 1, // Start code is ignored, we're interested in dimmer data 89 | // e131.stats.num_packets, // Packet counter 90 | // e131.stats.packet_errors, // Packet error counter 91 | // packet.property_values[1]); // Dimmer data for Channel 1 92 | //} 93 | 94 | /* Parse a packet and update pixels */ 95 | for (int i = 1; i < CHANNELS; i++) { 96 | int v = packet.property_values[i]; 97 | dmx.write(i, v); 98 | } 99 | } 100 | 101 | dmx.update(); 102 | 103 | } 104 | -------------------------------------------------------------------------------- /examples/WLED-MSGEQ7-Sender/README.md: -------------------------------------------------------------------------------- 1 | # WLED-MSGEQ7-Sender 2 | Read data from MSGEQ7 chip and send to wifi network in WLED compatible format, just use the Audio Responsive fork or 0.14 with AudioResponsive UserMod of WLED and set your device to receive 3 | 4 | Create a wifi.h file with your wifi details 5 | 6 | #define SECRET_SSID ""; /* Replace with your SSID */ 7 | 8 | #define SECRET_PSK ""; /* Replace with your WPA2 passphrase */ 9 | -------------------------------------------------------------------------------- /examples/WLED-MSGEQ7-Sender/WLED-MSGEQ7-Sender.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Read audio data from MSGEQ7 chip and send as UDP data in the same format as used in https://github.com/atuline/WLED 3 | This allows for sending FFT data from either ESP8266 or ESP32 to other devices running the audio responsive WLED 4 | */ 5 | 6 | #if defined(ESP8266) // ESP8266 7 | #include 8 | #else // ESP32 9 | #include 10 | #endif 11 | #include 12 | #include // https://github.com/netmindz/WLED-sync 13 | 14 | #include "wifi.h" 15 | // Create file with the following 16 | // ************************************************************************* 17 | // #define SECRET_SSID ""; /* Replace with your SSID */ 18 | // #define SECRET_PSK ""; /* Replace with your WPA2 passphrase */ 19 | // ************************************************************************* 20 | const char ssid[] = SECRET_SSID; 21 | const char passphrase[] = SECRET_PSK; 22 | 23 | 24 | // MSGEQ7 25 | #include "MSGEQ7.h" 26 | #define pinAnalogLeft A0 // "VP" 27 | #define pinAnalogRight A0 28 | #define pinReset 22 29 | #define pinStrobe 19 30 | #define MSGEQ7_INTERVAL ReadsPerSecond(50) 31 | #define MSGEQ7_SMOOTH false 32 | 33 | CMSGEQ7 MSGEQ7; 34 | 35 | WLEDSync sync; 36 | 37 | void setup() { 38 | Serial.begin(115200); 39 | WiFi.mode(WIFI_STA); 40 | WiFi.begin(ssid, passphrase); 41 | Serial.print("Connecting to WiFi "); 42 | while (WiFi.status() != WL_CONNECTED) { 43 | Serial.print('.'); 44 | delay(500); 45 | } 46 | Serial.print("\nConnected! IP address: "); 47 | Serial.println(WiFi.localIP()); 48 | 49 | // This will set the IC ready for reading 50 | MSGEQ7.begin(); 51 | 52 | ArduinoOTA.setHostname("WLED-MSGEQ7"); 53 | ArduinoOTA.begin(); 54 | 55 | sync.begin(); 56 | } 57 | 58 | void loop() { 59 | ArduinoOTA.handle(); 60 | // Analyze without delay every interval 61 | bool newReading = MSGEQ7.read(MSGEQ7_INTERVAL); 62 | if (newReading) { 63 | audioSyncPacket transmitData; 64 | 65 | for (int b = 0; b < NUM_GEQ_CHANNELS; b++) { 66 | int val = MSGEQ7.get(map(b, 0, (NUM_GEQ_CHANNELS - 1), 0, 6)); 67 | val = mapNoise(val); 68 | // Serial.printf("%u ", val); 69 | transmitData.fftResult[b] = val; 70 | } 71 | // Serial.println(); 72 | 73 | int v = map(MSGEQ7.getVolume(), 0, MSGEQ7_OUT_MAX, 0, 1023); // TODO: not sure this is right 74 | transmitData.sampleRaw = v; // Current sample 75 | 76 | sync.send(transmitData); 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /examples/WLED-Sync-Receive/WLED-Sync-Receive.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Read audio data from MSGEQ7 chip and send as UDP data in the same format as used in https://github.com/atuline/WLED 3 | This allows for sending FFT data from either ESP8266 or ESP32 to other devices running the audio responsive WLED 4 | */ 5 | 6 | #if defined(ESP8266) // ESP8266 7 | #include 8 | #else // ESP32 9 | #include 10 | #endif 11 | #include // https://github.com/netmindz/WLED-sync 12 | 13 | #include "wifi.h" 14 | // Create file with the following 15 | // ************************************************************************* 16 | // #define SECRET_SSID ""; /* Replace with your SSID */ 17 | // #define SECRET_PSK ""; /* Replace with your WPA2 passphrase */ 18 | // ************************************************************************* 19 | const char ssid[] = SECRET_SSID; 20 | const char passphrase[] = SECRET_PSK; 21 | 22 | WLEDSync sync; 23 | 24 | void setup() { 25 | Serial.begin(115200); 26 | WiFi.mode(WIFI_STA); 27 | WiFi.begin(ssid, passphrase); 28 | Serial.print("Connecting to WiFi "); 29 | while (WiFi.status() != WL_CONNECTED) { 30 | Serial.print('.'); 31 | delay(500); 32 | } 33 | Serial.print("\nConnected! IP address: "); 34 | Serial.println(WiFi.localIP()); 35 | 36 | sync.begin(); 37 | 38 | Serial.println("Setup complete"); 39 | } 40 | 41 | void loop() { 42 | 43 | if (sync.read()) { 44 | for (int b = 0; b < NUM_GEQ_CHANNELS; b++) { 45 | uint8_t val = sync.fftResult[b]; 46 | Serial.printf("%u ", val); 47 | } 48 | Serial.println(); 49 | 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /library.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "WLED-sync", 3 | "keywords": "wled, audio, sync", 4 | "description": "Library to send and recieve WLED compatible Audio Sync data", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/netmindz/WLED-sync.git" 8 | }, 9 | "version": "0.14.0", 10 | "authors": { 11 | "name": "Will Tatam", 12 | "url": "http://netmindz.net", 13 | "maintainer": true 14 | }, 15 | "frameworks": "arduino", 16 | "platforms": [ 17 | "espressif32" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=WLED-sync 2 | version=0.14.0 3 | author=netmindz 4 | maintainer=netmindz 5 | sentence=Library to send and recieve WLED compatible Audio Sync data 6 | paragraph=Library to send and recieve WLED compatible Audio Sync data 7 | category=Communication 8 | url=https://github.com/netmindz/WLED-sync 9 | architectures=esp8266,esp32 10 | includes=WLED-sync.h 11 | --------------------------------------------------------------------------------