From eb3406e20043dda4e9b3d684eeca0407ead20268 Mon Sep 17 00:00:00 2001 From: Brandon502 <105077712+Brandon502@users.noreply.github.com> Date: Fri, 24 May 2024 11:35:34 -0400 Subject: [PATCH 001/200] GoL Memory Leak fix Removed gameoflife struct. --- wled00/FX.cpp | 58 ++++++++++++++++++++++----------------------------- 1 file changed, 25 insertions(+), 33 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index dce51a4a..de7c9a86 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -5161,30 +5161,22 @@ static void setBitValue(uint8_t* byteArray, size_t n, bool value) { else byteArray[byteIndex] &= ~(1 << bitIndex); } -// create game of life struct to hold cells and future cells -struct gameOfLife { - uint8_t* cells; - uint8_t* futureCells; - uint8_t gliderLength; - uint16_t oscillatorCRC; - uint16_t spaceshipCRC; -}; + uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https://natureofcode.com/book/chapter-7-cellular-automata/ // and https://github.com/DougHaber/nlife-color , Modified By: Brandon Butler if (!strip.isMatrix) return mode_static(); // not a 2D set-up const uint16_t cols = SEGMENT.virtualWidth(); const uint16_t rows = SEGMENT.virtualHeight(); - const size_t dataSize = (SEGMENT.length() / 8) + (((SEGMENT.length() % 8) != 0) ? 1 : 0); // add one byte when extra bits needed (length not a multiple of 8) - const size_t totalSize = dataSize*2 + sizeof(gameOfLife); + const size_t dataSize = ((SEGMENT.length() + 7) / 8); // round up to nearest byte + const size_t totalSize = dataSize*2 + sizeof(uint8_t) + sizeof(uint16_t)*2; // 2 byte arrays, 1 uint8_t, 2 uint16_t if (!SEGENV.allocateData(totalSize)) return mode_static(); //allocation failed - gameOfLife* gol = reinterpret_cast(SEGENV.data); - - if (gol->cells == nullptr) { - gol->cells = new uint8_t[dataSize]; - gol->futureCells = new uint8_t[dataSize]; - } + byte *cells = reinterpret_cast(SEGENV.data); + byte *futureCells = reinterpret_cast(SEGENV.data + dataSize); + uint8_t *gliderLength = reinterpret_cast(SEGENV.data + dataSize*2); + uint16_t *oscillatorCRC = reinterpret_cast(SEGENV.data + dataSize*2 + sizeof(uint8_t)); + uint16_t *spaceshipCRC = reinterpret_cast(SEGENV.data + dataSize*2 + sizeof(uint8_t) + sizeof(uint16_t)); uint16_t &generation = SEGENV.aux0; //rename aux0 and aux1 for readability (not needed) uint16_t &pauseFrames = SEGENV.aux1; @@ -5205,22 +5197,22 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: for (int x = 0; x < cols; x++) for (int y = 0; y < rows; y++) { uint8_t state = (random8() < 82) ? 1 : 0; // ~32% chance of being alive if (state == 0) { - setBitValue(gol->cells, y * cols + x, false); - setBitValue(gol->futureCells, y * cols + x, false); + setBitValue(cells, y * cols + x, false); + setBitValue(futureCells, y * cols + x, false); if (SEGMENT.check2) continue; SEGMENT.setPixelColorXY(x,y, !SEGMENT.check1?backgroundColor : RGBW32(backgroundColor.r, backgroundColor.g, backgroundColor.b, 0)); } else { - setBitValue(gol->cells, y * cols + x, true); - setBitValue(gol->futureCells, y * cols + x, true); + setBitValue(cells, y * cols + x, true); + setBitValue(futureCells, y * cols + x, true); color = SEGMENT.color_from_palette(random8(), false, PALETTE_SOLID_WRAP, 0); SEGMENT.setPixelColorXY(x,y,!SEGMENT.check1?color : RGBW32(color.r, color.g, color.b, 0)); } } //Clear CRCs - gol->oscillatorCRC = 0; - gol->spaceshipCRC = 0; + *oscillatorCRC = 0; + *spaceshipCRC = 0; //Calculate glider length LCM(rows,cols)*4 uint8_t a = rows; @@ -5230,14 +5222,14 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: b = a % b; a = t; } - gol->gliderLength = cols * rows / a * 4; + *gliderLength = cols * rows / a * 4; return FRAMETIME; } //Redraw immediately if overlay to avoid flicker if (SEGMENT.check2) { for (int x = 0; x < cols; x++) for (int y = 0; y < rows; y++) { //redraw foreground/alive - if (getBitValue(gol->cells, y * cols + x)) { + if (getBitValue(cells, y * cols + x)) { color = SEGMENT.getPixelColorXY(x,y); SEGMENT.setPixelColorXY(x,y, !SEGMENT.check1?color : RGBW32(color.r, color.g, color.b, 0)); } @@ -5271,7 +5263,7 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: } cIndex = cY * cols + cX; // count neighbors and store upto 3 neighbor colors - if (getBitValue(gol->cells, cIndex)) { //if alive + if (getBitValue(cells, cIndex)) { //if alive neighbors++; color = SEGMENT.getPixelColorXY(cX, cY); if (color == backgroundColor) continue; //parent just died, color lost @@ -5281,16 +5273,16 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: } // Rules of Life - bool cellValue = getBitValue(gol->cells, y * cols + x); + bool cellValue = getBitValue(cells, y * cols + x); if ((cellValue) && (neighbors < 2 || neighbors > 3)) { // Loneliness or overpopulation cellChanged = true; - setBitValue(gol->futureCells, y * cols + x, false); + setBitValue(futureCells, y * cols + x, false); if (!SEGMENT.check2) SEGMENT.setPixelColorXY(x,y, !SEGMENT.check1?backgroundColor : RGBW32(backgroundColor.r, backgroundColor.g, backgroundColor.b, 0)); } else if (!(cellValue) && (neighbors == 3)) { // Reproduction - setBitValue(gol->futureCells, y * cols + x, true); + setBitValue(futureCells, y * cols + x, true); cellChanged = true; // find dominant color and assign it to a cell // no longer storing colors, if parent dies the color is lost @@ -5311,21 +5303,21 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: } } //update cell values - memcpy(gol->cells, gol->futureCells, dataSize); + memcpy(cells, futureCells, dataSize); // Get current crc value - uint16_t crc = crc16((const unsigned char*)gol->cells, dataSize); + uint16_t crc = crc16((const unsigned char*)cells, dataSize); bool repetition = false; - if (!cellChanged || crc == gol->oscillatorCRC || crc == gol->spaceshipCRC) repetition = true; //check if cell changed this gen and compare previous stored crc values + if (!cellChanged || crc == *oscillatorCRC || crc == *spaceshipCRC) repetition = true; //check if cell changed this gen and compare previous stored crc values if (repetition) { generation = 0; // reset on next call pauseFrames = 50; return FRAMETIME; } // Update CRC values - if (generation % 16 == 0) gol->oscillatorCRC = crc; - if (generation % gol->gliderLength == 0) gol->spaceshipCRC = crc; + if (generation % 16 == 0) *oscillatorCRC = crc; + if (generation % *gliderLength == 0) *spaceshipCRC = crc; generation++; SEGENV.step = strip.now; From 09dad1d79658b8d3454a611c72878ef35ee31d5a Mon Sep 17 00:00:00 2001 From: Brandon502 <105077712+Brandon502@users.noreply.github.com> Date: Fri, 24 May 2024 12:45:13 -0400 Subject: [PATCH 002/200] GoL - Added blur slider Blurs dead cells instead of immediately turning off depending on slider value. Blur is disabled when using overlay. --- wled00/FX.cpp | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index de7c9a86..75d1d896 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -5180,7 +5180,7 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: uint16_t &generation = SEGENV.aux0; //rename aux0 and aux1 for readability (not needed) uint16_t &pauseFrames = SEGENV.aux1; - CRGB backgroundColor = SEGCOLOR(1); + CRGB bgColor = SEGCOLOR(1); CRGB color; if (SEGENV.call == 0) { @@ -5200,7 +5200,7 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: setBitValue(cells, y * cols + x, false); setBitValue(futureCells, y * cols + x, false); if (SEGMENT.check2) continue; - SEGMENT.setPixelColorXY(x,y, !SEGMENT.check1?backgroundColor : RGBW32(backgroundColor.r, backgroundColor.g, backgroundColor.b, 0)); + SEGMENT.setPixelColorXY(x,y, !SEGMENT.check1?bgColor : RGBW32(bgColor.r, bgColor.g, bgColor.b, 0)); } else { setBitValue(cells, y * cols + x, true); @@ -5265,8 +5265,9 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: // count neighbors and store upto 3 neighbor colors if (getBitValue(cells, cIndex)) { //if alive neighbors++; + if (!getBitValue(futureCells, cIndex)) continue; //parent just died, color lost color = SEGMENT.getPixelColorXY(cX, cY); - if (color == backgroundColor) continue; //parent just died, color lost + if (color == bgColor) continue; nColors[colorCount%3] = color; colorCount++; } @@ -5278,7 +5279,8 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: // Loneliness or overpopulation cellChanged = true; setBitValue(futureCells, y * cols + x, false); - if (!SEGMENT.check2) SEGMENT.setPixelColorXY(x,y, !SEGMENT.check1?backgroundColor : RGBW32(backgroundColor.r, backgroundColor.g, backgroundColor.b, 0)); + // blur/turn off dying cells + if (!SEGMENT.check2) SEGMENT.setPixelColorXY(x,y, blend(SEGMENT.getPixelColorXY(x,y), bgColor, map(SEGMENT.custom1, 0, 255, 255, 0))); } else if (!(cellValue) && (neighbors == 3)) { // Reproduction @@ -5296,11 +5298,13 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: else if (colorCount == 1) dominantColor = nColors[0]; // 2 leading parents died else dominantColor = color; // all parents died last used color // mutate color chance - if (random8() < SEGMENT.intensity) dominantColor = !SEGMENT.check1?SEGMENT.color_from_palette(random8(), false, PALETTE_SOLID_WRAP, 0): random16()*random16(); - + if (random8() < SEGMENT.intensity || dominantColor == bgColor) dominantColor = !SEGMENT.check1?SEGMENT.color_from_palette(random8(), false, PALETTE_SOLID_WRAP, 0): random16()*random16(); if (SEGMENT.check1) dominantColor = RGBW32(dominantColor.r, dominantColor.g, dominantColor.b, 0); //WLEDMM support all colors SEGMENT.setPixelColorXY(x,y, dominantColor); - } + } + else { // blur dead cells + if (!cellValue && !SEGMENT.check2) SEGMENT.setPixelColorXY(x,y, blend(SEGMENT.getPixelColorXY(x,y), bgColor, map(SEGMENT.custom1, 0, 255, 255, 0))); + } } //update cell values memcpy(cells, futureCells, dataSize); @@ -5323,7 +5327,7 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: SEGENV.step = strip.now; return FRAMETIME; } // mode_2Dgameoflife() -static const char _data_FX_MODE_2DGAMEOFLIFE[] PROGMEM = "Game Of Life@!,Color Mutation ☾,,,,All Colors ☾,Overlay ☾,Wrap ☾,;!,!;!;2;sx=200,ix=12,c1=0,o3=1"; +static const char _data_FX_MODE_2DGAMEOFLIFE[] PROGMEM = "Game Of Life@!,Color Mutation ☾,Blur ☾,,,All Colors ☾,Overlay ☾,Wrap ☾,;!,!;!;2;sx=200,ix=12,c1=0,o3=1"; ///////////////////////// // 2D Hiphotic // From 8491cf18f7b6f8d532e9e6641b0e45d965a8b6dd Mon Sep 17 00:00:00 2001 From: Brandon502 <105077712+Brandon502@users.noreply.github.com> Date: Fri, 24 May 2024 13:00:10 -0400 Subject: [PATCH 003/200] GoL - Removed pauseFrames Uses .step now to pause. --- wled00/FX.cpp | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 75d1d896..56c6c259 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -5178,8 +5178,7 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: uint16_t *oscillatorCRC = reinterpret_cast(SEGENV.data + dataSize*2 + sizeof(uint8_t)); uint16_t *spaceshipCRC = reinterpret_cast(SEGENV.data + dataSize*2 + sizeof(uint8_t) + sizeof(uint16_t)); - uint16_t &generation = SEGENV.aux0; //rename aux0 and aux1 for readability (not needed) - uint16_t &pauseFrames = SEGENV.aux1; + uint16_t &generation = SEGENV.aux0; //rename aux0 readability (not needed) CRGB bgColor = SEGCOLOR(1); CRGB color; @@ -5188,10 +5187,9 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: SEGMENT.fill(BLACK); // to make sure that segment buffer and physical leds are aligned initially } //start new game of life - if ((SEGENV.call == 0 || generation == 0) && pauseFrames == 0) { - SEGENV.step = strip.now; // .step = previous call time + if ((SEGENV.call == 0 || generation == 0) && SEGENV.step < strip.now) { + SEGENV.step = strip.now + 1500; // show initial state for 1.5 seconds generation = 1; - pauseFrames = 75; // show initial state for longer random16_set_seed(strip.now>>2); //seed the random generator //Setup Grid for (int x = 0; x < cols; x++) for (int y = 0; y < rows; y++) { @@ -5235,8 +5233,7 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: } } } - if (pauseFrames || strip.now - SEGENV.step < FRAMETIME_FIXED * (uint32_t)map(SEGMENT.speed,0,255,64,2)) { - if(pauseFrames) pauseFrames--; + if (SEGENV.step > strip.now || strip.now - SEGENV.step < FRAMETIME_FIXED * (uint32_t)map(SEGMENT.speed,0,255,64,2)) { return FRAMETIME; //skip if not enough time has passed } //Update Game of Life @@ -5316,7 +5313,7 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: if (!cellChanged || crc == *oscillatorCRC || crc == *spaceshipCRC) repetition = true; //check if cell changed this gen and compare previous stored crc values if (repetition) { generation = 0; // reset on next call - pauseFrames = 50; + SEGENV.step += 1000; // pause final generation for 1 second return FRAMETIME; } // Update CRC values From 631aa35718078eeb95cfb7c0de677361c0c70fd4 Mon Sep 17 00:00:00 2001 From: Brandon502 <105077712+Brandon502@users.noreply.github.com> Date: Fri, 24 May 2024 13:20:29 -0400 Subject: [PATCH 004/200] GoL - Change default values. General cleanup. --- wled00/FX.cpp | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 56c6c259..5f454fca 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -5188,24 +5188,23 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: } //start new game of life if ((SEGENV.call == 0 || generation == 0) && SEGENV.step < strip.now) { - SEGENV.step = strip.now + 1500; // show initial state for 1.5 seconds + SEGENV.step = strip.now + 1250; // show initial state for 1.25 seconds generation = 1; random16_set_seed(strip.now>>2); //seed the random generator //Setup Grid for (int x = 0; x < cols; x++) for (int y = 0; y < rows; y++) { - uint8_t state = (random8() < 82) ? 1 : 0; // ~32% chance of being alive - if (state == 0) { - setBitValue(cells, y * cols + x, false); - setBitValue(futureCells, y * cols + x, false); - if (SEGMENT.check2) continue; - SEGMENT.setPixelColorXY(x,y, !SEGMENT.check1?bgColor : RGBW32(bgColor.r, bgColor.g, bgColor.b, 0)); - } - else { + if (random8() < 82) { // ~32% chance of being alive setBitValue(cells, y * cols + x, true); setBitValue(futureCells, y * cols + x, true); color = SEGMENT.color_from_palette(random8(), false, PALETTE_SOLID_WRAP, 0); SEGMENT.setPixelColorXY(x,y,!SEGMENT.check1?color : RGBW32(color.r, color.g, color.b, 0)); } + else { + setBitValue(cells, y * cols + x, false); + setBitValue(futureCells, y * cols + x, false); + if (SEGMENT.check2) continue; //overlay + SEGMENT.setPixelColorXY(x,y, !SEGMENT.check1?bgColor : RGBW32(bgColor.r, bgColor.g, bgColor.b, 0)); + } } //Clear CRCs @@ -5250,7 +5249,7 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: for (int i = -1; i <= 1; i++) for (int j = -1; j <= 1; j++) { // iterate through 3*3 matrix if (i==0 && j==0) continue; // ignore itself - if (!SEGMENT.check3 || generation % 1500 == 0) { //no wrap disable wrap every 1500 generations to prevent undetected repeats + if (!SEGMENT.check3 || generation % 1500 == 0) { //no wrap, disable wrap every 1500 generations to prevent undetected repeats cX = x+i; cY = y+j; if (cX < 0 || cY < 0 || cX >= cols || cY >= rows) continue; //skip if out of bounds @@ -5265,7 +5264,7 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: if (!getBitValue(futureCells, cIndex)) continue; //parent just died, color lost color = SEGMENT.getPixelColorXY(cX, cY); if (color == bgColor) continue; - nColors[colorCount%3] = color; + nColors[colorCount % 3] = color; colorCount++; } } @@ -5292,8 +5291,8 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: else dominantColor = nColors[random8()%3]; } else if (colorCount == 2) dominantColor = nColors[random8()%2]; // 1 leading parent died - else if (colorCount == 1) dominantColor = nColors[0]; // 2 leading parents died - else dominantColor = color; // all parents died last used color + else if (colorCount == 1) dominantColor = nColors[0]; // 2 leading parents died + else dominantColor = color; // all parents died last used color // mutate color chance if (random8() < SEGMENT.intensity || dominantColor == bgColor) dominantColor = !SEGMENT.check1?SEGMENT.color_from_palette(random8(), false, PALETTE_SOLID_WRAP, 0): random16()*random16(); if (SEGMENT.check1) dominantColor = RGBW32(dominantColor.r, dominantColor.g, dominantColor.b, 0); //WLEDMM support all colors @@ -5312,7 +5311,7 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: bool repetition = false; if (!cellChanged || crc == *oscillatorCRC || crc == *spaceshipCRC) repetition = true; //check if cell changed this gen and compare previous stored crc values if (repetition) { - generation = 0; // reset on next call + generation = 0; // reset on next call SEGENV.step += 1000; // pause final generation for 1 second return FRAMETIME; } @@ -5324,7 +5323,7 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: SEGENV.step = strip.now; return FRAMETIME; } // mode_2Dgameoflife() -static const char _data_FX_MODE_2DGAMEOFLIFE[] PROGMEM = "Game Of Life@!,Color Mutation ☾,Blur ☾,,,All Colors ☾,Overlay ☾,Wrap ☾,;!,!;!;2;sx=200,ix=12,c1=0,o3=1"; +static const char _data_FX_MODE_2DGAMEOFLIFE[] PROGMEM = "Game Of Life@!,Color Mutation ☾,Blur ☾,,,All Colors ☾,Overlay ☾,Wrap ☾,;!,!;!;2;sx=200,ix=12,c1=8,o3=1"; ///////////////////////// // 2D Hiphotic // From 886ea5f88cc3b3b8d21f84ec4f3b4802dc0ff633 Mon Sep 17 00:00:00 2001 From: Brandon502 <105077712+Brandon502@users.noreply.github.com> Date: Fri, 24 May 2024 13:41:49 -0400 Subject: [PATCH 005/200] GoL - Palette Switching Recolors all live cells on palette/color change. Before you needed a game restart or mutation over time. --- wled00/FX.cpp | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 5f454fca..1f06850e 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -5169,7 +5169,8 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: const uint16_t cols = SEGMENT.virtualWidth(); const uint16_t rows = SEGMENT.virtualHeight(); const size_t dataSize = ((SEGMENT.length() + 7) / 8); // round up to nearest byte - const size_t totalSize = dataSize*2 + sizeof(uint8_t) + sizeof(uint16_t)*2; // 2 byte arrays, 1 uint8_t, 2 uint16_t + const size_t detectionSize = sizeof(uint8_t) + sizeof(uint16_t)*2; // 1 uint8_t (gliderLen), 2 uint16_t (2 CRCs) + const size_t totalSize = dataSize * 2 + detectionSize + sizeof(CRGB); //CRGB prevColor if (!SEGENV.allocateData(totalSize)) return mode_static(); //allocation failed byte *cells = reinterpret_cast(SEGENV.data); @@ -5177,6 +5178,7 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: uint8_t *gliderLength = reinterpret_cast(SEGENV.data + dataSize*2); uint16_t *oscillatorCRC = reinterpret_cast(SEGENV.data + dataSize*2 + sizeof(uint8_t)); uint16_t *spaceshipCRC = reinterpret_cast(SEGENV.data + dataSize*2 + sizeof(uint8_t) + sizeof(uint16_t)); + CRGB *prevColor = reinterpret_cast(SEGENV.data + dataSize*2 + detectionSize); uint16_t &generation = SEGENV.aux0; //rename aux0 readability (not needed) CRGB bgColor = SEGCOLOR(1); @@ -5235,6 +5237,17 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: if (SEGENV.step > strip.now || strip.now - SEGENV.step < FRAMETIME_FIXED * (uint32_t)map(SEGMENT.speed,0,255,64,2)) { return FRAMETIME; //skip if not enough time has passed } + + //Recolor live cells if palette/color changed + if (SEGMENT.color_from_palette(0, false, PALETTE_SOLID_WRAP, 0) != *prevColor){ + for (int x = 0; x < cols; x++) for (int y = 0; y < rows; y++) { + if (getBitValue(cells, y * cols + x)) { + SEGMENT.setPixelColorXY(x,y, SEGMENT.color_from_palette(random8(), false, PALETTE_SOLID_WRAP, 0)); + } + } + *prevColor = SEGMENT.color_from_palette(0, false, PALETTE_SOLID_WRAP, 0); + } + //Update Game of Life bool cellChanged = false; // Detect still live and dead grids //cell index and coordinates From 84128eef8b6717c1f6f7eab21b5a14fb5ce266c1 Mon Sep 17 00:00:00 2001 From: Brandon502 <105077712+Brandon502@users.noreply.github.com> Date: Fri, 24 May 2024 14:10:16 -0400 Subject: [PATCH 006/200] GoL - Changed speed slider to updates/sec Every 4 ticks of speed adds 1 update per second. 1 - 64. --- wled00/FX.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 1f06850e..17027406 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -5234,7 +5234,7 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: } } } - if (SEGENV.step > strip.now || strip.now - SEGENV.step < FRAMETIME_FIXED * (uint32_t)map(SEGMENT.speed,0,255,64,2)) { + if (SEGENV.step > strip.now || strip.now - SEGENV.step < 1000 / (uint32_t)map(SEGMENT.speed,0,255,1,64)) { // 1 - 64 updates per second return FRAMETIME; //skip if not enough time has passed } @@ -5336,7 +5336,7 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: SEGENV.step = strip.now; return FRAMETIME; } // mode_2Dgameoflife() -static const char _data_FX_MODE_2DGAMEOFLIFE[] PROGMEM = "Game Of Life@!,Color Mutation ☾,Blur ☾,,,All Colors ☾,Overlay ☾,Wrap ☾,;!,!;!;2;sx=200,ix=12,c1=8,o3=1"; +static const char _data_FX_MODE_2DGAMEOFLIFE[] PROGMEM = "Game Of Life@!,Color Mutation ☾,Blur ☾,,,All Colors ☾,Overlay ☾,Wrap ☾,;!,!;!;2;sx=82,ix=12,c1=8,o3=1"; ///////////////////////// // 2D Hiphotic // From 7657c90c75e9218860b21054c589dd26d911edee Mon Sep 17 00:00:00 2001 From: Brandon502 <105077712+Brandon502@users.noreply.github.com> Date: Fri, 24 May 2024 14:38:35 -0400 Subject: [PATCH 007/200] GoL - Uses cIndex more consistently --- wled00/FX.cpp | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 4ae889fc..186edf72 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -5186,6 +5186,7 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: uint16_t &generation = SEGENV.aux0; //rename aux0 readability (not needed) CRGB bgColor = SEGCOLOR(1); CRGB color; + uint16_t cIndex; if (SEGENV.call == 0) { SEGMENT.setUpLeds(); @@ -5198,15 +5199,16 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: random16_set_seed(strip.now>>2); //seed the random generator //Setup Grid for (int x = 0; x < cols; x++) for (int y = 0; y < rows; y++) { + cIndex = y * cols + x; if (random8() < 82) { // ~32% chance of being alive - setBitValue(cells, y * cols + x, true); - setBitValue(futureCells, y * cols + x, true); + setBitValue(cells, cIndex, true); + setBitValue(futureCells, cIndex, true); color = SEGMENT.color_from_palette(random8(), false, PALETTE_SOLID_WRAP, 0); SEGMENT.setPixelColorXY(x,y,!SEGMENT.check1?color : RGBW32(color.r, color.g, color.b, 0)); } else { - setBitValue(cells, y * cols + x, false); - setBitValue(futureCells, y * cols + x, false); + setBitValue(cells, cIndex, false); + setBitValue(futureCells, cIndex, false); if (SEGMENT.check2) continue; //overlay SEGMENT.setPixelColorXY(x,y, !SEGMENT.check1?bgColor : RGBW32(bgColor.r, bgColor.g, bgColor.b, 0)); } @@ -5231,7 +5233,8 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: if (SEGMENT.check2) { for (int x = 0; x < cols; x++) for (int y = 0; y < rows; y++) { //redraw foreground/alive - if (getBitValue(cells, y * cols + x)) { + cIndex = y * cols + x; + if (getBitValue(cells, cIndex)) { color = SEGMENT.getPixelColorXY(x,y); SEGMENT.setPixelColorXY(x,y, !SEGMENT.check1?color : RGBW32(color.r, color.g, color.b, 0)); } @@ -5244,7 +5247,8 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: //Recolor live cells if palette/color changed if (SEGMENT.color_from_palette(0, false, PALETTE_SOLID_WRAP, 0) != *prevColor){ for (int x = 0; x < cols; x++) for (int y = 0; y < rows; y++) { - if (getBitValue(cells, y * cols + x)) { + cIndex = y * cols + x; + if (getBitValue(cells, cIndex)) { SEGMENT.setPixelColorXY(x,y, SEGMENT.color_from_palette(random8(), false, PALETTE_SOLID_WRAP, 0)); } } @@ -5253,8 +5257,7 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: //Update Game of Life bool cellChanged = false; // Detect still live and dead grids - //cell index and coordinates - uint16_t cIndex; + //cell coordinates uint16_t cX; uint16_t cY; //Loop through all cells. Count neighbors, apply rules, setPixel @@ -5273,7 +5276,7 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: cX = (x+i+cols) % cols; cY = (y+j+rows) % rows; } - cIndex = cY * cols + cX; + cIndex = cY * cols + cX; //neighbor cell index // count neighbors and store upto 3 neighbor colors if (getBitValue(cells, cIndex)) { //if alive neighbors++; @@ -5286,17 +5289,18 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: } // Rules of Life - bool cellValue = getBitValue(cells, y * cols + x); + cIndex = y * cols + x; //current cell index + bool cellValue = getBitValue(cells, cIndex); if ((cellValue) && (neighbors < 2 || neighbors > 3)) { // Loneliness or overpopulation cellChanged = true; - setBitValue(futureCells, y * cols + x, false); + setBitValue(futureCells, cIndex, false); // blur/turn off dying cells if (!SEGMENT.check2) SEGMENT.setPixelColorXY(x,y, blend(SEGMENT.getPixelColorXY(x,y), bgColor, map(SEGMENT.custom1, 0, 255, 255, 0))); } else if (!(cellValue) && (neighbors == 3)) { // Reproduction - setBitValue(futureCells, y * cols + x, true); + setBitValue(futureCells, cIndex, true); cellChanged = true; // find dominant color and assign it to a cell // no longer storing colors, if parent dies the color is lost From 8c3e2cefab07fb1e56a5cb0d55e55c6c7974d705 Mon Sep 17 00:00:00 2001 From: Brandon502 <105077712+Brandon502@users.noreply.github.com> Date: Tue, 4 Jun 2024 16:18:54 -0400 Subject: [PATCH 008/200] GoL - Rework Blur now fades while game is paused. New blur mode when blur slider is >220 Added variables to make code easier to follow instead of check1 custom1 etc. (not needed) Removed % with random8 calls. Misc changes --- wled00/FX.cpp | 110 +++++++++++++++++++++++++------------------------- 1 file changed, 54 insertions(+), 56 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 186edf72..ecc4af14 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -5173,50 +5173,54 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: const uint16_t rows = SEGMENT.virtualHeight(); const size_t dataSize = ((SEGMENT.length() + 7) / 8); // round up to nearest byte const size_t detectionSize = sizeof(uint8_t) + sizeof(uint16_t)*2; // 1 uint8_t (gliderLen), 2 uint16_t (2 CRCs) - const size_t totalSize = dataSize * 2 + detectionSize + sizeof(CRGB); //CRGB prevColor + const size_t totalSize = dataSize * 2 + detectionSize + sizeof(uint8_t); if (!SEGENV.allocateData(totalSize)) return mode_static(); //allocation failed - byte *cells = reinterpret_cast(SEGENV.data); + byte *cells = reinterpret_cast(SEGENV.data); byte *futureCells = reinterpret_cast(SEGENV.data + dataSize); - uint8_t *gliderLength = reinterpret_cast(SEGENV.data + dataSize*2); + uint8_t *gliderLength = reinterpret_cast(SEGENV.data + dataSize*2); uint16_t *oscillatorCRC = reinterpret_cast(SEGENV.data + dataSize*2 + sizeof(uint8_t)); - uint16_t *spaceshipCRC = reinterpret_cast(SEGENV.data + dataSize*2 + sizeof(uint8_t) + sizeof(uint16_t)); - CRGB *prevColor = reinterpret_cast(SEGENV.data + dataSize*2 + detectionSize); + uint16_t *spaceshipCRC = reinterpret_cast(SEGENV.data + dataSize*2 + sizeof(uint8_t) + sizeof(uint16_t)); + uint8_t *prevPalette = reinterpret_cast(SEGENV.data + dataSize*2 + detectionSize); - uint16_t &generation = SEGENV.aux0; //rename aux0 readability (not needed) - CRGB bgColor = SEGCOLOR(1); - CRGB color; + uint16_t &generation = SEGENV.aux0; //Rename SEGENV/SEGMENT variables for readability + bool allColors = SEGMENT.check1; + bool overlayBG = SEGMENT.check2; + bool wrap = SEGMENT.check3; + byte blur = map(SEGMENT.custom1, 0, 255, 255, 0); + bool bgBlendMode = SEGMENT.custom1 > 220 && !overlayBG; // if blur is high and not overlaying, use bg blend mode + byte bgBlur = map(SEGMENT.custom1 - 220, 0, 35, 255, 128); + CRGB bgColor = SEGCOLOR(1); + CRGB color = allColors ? random16() * random16() : SEGMENT.color_from_palette(0, false, PALETTE_SOLID_WRAP, 0); uint16_t cIndex; if (SEGENV.call == 0) { SEGMENT.setUpLeds(); SEGMENT.fill(BLACK); // to make sure that segment buffer and physical leds are aligned initially } - //start new game of life + // Setup New Game of Life if ((SEGENV.call == 0 || generation == 0) && SEGENV.step < strip.now) { SEGENV.step = strip.now + 1250; // show initial state for 1.25 seconds generation = 1; + *prevPalette = SEGMENT.palette; random16_set_seed(strip.now>>2); //seed the random generator //Setup Grid + memset(cells, 0, dataSize); for (int x = 0; x < cols; x++) for (int y = 0; y < rows; y++) { cIndex = y * cols + x; - if (random8() < 82) { // ~32% chance of being alive + if (random8(100) < 32) { // ~32% chance of being alive setBitValue(cells, cIndex, true); - setBitValue(futureCells, cIndex, true); color = SEGMENT.color_from_palette(random8(), false, PALETTE_SOLID_WRAP, 0); - SEGMENT.setPixelColorXY(x,y,!SEGMENT.check1?color : RGBW32(color.r, color.g, color.b, 0)); - } - else { - setBitValue(cells, cIndex, false); - setBitValue(futureCells, cIndex, false); - if (SEGMENT.check2) continue; //overlay - SEGMENT.setPixelColorXY(x,y, !SEGMENT.check1?bgColor : RGBW32(bgColor.r, bgColor.g, bgColor.b, 0)); + SEGMENT.setPixelColorXY(x,y, allColors ? random16() * random16() : color); } + else if (!overlayBG) SEGMENT.setPixelColorXY(x,y, allColors ? RGBW32(bgColor.r, bgColor.g, bgColor.b, 0) : bgColor); // set background color if not overlaying } - - //Clear CRCs - *oscillatorCRC = 0; - *spaceshipCRC = 0; + memcpy(futureCells, cells, dataSize); + + //Set CRCs + uint16_t crc = crc16((const unsigned char*)cells, dataSize); + *oscillatorCRC = crc; + *spaceshipCRC = crc; //Calculate glider length LCM(rows,cols)*4 uint8_t a = rows; @@ -5229,32 +5233,27 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: *gliderLength = cols * rows / a * 4; return FRAMETIME; } - //Redraw immediately if overlay to avoid flicker - if (SEGMENT.check2) { + + // Redraw if paused (remove blur), palette changed, or overlaying background (avoid flicker) + bool palChanged = SEGMENT.palette != *prevPalette && !allColors; + bool blurDead = SEGENV.step > strip.now && !bgBlendMode && !overlayBG; + if (palChanged || blurDead || overlayBG) { for (int x = 0; x < cols; x++) for (int y = 0; y < rows; y++) { - //redraw foreground/alive cIndex = y * cols + x; - if (getBitValue(cells, cIndex)) { + bool alive = getBitValue(cells, cIndex); + if (palChanged && alive) SEGMENT.setPixelColorXY(x,y, SEGMENT.color_from_palette(random8(), false, PALETTE_SOLID_WRAP, 0)); + else if (overlayBG & alive) { color = SEGMENT.getPixelColorXY(x,y); - SEGMENT.setPixelColorXY(x,y, !SEGMENT.check1?color : RGBW32(color.r, color.g, color.b, 0)); + SEGMENT.setPixelColorXY(x,y, allColors ? RGBW32(color.r, color.g, color.b, 0) : color); } + if (palChanged && !alive && !overlayBG) SEGMENT.setPixelColorXY(x,y, bgColor); // remove blurred cells from previous palette + else if (blurDead && !alive) SEGMENT.setPixelColorXY(x,y, blend(SEGMENT.getPixelColorXY(x,y), bgColor, blur)); } - } - if (SEGENV.step > strip.now || strip.now - SEGENV.step < 1000 / (uint32_t)map(SEGMENT.speed,0,255,1,64)) { // 1 - 64 updates per second - return FRAMETIME; //skip if not enough time has passed - } - - //Recolor live cells if palette/color changed - if (SEGMENT.color_from_palette(0, false, PALETTE_SOLID_WRAP, 0) != *prevColor){ - for (int x = 0; x < cols; x++) for (int y = 0; y < rows; y++) { - cIndex = y * cols + x; - if (getBitValue(cells, cIndex)) { - SEGMENT.setPixelColorXY(x,y, SEGMENT.color_from_palette(random8(), false, PALETTE_SOLID_WRAP, 0)); - } - } - *prevColor = SEGMENT.color_from_palette(0, false, PALETTE_SOLID_WRAP, 0); + if (palChanged) *prevPalette = SEGMENT.palette; } + if (SEGENV.step > strip.now || strip.now - SEGENV.step < 1000 / (uint32_t)map(SEGMENT.speed,0,255,1,64)) return FRAMETIME; //skip if not enough time has passed (1-64 updates/sec) + //Update Game of Life bool cellChanged = false; // Detect still live and dead grids //cell coordinates @@ -5268,7 +5267,7 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: for (int i = -1; i <= 1; i++) for (int j = -1; j <= 1; j++) { // iterate through 3*3 matrix if (i==0 && j==0) continue; // ignore itself - if (!SEGMENT.check3 || generation % 1500 == 0) { //no wrap, disable wrap every 1500 generations to prevent undetected repeats + if (!wrap || generation % 1500 == 0) { //no wrap, disable wrap every 1500 generations to prevent undetected repeats cX = x+i; cY = y+j; if (cX < 0 || cY < 0 || cX >= cols || cY >= rows) continue; //skip if out of bounds @@ -5292,34 +5291,33 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: cIndex = y * cols + x; //current cell index bool cellValue = getBitValue(cells, cIndex); if ((cellValue) && (neighbors < 2 || neighbors > 3)) { - // Loneliness or overpopulation + // Loneliness or Overpopulation cellChanged = true; setBitValue(futureCells, cIndex, false); - // blur/turn off dying cells - if (!SEGMENT.check2) SEGMENT.setPixelColorXY(x,y, blend(SEGMENT.getPixelColorXY(x,y), bgColor, map(SEGMENT.custom1, 0, 255, 255, 0))); + // Blur/turn off dying cells + if (!overlayBG) SEGMENT.setPixelColorXY(x,y, blend(SEGMENT.getPixelColorXY(x,y), bgColor, bgBlendMode ? bgBlur : blur)); } else if (!(cellValue) && (neighbors == 3)) { // Reproduction setBitValue(futureCells, cIndex, true); cellChanged = true; - // find dominant color and assign it to a cell - // no longer storing colors, if parent dies the color is lost - CRGB dominantColor; + // find dominant color and assign it to a new born cell no longer storing colors, if parent dies the color is lost + CRGB dominantColor; if (colorCount == 3) { //All parents survived if ((nColors[0] == nColors[1]) || (nColors[0] == nColors[2])) dominantColor = nColors[0]; else if (nColors[1] == nColors[2]) dominantColor = nColors[1]; - else dominantColor = nColors[random8()%3]; + else dominantColor = nColors[random8(3)]; } - else if (colorCount == 2) dominantColor = nColors[random8()%2]; // 1 leading parent died - else if (colorCount == 1) dominantColor = nColors[0]; // 2 leading parents died - else dominantColor = color; // all parents died last used color + else if (colorCount == 2) dominantColor = nColors[random8(2)]; // 1 leading parent died + else if (colorCount == 1) dominantColor = nColors[0]; // 2 leading parents died + else dominantColor = color; // all parents died last used color // mutate color chance - if (random8() < SEGMENT.intensity || dominantColor == bgColor) dominantColor = !SEGMENT.check1?SEGMENT.color_from_palette(random8(), false, PALETTE_SOLID_WRAP, 0): random16()*random16(); - if (SEGMENT.check1) dominantColor = RGBW32(dominantColor.r, dominantColor.g, dominantColor.b, 0); //WLEDMM support all colors + if (allColors) dominantColor = RGBW32(dominantColor.r, dominantColor.g, dominantColor.b, 0); //WLEDMM support all colors + if (random8() < SEGMENT.intensity || dominantColor == bgColor) dominantColor = allColors ? random16() * random16() : SEGMENT.color_from_palette(random8(), false, PALETTE_SOLID_WRAP, 0); SEGMENT.setPixelColorXY(x,y, dominantColor); } else { // blur dead cells - if (!cellValue && !SEGMENT.check2) SEGMENT.setPixelColorXY(x,y, blend(SEGMENT.getPixelColorXY(x,y), bgColor, map(SEGMENT.custom1, 0, 255, 255, 0))); + if (!cellValue && !overlayBG && !bgBlendMode) SEGMENT.setPixelColorXY(x,y, blend(SEGMENT.getPixelColorXY(x,y), bgColor, bgBlendMode ? bgBlur : blur)); } } //update cell values @@ -5343,7 +5341,7 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: SEGENV.step = strip.now; return FRAMETIME; } // mode_2Dgameoflife() -static const char _data_FX_MODE_2DGAMEOFLIFE[] PROGMEM = "Game Of Life@!,Color Mutation ☾,Blur ☾,,,All Colors ☾,Overlay ☾,Wrap ☾,;!,!;!;2;sx=82,ix=12,c1=8,o3=1"; +static const char _data_FX_MODE_2DGAMEOFLIFE[] PROGMEM = "Game Of Life@!,Color Mutation ☾,Blur ☾,,,All Colors ☾,Overlay BG ☾,Wrap ☾,;!,!;!;2;sx=82,ix=4,c1=48,o1=0,o2=0,o3=1"; ///////////////////////// // 2D Hiphotic // From f528aef6ba02ce52bb1c015417e8ce646451ed00 Mon Sep 17 00:00:00 2001 From: Brandon502 <105077712+Brandon502@users.noreply.github.com> Date: Wed, 5 Jun 2024 01:07:27 -0400 Subject: [PATCH 009/200] GoL - Allows default overlay mode. Flicker fixed by always redrawing dead cells if overlay unchecked. Can swap between overlay types by checking/unchecking overlayBG. --- wled00/FX.cpp | 43 +++++++++++++++++++++---------------------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index ecc4af14..597ae1fd 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -5234,24 +5234,23 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: return FRAMETIME; } - // Redraw if paused (remove blur), palette changed, or overlaying background (avoid flicker) + bool blurDead = SEGENV.step > strip.now && blur && !bgBlendMode && !overlayBG; bool palChanged = SEGMENT.palette != *prevPalette && !allColors; - bool blurDead = SEGENV.step > strip.now && !bgBlendMode && !overlayBG; - if (palChanged || blurDead || overlayBG) { - for (int x = 0; x < cols; x++) for (int y = 0; y < rows; y++) { - cIndex = y * cols + x; - bool alive = getBitValue(cells, cIndex); - if (palChanged && alive) SEGMENT.setPixelColorXY(x,y, SEGMENT.color_from_palette(random8(), false, PALETTE_SOLID_WRAP, 0)); - else if (overlayBG & alive) { - color = SEGMENT.getPixelColorXY(x,y); - SEGMENT.setPixelColorXY(x,y, allColors ? RGBW32(color.r, color.g, color.b, 0) : color); - } - if (palChanged && !alive && !overlayBG) SEGMENT.setPixelColorXY(x,y, bgColor); // remove blurred cells from previous palette - else if (blurDead && !alive) SEGMENT.setPixelColorXY(x,y, blend(SEGMENT.getPixelColorXY(x,y), bgColor, blur)); - } - if (palChanged) *prevPalette = SEGMENT.palette; - } + if (palChanged) *prevPalette = SEGMENT.palette; + // Redraw if paused (remove blur), palette changed, overlaying background (avoid flicker), + // always redraw dead cells if not overlaying background. Allows overlayFG by default. + for (int x = 0; x < cols; x++) for (int y = 0; y < rows; y++) { + cIndex = y * cols + x; + bool alive = getBitValue(cells, cIndex); + CRGB color = SEGMENT.getPixelColorXY(x,y); + if (palChanged && alive) SEGMENT.setPixelColorXY(x,y, SEGMENT.color_from_palette(random8(), false, PALETTE_SOLID_WRAP, 0)); // Recolor alive cells + else if (overlayBG & alive) SEGMENT.setPixelColorXY(x,y, allColors ? RGBW32(color.r, color.g, color.b, 0) : color); // Redraw alive cells for overlayBG + if (palChanged && !alive && !overlayBG) SEGMENT.setPixelColorXY(x,y, bgColor); // Remove blurred cells from previous palette + else if (blurDead && !alive) SEGMENT.setPixelColorXY(x,y, blend(color, bgColor, blur)); // Blur dead cells (paused) + else if (!overlayBG && !alive) SEGMENT.setPixelColorXY(x,y, color); // Redraw dead cells for default overlayFG + } + if (SEGENV.step > strip.now || strip.now - SEGENV.step < 1000 / (uint32_t)map(SEGMENT.speed,0,255,1,64)) return FRAMETIME; //skip if not enough time has passed (1-64 updates/sec) //Update Game of Life @@ -5266,14 +5265,14 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: CRGB nColors[3]; // track 3 colors, dying cells may overwrite but this wont be used for (int i = -1; i <= 1; i++) for (int j = -1; j <= 1; j++) { // iterate through 3*3 matrix - if (i==0 && j==0) continue; // ignore itself + if (i == 0 && j == 0) continue; // ignore itself if (!wrap || generation % 1500 == 0) { //no wrap, disable wrap every 1500 generations to prevent undetected repeats - cX = x+i; - cY = y+j; + cX = x + i; + cY = y + j; if (cX < 0 || cY < 0 || cX >= cols || cY >= rows) continue; //skip if out of bounds } else { //wrap around - cX = (x+i+cols) % cols; - cY = (y+j+rows) % rows; + cX = (x + i + cols) % cols; + cY = (y + j + rows) % rows; } cIndex = cY * cols + cX; //neighbor cell index // count neighbors and store upto 3 neighbor colors @@ -5341,7 +5340,7 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: SEGENV.step = strip.now; return FRAMETIME; } // mode_2Dgameoflife() -static const char _data_FX_MODE_2DGAMEOFLIFE[] PROGMEM = "Game Of Life@!,Color Mutation ☾,Blur ☾,,,All Colors ☾,Overlay BG ☾,Wrap ☾,;!,!;!;2;sx=82,ix=4,c1=48,o1=0,o2=0,o3=1"; +static const char _data_FX_MODE_2DGAMEOFLIFE[] PROGMEM = "Game Of Life@!,Color Mutation ☾,Blur ☾,,,All Colors ☾,Overlay BG ☾,Wrap ☾,;!,!;!;2;sx=56,ix=2,c1=128,o1=0,o2=0,o3=1"; ///////////////////////// // 2D Hiphotic // From c655db38640e8694289c085ab53d340b0913b9b5 Mon Sep 17 00:00:00 2001 From: Brandon502 <105077712+Brandon502@users.noreply.github.com> Date: Wed, 5 Jun 2024 11:46:23 -0400 Subject: [PATCH 010/200] GoL - gliderLength switched to uint16_t --- wled00/FX.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 597ae1fd..9c2f77da 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -5172,16 +5172,16 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: const uint16_t cols = SEGMENT.virtualWidth(); const uint16_t rows = SEGMENT.virtualHeight(); const size_t dataSize = ((SEGMENT.length() + 7) / 8); // round up to nearest byte - const size_t detectionSize = sizeof(uint8_t) + sizeof(uint16_t)*2; // 1 uint8_t (gliderLen), 2 uint16_t (2 CRCs) - const size_t totalSize = dataSize * 2 + detectionSize + sizeof(uint8_t); + const size_t detectionSize = sizeof(uint16_t) * 3; // 2 CRCs, gliderLength + const size_t totalSize = dataSize * 2 + detectionSize + sizeof(uint8_t); // detectionSize + prevPalette if (!SEGENV.allocateData(totalSize)) return mode_static(); //allocation failed byte *cells = reinterpret_cast(SEGENV.data); byte *futureCells = reinterpret_cast(SEGENV.data + dataSize); - uint8_t *gliderLength = reinterpret_cast(SEGENV.data + dataSize*2); - uint16_t *oscillatorCRC = reinterpret_cast(SEGENV.data + dataSize*2 + sizeof(uint8_t)); - uint16_t *spaceshipCRC = reinterpret_cast(SEGENV.data + dataSize*2 + sizeof(uint8_t) + sizeof(uint16_t)); - uint8_t *prevPalette = reinterpret_cast(SEGENV.data + dataSize*2 + detectionSize); + uint16_t *gliderLength = reinterpret_cast(SEGENV.data + dataSize * 2); + uint16_t *oscillatorCRC = reinterpret_cast(SEGENV.data + dataSize * 2 + sizeof(uint8_t)); + uint16_t *spaceshipCRC = reinterpret_cast(SEGENV.data + dataSize * 2 + sizeof(uint8_t) + sizeof(uint16_t)); + uint8_t *prevPalette = reinterpret_cast(SEGENV.data + dataSize * 2 + detectionSize); uint16_t &generation = SEGENV.aux0; //Rename SEGENV/SEGMENT variables for readability bool allColors = SEGMENT.check1; @@ -5234,7 +5234,7 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: return FRAMETIME; } - bool blurDead = SEGENV.step > strip.now && blur && !bgBlendMode && !overlayBG; + bool blurDead = SEGENV.step > strip.now && blur && !bgBlendMode && !overlayBG; bool palChanged = SEGMENT.palette != *prevPalette && !allColors; if (palChanged) *prevPalette = SEGMENT.palette; From 5a6d80f30b265fc235b6070b65e24a1aff1aa2bc Mon Sep 17 00:00:00 2001 From: Brandon502 <105077712+Brandon502@users.noreply.github.com> Date: Wed, 5 Jun 2024 12:41:36 -0400 Subject: [PATCH 011/200] GoL - Correct data allocation Fixed overlooked data allocation after switching gliderLength to uint16. --- wled00/FX.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 9c2f77da..cb117339 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -5179,8 +5179,8 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: byte *cells = reinterpret_cast(SEGENV.data); byte *futureCells = reinterpret_cast(SEGENV.data + dataSize); uint16_t *gliderLength = reinterpret_cast(SEGENV.data + dataSize * 2); - uint16_t *oscillatorCRC = reinterpret_cast(SEGENV.data + dataSize * 2 + sizeof(uint8_t)); - uint16_t *spaceshipCRC = reinterpret_cast(SEGENV.data + dataSize * 2 + sizeof(uint8_t) + sizeof(uint16_t)); + uint16_t *oscillatorCRC = reinterpret_cast(SEGENV.data + dataSize * 2 + sizeof(uint16_t)); + uint16_t *spaceshipCRC = reinterpret_cast(SEGENV.data + dataSize * 2 + sizeof(uint16_t) * 2); uint8_t *prevPalette = reinterpret_cast(SEGENV.data + dataSize * 2 + detectionSize); uint16_t &generation = SEGENV.aux0; //Rename SEGENV/SEGMENT variables for readability @@ -5216,7 +5216,7 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: else if (!overlayBG) SEGMENT.setPixelColorXY(x,y, allColors ? RGBW32(bgColor.r, bgColor.g, bgColor.b, 0) : bgColor); // set background color if not overlaying } memcpy(futureCells, cells, dataSize); - + //Set CRCs uint16_t crc = crc16((const unsigned char*)cells, dataSize); *oscillatorCRC = crc; @@ -5334,7 +5334,7 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: } // Update CRC values if (generation % 16 == 0) *oscillatorCRC = crc; - if (generation % *gliderLength == 0) *spaceshipCRC = crc; + if (*gliderLength && generation % *gliderLength == 0) *spaceshipCRC = crc; generation++; SEGENV.step = strip.now; From 015260c5addca44ce8582efa3ff6a901bc21146a Mon Sep 17 00:00:00 2001 From: Brandon502 <105077712+Brandon502@users.noreply.github.com> Date: Sun, 9 Jun 2024 15:50:07 -0400 Subject: [PATCH 012/200] GoL - Small color fix and glider detection change. Wrap is disabled if only 5 remaining alive cells (Glider). --- wled00/FX.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index cb117339..506f408e 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -5234,6 +5234,7 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: return FRAMETIME; } + int aliveCount = 0; // Solo glider detection bool blurDead = SEGENV.step > strip.now && blur && !bgBlendMode && !overlayBG; bool palChanged = SEGMENT.palette != *prevPalette && !allColors; if (palChanged) *prevPalette = SEGMENT.palette; @@ -5243,6 +5244,7 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: for (int x = 0; x < cols; x++) for (int y = 0; y < rows; y++) { cIndex = y * cols + x; bool alive = getBitValue(cells, cIndex); + if (alive) aliveCount++; CRGB color = SEGMENT.getPixelColorXY(x,y); if (palChanged && alive) SEGMENT.setPixelColorXY(x,y, SEGMENT.color_from_palette(random8(), false, PALETTE_SOLID_WRAP, 0)); // Recolor alive cells else if (overlayBG & alive) SEGMENT.setPixelColorXY(x,y, allColors ? RGBW32(color.r, color.g, color.b, 0) : color); // Redraw alive cells for overlayBG @@ -5266,7 +5268,7 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: for (int i = -1; i <= 1; i++) for (int j = -1; j <= 1; j++) { // iterate through 3*3 matrix if (i == 0 && j == 0) continue; // ignore itself - if (!wrap || generation % 1500 == 0) { //no wrap, disable wrap every 1500 generations to prevent undetected repeats + if (!wrap || generation % 1500 == 0 || aliveCount == 5) { //no wrap, disable wrap every 1500 generations to prevent undetected repeats cX = x + i; cY = y + j; if (cX < 0 || cY < 0 || cX >= cols || cY >= rows) continue; //skip if out of bounds @@ -5278,9 +5280,8 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: // count neighbors and store upto 3 neighbor colors if (getBitValue(cells, cIndex)) { //if alive neighbors++; - if (!getBitValue(futureCells, cIndex)) continue; //parent just died, color lost + if (!getBitValue(futureCells, cIndex) || SEGMENT.getPixelColorXY(cX,cY) == bgColor) continue; //parent just died, color lost color = SEGMENT.getPixelColorXY(cX, cY); - if (color == bgColor) continue; nColors[colorCount % 3] = color; colorCount++; } From e8e784ddcd035fa0d06dbc6126f89eacd0f3f08e Mon Sep 17 00:00:00 2001 From: Brandon502 <105077712+Brandon502@users.noreply.github.com> Date: Wed, 19 Jun 2024 15:25:51 -0400 Subject: [PATCH 013/200] GoL CRGB changed to uint32 & color_blend() Other small changes. --- wled00/FX.cpp | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 506f408e..7d7b1eac 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -5190,8 +5190,8 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: byte blur = map(SEGMENT.custom1, 0, 255, 255, 0); bool bgBlendMode = SEGMENT.custom1 > 220 && !overlayBG; // if blur is high and not overlaying, use bg blend mode byte bgBlur = map(SEGMENT.custom1 - 220, 0, 35, 255, 128); - CRGB bgColor = SEGCOLOR(1); - CRGB color = allColors ? random16() * random16() : SEGMENT.color_from_palette(0, false, PALETTE_SOLID_WRAP, 0); + uint32_t bgColor = SEGCOLOR(1); + uint32_t color = allColors ? random16() * random16() : SEGMENT.color_from_palette(0, false, PALETTE_SOLID_WRAP, 0); uint16_t cIndex; if (SEGENV.call == 0) { @@ -5213,7 +5213,7 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: color = SEGMENT.color_from_palette(random8(), false, PALETTE_SOLID_WRAP, 0); SEGMENT.setPixelColorXY(x,y, allColors ? random16() * random16() : color); } - else if (!overlayBG) SEGMENT.setPixelColorXY(x,y, allColors ? RGBW32(bgColor.r, bgColor.g, bgColor.b, 0) : bgColor); // set background color if not overlaying + else if (!overlayBG) SEGMENT.setPixelColorXY(x,y, bgColor); // set background color if not overlaying } memcpy(futureCells, cells, dataSize); @@ -5234,7 +5234,7 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: return FRAMETIME; } - int aliveCount = 0; // Solo glider detection + int aliveCount = 0; // Solo glider detection bool blurDead = SEGENV.step > strip.now && blur && !bgBlendMode && !overlayBG; bool palChanged = SEGMENT.palette != *prevPalette && !allColors; if (palChanged) *prevPalette = SEGMENT.palette; @@ -5245,12 +5245,12 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: cIndex = y * cols + x; bool alive = getBitValue(cells, cIndex); if (alive) aliveCount++; - CRGB color = SEGMENT.getPixelColorXY(x,y); - if (palChanged && alive) SEGMENT.setPixelColorXY(x,y, SEGMENT.color_from_palette(random8(), false, PALETTE_SOLID_WRAP, 0)); // Recolor alive cells - else if (overlayBG & alive) SEGMENT.setPixelColorXY(x,y, allColors ? RGBW32(color.r, color.g, color.b, 0) : color); // Redraw alive cells for overlayBG - if (palChanged && !alive && !overlayBG) SEGMENT.setPixelColorXY(x,y, bgColor); // Remove blurred cells from previous palette - else if (blurDead && !alive) SEGMENT.setPixelColorXY(x,y, blend(color, bgColor, blur)); // Blur dead cells (paused) - else if (!overlayBG && !alive) SEGMENT.setPixelColorXY(x,y, color); // Redraw dead cells for default overlayFG + uint32_t cellColor = SEGMENT.getPixelColorXY(x,y); + if ( alive && palChanged) SEGMENT.setPixelColorXY(x,y, SEGMENT.color_from_palette(random8(), false, PALETTE_SOLID_WRAP, 0)); // Recolor alive cells + else if ( alive && overlayBG) SEGMENT.setPixelColorXY(x,y, cellColor); // Redraw alive cells for overlayBG + if (!alive && palChanged && !overlayBG) SEGMENT.setPixelColorXY(x,y, bgColor); // Remove blurred cells from previous palette + else if (!alive && blurDead) SEGMENT.setPixelColorXY(x,y, color_blend(cellColor, bgColor, blur)); // Blur dead cells (paused) + else if (!alive && !overlayBG) SEGMENT.setPixelColorXY(x,y, cellColor); // Redraw dead cells for default overlayFG } if (SEGENV.step > strip.now || strip.now - SEGENV.step < 1000 / (uint32_t)map(SEGMENT.speed,0,255,1,64)) return FRAMETIME; //skip if not enough time has passed (1-64 updates/sec) @@ -5264,7 +5264,7 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: for (int x = 0; x < cols; x++) for (int y = 0; y < rows; y++) { byte neighbors = 0; byte colorCount = 0; //track number of valid colors - CRGB nColors[3]; // track 3 colors, dying cells may overwrite but this wont be used + uint32_t nColors[3]; // track 3 colors, dying cells may overwrite but this wont be used for (int i = -1; i <= 1; i++) for (int j = -1; j <= 1; j++) { // iterate through 3*3 matrix if (i == 0 && j == 0) continue; // ignore itself @@ -5295,14 +5295,14 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: cellChanged = true; setBitValue(futureCells, cIndex, false); // Blur/turn off dying cells - if (!overlayBG) SEGMENT.setPixelColorXY(x,y, blend(SEGMENT.getPixelColorXY(x,y), bgColor, bgBlendMode ? bgBlur : blur)); + if (!overlayBG) SEGMENT.setPixelColorXY(x,y, color_blend(SEGMENT.getPixelColorXY(x,y), bgColor, bgBlendMode ? bgBlur : blur)); } else if (!(cellValue) && (neighbors == 3)) { // Reproduction setBitValue(futureCells, cIndex, true); cellChanged = true; // find dominant color and assign it to a new born cell no longer storing colors, if parent dies the color is lost - CRGB dominantColor; + uint32_t dominantColor; if (colorCount == 3) { //All parents survived if ((nColors[0] == nColors[1]) || (nColors[0] == nColors[2])) dominantColor = nColors[0]; else if (nColors[1] == nColors[2]) dominantColor = nColors[1]; @@ -5310,14 +5310,13 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: } else if (colorCount == 2) dominantColor = nColors[random8(2)]; // 1 leading parent died else if (colorCount == 1) dominantColor = nColors[0]; // 2 leading parents died - else dominantColor = color; // all parents died last used color + else dominantColor = color; // all parents died use last seen color // mutate color chance - if (allColors) dominantColor = RGBW32(dominantColor.r, dominantColor.g, dominantColor.b, 0); //WLEDMM support all colors if (random8() < SEGMENT.intensity || dominantColor == bgColor) dominantColor = allColors ? random16() * random16() : SEGMENT.color_from_palette(random8(), false, PALETTE_SOLID_WRAP, 0); SEGMENT.setPixelColorXY(x,y, dominantColor); } else { // blur dead cells - if (!cellValue && !overlayBG && !bgBlendMode) SEGMENT.setPixelColorXY(x,y, blend(SEGMENT.getPixelColorXY(x,y), bgColor, bgBlendMode ? bgBlur : blur)); + if (!cellValue && !overlayBG && !bgBlendMode) SEGMENT.setPixelColorXY(x,y, color_blend(SEGMENT.getPixelColorXY(x,y), bgColor, bgBlendMode ? bgBlur : blur)); } } //update cell values From 4aee7eb6c6810f5f4d4fbb522433442e1b1a01f9 Mon Sep 17 00:00:00 2001 From: Brandon502 <105077712+Brandon502@users.noreply.github.com> Date: Wed, 19 Jun 2024 19:00:14 -0400 Subject: [PATCH 014/200] GoL - Small fix Remove redundant check --- wled00/FX.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 7d7b1eac..72642a6a 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -5316,7 +5316,7 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: SEGMENT.setPixelColorXY(x,y, dominantColor); } else { // blur dead cells - if (!cellValue && !overlayBG && !bgBlendMode) SEGMENT.setPixelColorXY(x,y, color_blend(SEGMENT.getPixelColorXY(x,y), bgColor, bgBlendMode ? bgBlur : blur)); + if (!cellValue && !overlayBG && !bgBlendMode) SEGMENT.setPixelColorXY(x,y, color_blend(SEGMENT.getPixelColorXY(x,y), bgColor, blur)); } } //update cell values From 086b0e3e0e4bb455fa91865c8539e83680b3e25b Mon Sep 17 00:00:00 2001 From: Brandon502 <105077712+Brandon502@users.noreply.github.com> Date: Wed, 19 Jun 2024 23:09:31 -0400 Subject: [PATCH 015/200] GoL - Game start transition Starting alive cells are randomly colored in and bg fades on game start. --- wled00/FX.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 72642a6a..c19362be 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -5210,10 +5210,8 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: cIndex = y * cols + x; if (random8(100) < 32) { // ~32% chance of being alive setBitValue(cells, cIndex, true); - color = SEGMENT.color_from_palette(random8(), false, PALETTE_SOLID_WRAP, 0); - SEGMENT.setPixelColorXY(x,y, allColors ? random16() * random16() : color); + SEGMENT.setPixelColorXY(x,y, bgColor); // Initial color set in redraw loop } - else if (!overlayBG) SEGMENT.setPixelColorXY(x,y, bgColor); // set background color if not overlaying } memcpy(futureCells, cells, dataSize); @@ -5231,7 +5229,6 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: a = t; } *gliderLength = cols * rows / a * 4; - return FRAMETIME; } int aliveCount = 0; // Solo glider detection @@ -5246,6 +5243,15 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: bool alive = getBitValue(cells, cIndex); if (alive) aliveCount++; uint32_t cellColor = SEGMENT.getPixelColorXY(x,y); + if (generation == 1) {// Spawn initial colors randomly + bool aliveBgColor = (alive && cellColor == bgColor); + if (aliveBgColor && !random(12)) SEGMENT.setPixelColorXY(x,y, allColors ? random16() * random16() : SEGMENT.color_from_palette(random8(), false, PALETTE_SOLID_WRAP, 0)); // Color alive cell + else if (alive && !aliveBgColor) SEGMENT.setPixelColorXY(x,y, cellColor); // Redraw alive cells + else if (!alive && !overlayBG && !bgBlendMode) SEGMENT.setPixelColorXY(x,y, bgColor); // Redraw dead cells for default overlayFG + else if (!alive && !overlayBG) SEGMENT.setPixelColorXY(x,y, color_blend(cellColor, bgColor, 16)); // Fade dead cells (bgBlendMode) + continue; + } + if ( alive && palChanged) SEGMENT.setPixelColorXY(x,y, SEGMENT.color_from_palette(random8(), false, PALETTE_SOLID_WRAP, 0)); // Recolor alive cells else if ( alive && overlayBG) SEGMENT.setPixelColorXY(x,y, cellColor); // Redraw alive cells for overlayBG if (!alive && palChanged && !overlayBG) SEGMENT.setPixelColorXY(x,y, bgColor); // Remove blurred cells from previous palette From 9ef8ff6a162398e05454ebff095605cfd00211f4 Mon Sep 17 00:00:00 2001 From: Brandon502 <105077712+Brandon502@users.noreply.github.com> Date: Thu, 20 Jun 2024 01:07:52 -0400 Subject: [PATCH 016/200] GoL - Redraw loop changes Merged generation == 1 checks into standard checks. --- wled00/FX.cpp | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index c19362be..d9047e7d 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -5210,7 +5210,7 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: cIndex = y * cols + x; if (random8(100) < 32) { // ~32% chance of being alive setBitValue(cells, cIndex, true); - SEGMENT.setPixelColorXY(x,y, bgColor); // Initial color set in redraw loop + if (!overlayBG) SEGMENT.setPixelColorXY(x,y, bgColor); // Initial color set in redraw loop } } memcpy(futureCells, cells, dataSize); @@ -5236,27 +5236,27 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: bool palChanged = SEGMENT.palette != *prevPalette && !allColors; if (palChanged) *prevPalette = SEGMENT.palette; - // Redraw if paused (remove blur), palette changed, overlaying background (avoid flicker), - // always redraw dead cells if not overlaying background. Allows overlayFG by default. + // Redraw Loop + // Redraw if paused (remove blur), palette changed, overlaying background (avoid flicker) + // Always redraw dead cells if not overlaying background. Allows overlayFG by default. + // Generation 1 draws alive cells randomly and fades dead cells for (int x = 0; x < cols; x++) for (int y = 0; y < rows; y++) { cIndex = y * cols + x; bool alive = getBitValue(cells, cIndex); if (alive) aliveCount++; uint32_t cellColor = SEGMENT.getPixelColorXY(x,y); - if (generation == 1) {// Spawn initial colors randomly - bool aliveBgColor = (alive && cellColor == bgColor); - if (aliveBgColor && !random(12)) SEGMENT.setPixelColorXY(x,y, allColors ? random16() * random16() : SEGMENT.color_from_palette(random8(), false, PALETTE_SOLID_WRAP, 0)); // Color alive cell - else if (alive && !aliveBgColor) SEGMENT.setPixelColorXY(x,y, cellColor); // Redraw alive cells - else if (!alive && !overlayBG && !bgBlendMode) SEGMENT.setPixelColorXY(x,y, bgColor); // Redraw dead cells for default overlayFG - else if (!alive && !overlayBG) SEGMENT.setPixelColorXY(x,y, color_blend(cellColor, bgColor, 16)); // Fade dead cells (bgBlendMode) - continue; - } + bool aliveBgColor = (alive && !overlayBG && generation == 1 && cellColor == bgColor); - if ( alive && palChanged) SEGMENT.setPixelColorXY(x,y, SEGMENT.color_from_palette(random8(), false, PALETTE_SOLID_WRAP, 0)); // Recolor alive cells - else if ( alive && overlayBG) SEGMENT.setPixelColorXY(x,y, cellColor); // Redraw alive cells for overlayBG - if (!alive && palChanged && !overlayBG) SEGMENT.setPixelColorXY(x,y, bgColor); // Remove blurred cells from previous palette - else if (!alive && blurDead) SEGMENT.setPixelColorXY(x,y, color_blend(cellColor, bgColor, blur)); // Blur dead cells (paused) - else if (!alive && !overlayBG) SEGMENT.setPixelColorXY(x,y, cellColor); // Redraw dead cells for default overlayFG + if ( alive && (palChanged || (aliveBgColor && !random(12)))) { // Palette change or spawn initial colors randomly + uint32_t randomColor = allColors ? random16() * random16() : SEGMENT.color_from_palette(random8(), false, PALETTE_SOLID_WRAP, 0); + SEGMENT.setPixelColorXY(x,y, randomColor); // Recolor alive cells + aliveBgColor = false; + } + else if ( alive && overlayBG) SEGMENT.setPixelColorXY(x,y, cellColor); // Redraw alive cells for overlayBG + if (!alive && palChanged && !overlayBG) SEGMENT.setPixelColorXY(x,y, bgColor); // Remove blurred cells from previous palette + else if (!alive && blurDead) SEGMENT.setPixelColorXY(x,y, color_blend(cellColor, bgColor, blur));// Blur dead cells (paused) + else if ((!alive || aliveBgColor) && !overlayBG && !bgBlendMode) SEGMENT.setPixelColorXY(x,y, cellColor); // Redraw dead cells/alive off cells for default overlayFG + else if (!alive && !overlayBG && generation == 1) SEGMENT.setPixelColorXY(x,y, color_blend(cellColor, bgColor, 16)); // Fade dead cells (bgBlendMode) on generation 1 } if (SEGENV.step > strip.now || strip.now - SEGENV.step < 1000 / (uint32_t)map(SEGMENT.speed,0,255,1,64)) return FRAMETIME; //skip if not enough time has passed (1-64 updates/sec) From 2edfcb33437386d32d8327a3c91b1c54c8df68e1 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Thu, 20 Jun 2024 12:26:43 +0200 Subject: [PATCH 017/200] small optimization for color_blend * Early exit when color1 == color2 (nothing to blend) * pre-calculate `blendmax - blend` (repeated 4 times) --- wled00/colors.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/wled00/colors.cpp b/wled00/colors.cpp index 6546043b..465fefbb 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -9,9 +9,11 @@ */ IRAM_ATTR_YN uint32_t color_blend(uint32_t color1, uint32_t color2, uint_fast16_t blend, bool b16) { if(blend == 0) return color1; - uint_fast16_t blendmax = b16 ? 0xFFFF : 0xFF; + if (color1 == color2) return color1; // WLEDMM shortcut + const uint_fast16_t blendmax = b16 ? 0xFFFF : 0xFF; if(blend == blendmax) return color2; const uint_fast8_t shift = b16 ? 16 : 8; + const uint_fast16_t blend2 = blendmax - blend; // WLEDMM pre-calculate value uint32_t w1 = W(color1); uint32_t r1 = R(color1); @@ -23,10 +25,10 @@ IRAM_ATTR_YN uint32_t color_blend(uint32_t color1, uint32_t color2, uint_fast16_ uint32_t g2 = G(color2); uint32_t b2 = B(color2); - uint32_t w3 = ((w2 * blend) + (w1 * (blendmax - blend))) >> shift; - uint32_t r3 = ((r2 * blend) + (r1 * (blendmax - blend))) >> shift; - uint32_t g3 = ((g2 * blend) + (g1 * (blendmax - blend))) >> shift; - uint32_t b3 = ((b2 * blend) + (b1 * (blendmax - blend))) >> shift; + uint32_t w3 = ((w2 * blend) + (w1 * blend2)) >> shift; + uint32_t r3 = ((r2 * blend) + (r1 * blend2)) >> shift; + uint32_t g3 = ((g2 * blend) + (g1 * blend2)) >> shift; + uint32_t b3 = ((b2 * blend) + (b1 * blend2)) >> shift; return RGBW32(r3, g3, b3, w3); } From e28fa67bf0af9a55645958bf9b1412a4737bc257 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 23 Jun 2024 00:38:27 +0200 Subject: [PATCH 018/200] version bump -b32.40 --- .github/ISSUE_TEMPLATE/bug.yml | 2 +- package-lock.json | 4 ++-- package.json | 2 +- wled00/improv.cpp | 2 +- wled00/wled.h | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index 2cb69644..34b5904d 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -48,7 +48,7 @@ body: attributes: label: What version/release of MM WLED? description: You can find this in by going to Config -> Security & Updates -> Scroll to Bottom. Copy and paste the entire line after "Server message" - placeholder: "e.g. build 2401290, WLEDMM_0.14.1-b31.38_esp32_4MB_M.bin" + placeholder: "e.g. build 2401290, WLEDMM_0.14.1-b32.40_esp32_4MB_M.bin" validations: required: true - type: dropdown diff --git a/package-lock.json b/package-lock.json index b4bcd63e..80dcf5ec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "wled", - "version": "0.14.1-b31.38", + "version": "0.14.1-b32.40", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "wled", - "version": "0.14.1-b31.38", + "version": "0.14.1-b32.40", "license": "GPL-3.0-or-later", "dependencies": { "clean-css": "^4.2.3", diff --git a/package.json b/package.json index 347fec25..0536796c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wled", - "version": "0.14.1-b31.38", + "version": "0.14.1-b32.40", "description": "Tools for WLED project", "main": "tools/cdata.js", "directories": { diff --git a/wled00/improv.cpp b/wled00/improv.cpp index eef8ad82..d0b97182 100644 --- a/wled00/improv.cpp +++ b/wled00/improv.cpp @@ -211,7 +211,7 @@ void sendImprovInfoResponse() { //Use serverDescription if it has been changed from the default "WLED", else mDNS name bool useMdnsName = (strcmp(serverDescription, "WLED") == 0 && strlen(cmDNS) > 0); char vString[32]; - snprintf_P(vString, sizeof(vString)-1, PSTR("0.14.1-b31.38/%i"),VERSION); + snprintf_P(vString, sizeof(vString)-1, PSTR("0.14.1-b32.40/%i"),VERSION); const char *str[4] = {"WLED", vString, bString, useMdnsName ? cmDNS : serverDescription}; sendImprovRPCResult(ImprovRPCType::Request_Info, 4, str); diff --git a/wled00/wled.h b/wled00/wled.h index 96530ac2..2c7910a6 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -8,7 +8,7 @@ */ // version code in format yymmddb (b = daily build) -#define VERSION 2405241 +#define VERSION 2406230 // WLEDMM - you can check for this define in usermods, to only enabled WLEDMM specific code in the "right" fork. Its not defined in AC WLED. #define _MoonModules_WLED_ From 899899094f76d07528f525e83d5806c30f328c65 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Mon, 24 Jun 2024 17:01:43 +0200 Subject: [PATCH 019/200] sound sync: use last remaining gap to transmit soundPressure * use last remaining two bytes in audioSyncPacket for transmitting soundPressure * 0x0 is treated as "legacy value" --> soundPressure = volumeSmth; * decodeAudioData: ensure receivedPacket struct members are correctly aligned - strictly speaking it is not safe to cast a uint8_t* as it does not offer any alignment guarantees. * remove 8266 special handling in audioreactive::setup() --- usermods/audioreactive/audio_reactive.h | 88 +++++++++++++------------ 1 file changed, 47 insertions(+), 41 deletions(-) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index b958184c..753f2b54 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -1050,7 +1050,7 @@ class AudioReactive : public Usermod { // new "V2" audiosync struct - 44 Bytes struct __attribute__ ((packed)) audioSyncPacket { // WLEDMM "packed" ensures that there are no additional gaps char header[6]; // 06 Bytes offset 0 - uint8_t gap1[2]; // gap added by compiler: 02 Bytes, offset 6 + uint8_t pressure[2]; // 02 Bytes, offset 6 - sound pressure as fixed point (8bit integer, 8bit fraction) float sampleRaw; // 04 Bytes offset 8 - either "sampleRaw" or "rawSampleAgc" depending on soundAgc setting float sampleSmth; // 04 Bytes offset 12 - either "sampleAvg" or "sampleAgc" depending on soundAgc setting uint8_t samplePeak; // 01 Bytes offset 16 - 0 no peak; >=1 peak detected. In future, this will also provide peak Magnitude @@ -1065,8 +1065,8 @@ class AudioReactive : public Usermod { struct audioSyncPacket_v1 { char header[6]; // 06 Bytes uint8_t myVals[32]; // 32 Bytes - int sampleAgc; // 04 Bytes - int sampleRaw; // 04 Bytes + int32_t sampleAgc; // 04 Bytes + int32_t sampleRaw; // 04 Bytes float sampleAvg; // 04 Bytes bool samplePeak; // 01 Bytes uint8_t fftResult[16]; // 16 Bytes @@ -1602,6 +1602,14 @@ class AudioReactive : public Usermod { transmitData.fftResult[i] = fftResult[i]; } + // WLEDMM transmit soundPressure as 16 bit fixed point + uint32_t pressure16bit = max(0.0f, soundPressure) * 256.0f; // convert to fixed point, remove negative values + uint16_t pressInt = pressure16bit / 256; // integer part + uint16_t pressFract = pressure16bit % 256; // faction part + if (pressInt > 255) pressInt = 255; // saturation at 255 + transmitData.pressure[0] = (uint8_t)pressInt; + transmitData.pressure[1] = (uint8_t)pressFract; + transmitData.FFT_Magnitude = my_magnitude; transmitData.FFT_MajorPeak = FFT_MajorPeak; @@ -1622,30 +1630,33 @@ class AudioReactive : public Usermod { bool decodeAudioData(int packetSize, uint8_t *fftBuff) { if((0 == packetSize) || (nullptr == fftBuff)) return false; // sanity check - audioSyncPacket *receivedPacket = reinterpret_cast(fftBuff); + //audioSyncPacket *receivedPacket = reinterpret_cast(fftBuff); + audioSyncPacket receivedPacket; + memset(&receivedPacket, 0, sizeof(receivedPacket)); // start clean + memcpy(&receivedPacket, fftBuff, min((unsigned)packetSize, (unsigned)sizeof(receivedPacket))); // don't violate alignment - thanks @willmmiles // 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+ + 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(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); + 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; + lastFrameCounter = receivedPacket.frameCounter; } // update samples for effects - volumeSmth = fmaxf(receivedPacket->sampleSmth, 0.0f); - volumeRaw = fmaxf(receivedPacket->sampleRaw, 0.0f); + volumeSmth = fmaxf(receivedPacket.sampleSmth, 0.0f); + volumeRaw = fmaxf(receivedPacket.sampleRaw, 0.0f); #ifdef ARDUINO_ARCH_ESP32 // update internal samples sampleRaw = volumeRaw; @@ -1658,18 +1669,26 @@ class AudioReactive : public Usermod { // If it's true already, then the animation still needs to respond. autoResetPeak(); if (!samplePeak) { - samplePeak = receivedPacket->samplePeak >0 ? true:false; + samplePeak = receivedPacket.samplePeak >0 ? true:false; if (samplePeak) timeOfPeak = millis(); //userVar1 = samplePeak; } //These values are only computed by ESP32 - for (int i = 0; i < NUM_GEQ_CHANNELS; i++) fftResult[i] = receivedPacket->fftResult[i]; - my_magnitude = fmaxf(receivedPacket->FFT_Magnitude, 0.0f); + 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; - FFT_MajorPeak = constrain(receivedPacket->FFT_MajorPeak, 1.0f, 11025.0f); // restrict value to range expected by effects - soundPressure = volumeSmth; // substitute - V2 format does not (yet) include this value - agcSensitivity = 128.0f; // substitute - V2 format does not (yet) include this value - zeroCrossingCount = receivedPacket->zeroCrossingCount; + FFT_MajorPeak = constrain(receivedPacket.FFT_MajorPeak, 1.0f, 11025.0f); // restrict value to range expected by effects + agcSensitivity = 128.0f; // substitute - V2 format does not include this value + zeroCrossingCount = receivedPacket.zeroCrossingCount; + + // WLEDMM extract soundPressure + if ((receivedPacket.pressure[0] != 0) || (receivedPacket.pressure[1] != 0)) { + // found something in gap "reserved2" + soundPressure = float(receivedPacket.pressure[1]) / 256.0f; // fractional part + soundPressure += float(receivedPacket.pressure[0]); // integer part + } else { + soundPressure = volumeSmth; // fallback + } return true; } @@ -1786,36 +1805,23 @@ class AudioReactive : public Usermod { um_data->u_type[4] = UMT_FLOAT; um_data->u_data[5] = &my_magnitude; // used (New) um_data->u_type[5] = UMT_FLOAT; -#ifdef ARDUINO_ARCH_ESP32 um_data->u_data[6] = &maxVol; // assigned in effect function from UI element!!! (Puddlepeak, Ripplepeak, Waterfall) um_data->u_type[6] = UMT_BYTE; um_data->u_data[7] = &binNum; // assigned in effect function from UI element!!! (Puddlepeak, Ripplepeak, Waterfall) um_data->u_type[7] = UMT_BYTE; +#ifdef ARDUINO_ARCH_ESP32 um_data->u_data[8] = &FFT_MajPeakSmth; // new um_data->u_type[8] = UMT_FLOAT; +#else + um_data->u_data[8] = &FFT_MajorPeak; // substitute for 8266 + um_data->u_type[8] = UMT_FLOAT; +#endif um_data->u_data[9] = &soundPressure; // used (New) um_data->u_type[9] = UMT_FLOAT; - um_data->u_data[10] = &agcSensitivity; // used (New) + um_data->u_data[10] = &agcSensitivity; // used (New) - dummy value on 8266 um_data->u_type[10] = UMT_FLOAT; - um_data->u_data[11] = &zeroCrossingCount; + um_data->u_data[11] = &zeroCrossingCount; // for auto playlist usermod um_data->u_type[11] = UMT_UINT16; -#else - // ESP8266 - // 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; - um_data->u_data[7] = &binNum; // assigned in effect function from UI element!!! (Puddlepeak, Ripplepeak, Waterfall) - um_data->u_type[7] = UMT_BYTE; - um_data->u_data[8] = &FFT_MajorPeak; // new - substitute for FFT_MajPeakSmth - um_data->u_type[8] = UMT_FLOAT; - um_data->u_data[9] = &volumeSmth; // used (New) - substitute for soundPressure - um_data->u_type[9] = UMT_FLOAT; - um_data->u_data[10] = &agcSensitivity; // used (New) - dummy value (128 => 50%) - um_data->u_type[10] = UMT_FLOAT; - um_data->u_data[11] = &zeroCrossingCount; - um_data->u_type[11] = UMT_UINT16; -#endif } #ifdef ARDUINO_ARCH_ESP32 @@ -2201,7 +2207,7 @@ class AudioReactive : public Usermod { volumeSmth =0.0f; volumeRaw =0; my_magnitude = 0.1; FFT_Magnitude = 0.01; FFT_MajorPeak = 2; - soundPressure = 1.0f; + soundPressure = 1.0f; agcSensitivity = 64.0f; #ifdef ARDUINO_ARCH_ESP32 multAgc = 1; From c876267c95b0dd2e29251d69e4c8f9c6f7423027 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Mon, 24 Jun 2024 17:14:09 +0200 Subject: [PATCH 020/200] Update audio_reactive.h - debug fix --- usermods/audioreactive/audio_reactive.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index 753f2b54..52f8e17f 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -1981,7 +1981,7 @@ class AudioReactive : public Usermod { USER_PRINTF("\naudioSyncPacket_v1 size = %d\n", sizeof(audioSyncPacket_v1)); // size 88 USER_PRINTF("audioSyncPacket size = %d\n", sizeof(audioSyncPacket)); // size 44 USER_PRINTF("| char header[6] offset = %2d size = %2d\n", offsetof(audioSyncPacket, header[0]), sizeof(data.header)); // offset 0 size 6 - USER_PRINTF("| uint8_t gap1[2] offset = %2d size = %2d\n", offsetof(audioSyncPacket, gap1[0]), sizeof(data.gap1)); // offset 6 size 2 + USER_PRINTF("| uint8_t pressure[2] offset = %2d size = %2d\n", offsetof(audioSyncPacket, pressure[0]), sizeof(data.pressure)); // offset 6 size 2 USER_PRINTF("| float sampleRaw offset = %2d size = %2d\n", offsetof(audioSyncPacket, sampleRaw), sizeof(data.sampleRaw)); // offset 8 size 4 USER_PRINTF("| float sampleSmth offset = %2d size = %2d\n", offsetof(audioSyncPacket, sampleSmth), sizeof(data.sampleSmth)); // offset 12 size 4 USER_PRINTF("| uint8_t samplePeak offset = %2d size = %2d\n", offsetof(audioSyncPacket, samplePeak), sizeof(data.samplePeak)); // offset 16 size 1 From 2510292d1a8eb099f95aa4307405ed1dad6f892c Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Mon, 24 Jun 2024 18:04:18 +0200 Subject: [PATCH 021/200] ws preview: better handling of RGBW avoid over-saturation in preview --- wled00/ws.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/wled00/ws.cpp b/wled00/ws.cpp index 32420fcc..5b8bda42 100644 --- a/wled00/ws.cpp +++ b/wled00/ws.cpp @@ -277,9 +277,10 @@ static bool sendLiveLedsWs(uint32_t wsClient) // WLEDMM added "static" // WLEDMM begin: preview with color gamma correction if (gammaCorrectPreview) { uint8_t w = W(c); // not sure why, but it looks better if using "white" without corrections - buffer[pos++] = qadd8(w, unGamma8(R(c))); //R, add white channel to RGB channels as a simple RGBW -> RGB map - buffer[pos++] = qadd8(w, unGamma8(G(c))); //G - buffer[pos++] = qadd8(w, unGamma8(B(c))); //B + if (w>0) c = color_add(c, RGBW32(w, w, w, 0), false); // add white channel to RGB channels - color_add() will prevent over-saturation + buffer[pos++] = unGamma8(R(c)); //R + buffer[pos++] = unGamma8(G(c)); //G + buffer[pos++] = unGamma8(B(c)); //B } else { // WLEDMM end uint8_t w = W(c); // WLEDMM small optimization From bc006268ae2209e9c5d24737bc3445b7d8593e7c Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 24 Jun 2024 21:50:49 +0200 Subject: [PATCH 022/200] new MM build for -C3 with 2MB flash (no OTA) --- platformio.ini | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/platformio.ini b/platformio.ini index 5ada7d47..7d74b2a3 100644 --- a/platformio.ini +++ b/platformio.ini @@ -80,6 +80,7 @@ default_envs = ;; esp32s2_tinyUF2_PSRAM_S ;; experimental - only for adafruit -S2 boards with tinyUF2 bootloader !!! esp32s2_PSRAM_M ;; experimental esp32c3dev_4MB_M ;; experimental + esp32c3dev_2MB_M ;; experimental - 2MB Flash, no OTA esp32c3mini_dio_4MB_M ;; for boards that need "dio" flash mode (instead of qio) seeed_esp32c3_4MB_S ;; experimental esp32_4MB_V4_S ;; experimental @@ -2180,6 +2181,33 @@ build_flags = ${env:esp32c3dev_4MB_M.build_flags} ; RAM: [=== ] 25.8% (used 84700 bytes from 327680 bytes) ; Flash: [==========] 98.7% (used 1552582 bytes from 1572864 bytes) +[env:esp32c3dev_2MB_M] +extends = env:esp32c3dev_4MB_M +board = lolin_c3_mini +;;; replace WLED_RELEASE_NAME, disable CDC_ON_BOOT +build_unflags = ${env:esp32c3dev_4MB_M.build_unflags} + -DARDUINO_USB_CDC_ON_BOOT=1 + -D WLED_RELEASE_NAME=esp32c3dev_4MB_M + +;;; 2MB Flash, no OTA +board_build.partitions = tools/WLED_ESP32_2MB_noOTA.csv +board_build.flash_mode = dio +board_upload.flash_size = 2MB +board_upload.maximum_size = 2097152 + +build_flags = ${env:esp32c3dev_4MB_M.build_flags} + -D WLED_WATCHDOG_TIMEOUT=0 + -D WLED_DISABLE_OTA + ; -DARDUINO_USB_CDC_ON_BOOT=1 ;; for virtual CDC USB + -DARDUINO_USB_CDC_ON_BOOT=0 ;; for serial-to-USB chip + -D WLED_RELEASE_NAME=esp32c3dev_2MB_M + -D WLED_DISABLE_BROWNOUT_DET ;; the board only has a 500mA LDO, better to disable brownout detection + -D WLED_DISABLE_ADALIGHT ;; to disable serial protocols for boards with CDC USB (Serial RX will receive junk commands, unless its pulled down by resistor) + -D HW_PIN_SDA=0 -D HW_PIN_SCL=1 ;; avoid pin conflicts + +; RAM: [=== ] 25.3% (used 82828 bytes from 327680 bytes) +; Flash: [==========] 97.9% (used 1540138 bytes from 1572864 bytes) + ;; MM environment for "seeed xiao -C3" boards [env:seeed_esp32c3_4MB_S] extends = env:esp32c3dev_4MB_M From c67d5dbdc4df3f96d457bde54d61b78a1637ec38 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 24 Jun 2024 21:51:34 +0200 Subject: [PATCH 023/200] build 2406240 - release v0.14.1-b32 --- wled00/wled.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/wled.h b/wled00/wled.h index 2c7910a6..8ff90dd5 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -8,7 +8,7 @@ */ // version code in format yymmddb (b = daily build) -#define VERSION 2406230 +#define VERSION 2406240 // WLEDMM - you can check for this define in usermods, to only enabled WLEDMM specific code in the "right" fork. Its not defined in AC WLED. #define _MoonModules_WLED_ From 2dd9a8232e614336537a9d3734dbb3222e8ef493 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Mon, 24 Jun 2024 22:32:33 +0200 Subject: [PATCH 024/200] kicking gh build re-run --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 7d74b2a3..6ae4c790 100644 --- a/platformio.ini +++ b/platformio.ini @@ -84,7 +84,7 @@ default_envs = esp32c3mini_dio_4MB_M ;; for boards that need "dio" flash mode (instead of qio) seeed_esp32c3_4MB_S ;; experimental esp32_4MB_V4_S ;; experimental - esp32_16MB_V4_S ;; experimental, optimized for speed + esp32_16MB_V4_S ;; experimental - optimized for speed esp32_16MB_V4_M ;; experimental esp32_16MB_V4_M_debug ;; experimental esp32_pico_4MB_V4_S ;; experimental - may work better in case you experience wifi connectivity problems From 93ec9b915116dd6d8efbe5185f9a849e98624b85 Mon Sep 17 00:00:00 2001 From: Troy <5659019+troyhacks@users.noreply.github.com> Date: Tue, 25 Jun 2024 23:28:33 -0400 Subject: [PATCH 025/200] Stop double first pixel on waterfall effect --- wled00/FX.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index d9047e7d..416884cf 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -7794,13 +7794,14 @@ uint16_t mode_waterfall(void) { // Waterfall. By: Andrew Tulin uint8_t pixCol = (log10f(FFT_MajorPeak) - 2.26f) * 150; // 22Khz sampling - log10 frequency range is from 2.26 (182hz) to 3.967 (9260hz). Let's scale accordingly. if (FFT_MajorPeak < 182.0f) pixCol = 0; // handle underflow + // loop will not execute if SEGLEN equals 1 + for (int i = 0; i < SEGLEN-1; i++) SEGMENT.setPixelColor(i, SEGMENT.getPixelColor(i+1)); // shift left + if (samplePeak) { SEGMENT.setPixelColor(SEGLEN-1, CHSV(92,92,92)); } else { SEGMENT.setPixelColor(SEGLEN-1, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(pixCol+SEGMENT.intensity, false, PALETTE_SOLID_WRAP, 0), (int)my_magnitude)); } - // loop will not execute if SEGLEN equals 1 - for (int i = 0; i < SEGLEN-1; i++) SEGMENT.setPixelColor(i, SEGMENT.getPixelColor(i+1)); // shift left } return FRAMETIME; From 9103f6a4dfe4ed7e93b18bd4736496f8e66b5230 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Sat, 16 Mar 2024 12:07:26 -0400 Subject: [PATCH 026/200] Update for new AsyncWebSocketBuffer Eliminate the extra indirection and allocate shared buffers directly. --- wled00/ws.cpp | 65 ++++++++++++++------------------------------------- 1 file changed, 18 insertions(+), 47 deletions(-) diff --git a/wled00/ws.cpp b/wled00/ws.cpp index 5b8bda42..22c5afd2 100644 --- a/wled00/ws.cpp +++ b/wled00/ws.cpp @@ -108,7 +108,6 @@ void sendDataWs(AsyncWebSocketClient * client) { DEBUG_PRINTF("sendDataWs\n"); if (!ws.count()) return; - AsyncWebSocketMessageBuffer * buffer; if (!requestJSONBufferLock(12)) { if (client) { @@ -138,17 +137,7 @@ void sendDataWs(AsyncWebSocketClient * client) // DEBUG_PRINTF("%s min free stack %d\n", pcTaskGetTaskName(NULL), uxTaskGetStackHighWaterMark(NULL)); //WLEDMM #endif if (len < 1) return; // WLEDMM do not allocate 0 size buffer - - // WLEDMM use exceptions to catch out-of-memory errors - #if __cpp_exceptions - try{ - buffer = ws.makeBuffer(len); // will not allocate correct memory sometimes on ESP8266 - } catch(...) { - buffer = nullptr; - } - #else - buffer = ws.makeBuffer(len); // will not allocate correct memory sometimes on ESP8266 - #endif + AsyncWebSocketBuffer buffer(len); #ifdef ESP8266 size_t heap2 = ESP.getFreeHeap(); DEBUG_PRINT(F("heap ")); DEBUG_PRINTLN(ESP.getFreeHeap()); @@ -161,24 +150,19 @@ void sendDataWs(AsyncWebSocketClient * client) USER_PRINTLN(F("WS buffer allocation failed.")); ws.closeAll(1013); //code 1013 = temporary overload, try again later ws.cleanupClients(0); //disconnect all clients to release memory - ws._cleanBuffers(); errorFlag = ERR_LOW_WS_MEM; return; //out of memory } - - buffer->lock(); - serializeJson(doc, (char *)buffer->get(), len); + serializeJson(doc, (char *)buffer.data(), len); DEBUG_PRINT(F("Sending WS data ")); if (client) { - client->text(buffer); + client->text(std::move(buffer)); DEBUG_PRINTLN(F("to a single client.")); } else { - ws.textAll(buffer); + ws.textAll(std::move(buffer)); DEBUG_PRINTLN(F("to multiple clients.")); } - buffer->unlock(); - ws._cleanBuffers(); releaseJSONBufferLock(); } @@ -227,32 +211,21 @@ static bool sendLiveLedsWs(uint32_t wsClient) // WLEDMM added "static" #endif size_t pos = (strip.isMatrix ? 4 : 2); size_t bufSize = pos + (used/n)*3; - - if ((bufSize < 1) || (used < 1)) return(false); // WLEDMM should not happen - //AsyncWebSocketMessageBuffer * wsBuf = ws.makeBuffer(bufSize); - // WLEDMM protect against exceptions due to low memory - AsyncWebSocketMessageBuffer * wsBuf = nullptr; -#if __cpp_exceptions - try{ -#endif - wsBuf = ws.makeBuffer(bufSize); -#if __cpp_exceptions - } catch(...) { -#else - if (wsBuf == nullptr) { // 8266 does not support exceptions -#endif - wsBuf = nullptr; - USER_PRINTLN(F("WS buffer allocation failed.")); - //ws.closeAll(1013); //code 1013 = temporary overload, try again later - //ws.cleanupClients(0); //disconnect all clients to release memory - ws._cleanBuffers(); - } - if (!wsBuf) return false; //out of memory - uint8_t* buffer = wsBuf->get(); - if (!buffer) return false; //out of memory + if ((bufSize < 1) || (used < 1)) return(false); // WLEDMM should not happen + AsyncWebSocketBuffer wsBuf(bufSize); + if (!wsBuf) { + USER_PRINTLN(F("WS buffer allocation failed.")); + errorFlag = ERR_LOW_WS_MEM; + return false; //out of memory + } + uint8_t* buffer = reinterpret_cast(wsBuf.data()); + if (!buffer) { + USER_PRINTLN(F("WS buffer allocation failed.")); + errorFlag = ERR_LOW_WS_MEM; + return false; //out of memory + } - wsBuf->lock(); // protect buffer from being cleaned by another WS instance buffer[0] = 'L'; buffer[1] = 1; //version #ifndef WLED_DISABLE_2D @@ -290,9 +263,7 @@ static bool sendLiveLedsWs(uint32_t wsClient) // WLEDMM added "static" } } - wsc->binary(wsBuf); - wsBuf->unlock(); // un-protect buffer - ws._cleanBuffers(); // cleans up if the message is not added to any clients. + wsc->binary(std::move(wsBuf)); return true; } From 775e07c6d6e210754c83c9826b05dbc3c1850b23 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Thu, 14 Mar 2024 22:06:51 -0400 Subject: [PATCH 027/200] serveLiveLeds: Use dynamic buffer There were three problems here: - AsyncWebServer is going to copy to a heap buffer anyways, so we might as well just pass it one it can use - The buffer size estimate was wrong -- we need 9 bytes per pixel ("RRGGBB",), so the buffer could overflow, and it was not considering the extra 2D requirements - On ESP8266, the stack allocation was overflowing the stack, causing corruption and crashes. --- wled00/json.cpp | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/wled00/json.cpp b/wled00/json.cpp index 5456b455..020c46c3 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -1554,10 +1554,20 @@ bool serveLiveLeds(AsyncWebServerRequest* request, uint32_t wsClient) uint16_t used = strip.getLengthTotal(); uint16_t n = (used -1) /MAX_LIVE_LEDS +1; //only serve every n'th LED if count over MAX_LIVE_LEDS - char buffer[2000]; - strcpy_P(buffer, PSTR("{\"leds\":[")); - obuf = buffer; - olen = 9; +#ifndef WLED_DISABLE_2D + if (strip.isMatrix) { + // ignore anything behid matrix (i.e. extra strip) + used = Segment::maxWidth*Segment::maxHeight; // always the size of matrix (more or less than strip.getLengthTotal()) + n = 1; + if (used > MAX_LIVE_LEDS) n = 2; + if (used > MAX_LIVE_LEDS*4) n = 4; + } +#endif + + DynamicBuffer buffer(9 + (9*MAX_LIVE_LEDS) + 7 + 5 + 6 + 5 + 6 + 5 + 2); + char* buf = buffer.data(); // assign buffer for oappnd() functions + strncpy_P(buffer.data(), PSTR("{\"leds\":["), buffer.size()); + buf += 9; // sizeof(PSTR()) from last line for (size_t i= 0; i < used; i += n) { @@ -1575,20 +1585,21 @@ bool serveLiveLeds(AsyncWebServerRequest* request, uint32_t wsClient) g = qadd8(w, G(c)); b = qadd8(w, B(c)); } - olen += sprintf(obuf + olen, "\"%06X\",", RGBW32(r,g,b,0)); + buf += sprintf_P(buf, PSTR("\"%06X\","), RGBW32(r,g,b,0)); } - olen -= 1; - oappend((const char*)F("],\"n\":")); - oappendi(n); - oappend("}"); + buf--; // remove last comma + buf += sprintf_P(buf, PSTR("],\"n\":%d"), n); + (*buf++) = '}'; + (*buf++) = 0; + if (request) { - request->send(200, "application/json", buffer); + request->send(200, "application/json", toString(std::move(buffer))); } #ifdef WLED_ENABLE_WEBSOCKETS else { - wsc->text(obuf, olen); + wsc->text(toString(std::move(buffer))); } - #endif + #endif return true; } #endif From 543dfd206d59194e9ff14cabeda2ae4d3ad25c42 Mon Sep 17 00:00:00 2001 From: thatdonfc Date: Tue, 19 Mar 2024 14:04:24 -0700 Subject: [PATCH 028/200] Fix palette names when palette ID > 58 and not custom --- wled00/util.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wled00/util.cpp b/wled00/util.cpp index 896b57e3..bb8715d7 100644 --- a/wled00/util.cpp +++ b/wled00/util.cpp @@ -255,8 +255,8 @@ uint8_t extractModeName(uint8_t mode, const char *src, char *dest, uint8_t maxLe } else return 0; } - if (src == JSON_palette_names && mode > GRADIENT_PALETTE_COUNT) { - snprintf_P(dest, maxLen, PSTR("~ Custom %d~"), 255-mode); + if (src == JSON_palette_names && mode > (GRADIENT_PALETTE_COUNT + 13)) { + snprintf_P(dest, maxLen, PSTR("~ Custom %d ~"), 255-mode); dest[maxLen-1] = '\0'; return strlen(dest); } From 3df3b9acafe8487e215b0ebe529774dad7263c86 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Wed, 17 Apr 2024 18:52:35 +0200 Subject: [PATCH 029/200] ArduinoFFT update shadow variables --- usermods/multi_relay/usermod_multi_relay.h | 2 +- wled00/json.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/usermods/multi_relay/usermod_multi_relay.h b/usermods/multi_relay/usermod_multi_relay.h index cf0aca22..dd47e767 100644 --- a/usermods/multi_relay/usermod_multi_relay.h +++ b/usermods/multi_relay/usermod_multi_relay.h @@ -626,7 +626,7 @@ void MultiRelay::addToJsonInfo(JsonObject &root) { for (int i=0; i()) { From c5283da32aa23f5dbbb7d84618b28917a7377914 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Wed, 24 Apr 2024 16:04:54 +0200 Subject: [PATCH 030/200] Fix for #3878 --- usermods/BH1750_v2/usermod_bh1750.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usermods/BH1750_v2/usermod_bh1750.h b/usermods/BH1750_v2/usermod_bh1750.h index 5b5fa25f..a6e7d653 100644 --- a/usermods/BH1750_v2/usermod_bh1750.h +++ b/usermods/BH1750_v2/usermod_bh1750.h @@ -76,7 +76,7 @@ private: bool sensorFound = false; // Home Assistant and MQTT - String mqttLuminanceTopic = FPSTR(""); + String mqttLuminanceTopic; bool mqttInitialized = false; bool HomeAssistantDiscovery = true; // Publish Home Assistant Discovery messages From 74e999b1e5c5bcb3b8854821b102ae220ad80f96 Mon Sep 17 00:00:00 2001 From: Pasquale Pizzuti Date: Tue, 30 Apr 2024 14:09:12 +0200 Subject: [PATCH 031/200] using brightness in analog clock overlay --- wled00/overlay.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/wled00/overlay.cpp b/wled00/overlay.cpp index cfee7e81..96b15053 100644 --- a/wled00/overlay.cpp +++ b/wled00/overlay.cpp @@ -11,6 +11,7 @@ void _overlayAnalogClock() { _overlayAnalogCountdown(); return; } + uint8_t brightness = strip.getBrightness(); float hourP = ((float)(hour(localTime)%12))/12.0f; float minuteP = ((float)minute(localTime))/60.0f; hourP = hourP + minuteP/12.0f; @@ -25,11 +26,11 @@ void _overlayAnalogClock() { if (secondPixel < analogClock12pixel) { - strip.setRange(analogClock12pixel, overlayMax, 0xFF0000); - strip.setRange(overlayMin, secondPixel, 0xFF0000); + strip.setRange(analogClock12pixel, overlayMax, (uint32_t)brightness<<16); + strip.setRange(overlayMin, secondPixel, (uint32_t)brightness<<16); } else { - strip.setRange(analogClock12pixel, secondPixel, 0xFF0000); + strip.setRange(analogClock12pixel, secondPixel, (uint32_t)brightness<<16); } } if (analogClock5MinuteMarks) @@ -38,12 +39,12 @@ void _overlayAnalogClock() { int pix = analogClock12pixel + roundf((overlaySize / 12.0f) *i); if (pix > overlayMax) pix -= overlaySize; - strip.setPixelColor(pix, 0x00FFAA); + strip.setPixelColor(pix, ((uint32_t)brightness<<8)|((uint32_t)brightness*2/3)); } } - if (!analogClockSecondsTrail) strip.setPixelColor(secondPixel, 0xFF0000); - strip.setPixelColor(minutePixel, 0x00FF00); - strip.setPixelColor(hourPixel, 0x0000FF); + if (!analogClockSecondsTrail) strip.setPixelColor(secondPixel, (uint32_t)brightness<<16); + strip.setPixelColor(minutePixel, (uint32_t)brightness<<8); + strip.setPixelColor(hourPixel, (uint32_t)brightness); } From feac33653f72aea364d6f3b2e83042e492765f61 Mon Sep 17 00:00:00 2001 From: Woody Date: Tue, 30 Apr 2024 16:53:47 +0200 Subject: [PATCH 032/200] Fix resizing bug The bug was that when resizing the window, it always jumped to the Colors tab instead of staying on the currently selected tab. --- wled00/data/index.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/wled00/data/index.js b/wled00/data/index.js index 1fcbfcaf..d193a730 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -279,7 +279,6 @@ function updateTablinks(tabI) { var tablinks = gEBCN("tablinks"); for (var i of tablinks) i.classList.remove('active'); - if (pcMode) return; tablinks[tabI].classList.add('active'); } @@ -3615,12 +3614,11 @@ function togglePcMode(fromB = false) if (fromB) { pcModeA = !pcModeA; localStorage.setItem('pcm', pcModeA); + openTab(0, true); } pcMode = (wW >= 1024) && pcModeA; if (cpick) cpick.resize(pcMode && wW>1023 && wW<1250 ? 230 : 260); // for tablet in landscape if (!fromB && ((wW < 1024 && lastw < 1024) || (wW >= 1024 && lastw >= 1024))) return; // no change in size and called from size() - openTab(0, true); - updateTablinks(0); gId('buttonPcm').className = (pcMode) ? "active":""; gId('bot').style.height = (pcMode && !cfg.comp.pcmbot) ? "0":"auto"; sCol('--bh', gId('bot').clientHeight + "px"); From b01f3118b441f4ca40e49c5de99021b7f45a8efe Mon Sep 17 00:00:00 2001 From: Pasquale Pizzuti Date: Tue, 30 Apr 2024 17:52:35 +0200 Subject: [PATCH 033/200] using color_fade --- wled00/overlay.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/wled00/overlay.cpp b/wled00/overlay.cpp index 96b15053..71f7e081 100644 --- a/wled00/overlay.cpp +++ b/wled00/overlay.cpp @@ -26,11 +26,11 @@ void _overlayAnalogClock() { if (secondPixel < analogClock12pixel) { - strip.setRange(analogClock12pixel, overlayMax, (uint32_t)brightness<<16); - strip.setRange(overlayMin, secondPixel, (uint32_t)brightness<<16); + strip.setRange(analogClock12pixel, overlayMax, color_fade(0xFF0000, brightness)); + strip.setRange(overlayMin, secondPixel, color_fade(0xFF0000, brightness)); } else { - strip.setRange(analogClock12pixel, secondPixel, (uint32_t)brightness<<16); + strip.setRange(analogClock12pixel, secondPixel, color_fade(0xFF0000, brightness)); } } if (analogClock5MinuteMarks) @@ -39,12 +39,12 @@ void _overlayAnalogClock() { int pix = analogClock12pixel + roundf((overlaySize / 12.0f) *i); if (pix > overlayMax) pix -= overlaySize; - strip.setPixelColor(pix, ((uint32_t)brightness<<8)|((uint32_t)brightness*2/3)); + strip.setPixelColor(pix, color_fade(0x00FFAA, brightness)); } } - if (!analogClockSecondsTrail) strip.setPixelColor(secondPixel, (uint32_t)brightness<<16); - strip.setPixelColor(minutePixel, (uint32_t)brightness<<8); - strip.setPixelColor(hourPixel, (uint32_t)brightness); + if (!analogClockSecondsTrail) strip.setPixelColor(secondPixel, color_fade(0xFF0000, brightness)); + strip.setPixelColor(minutePixel, color_fade(0x00FF00, brightness)); + strip.setPixelColor(hourPixel, color_fade(0x0000FF, brightness)); } From 383c7977468864a0209962559eec389025343a41 Mon Sep 17 00:00:00 2001 From: Woody Date: Tue, 30 Apr 2024 18:57:53 +0200 Subject: [PATCH 034/200] add Webpage shortcuts, resolves #2362 --- wled00/data/index.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/wled00/data/index.js b/wled00/data/index.js index d193a730..e89ad78e 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -247,6 +247,7 @@ function onLoad() selectSlot(0); updateTablinks(0); + handleLocationHash(); pmtLS = localStorage.getItem('wledPmt'); // Load initial data @@ -289,6 +290,21 @@ function openTab(tabI, force = false) _C.classList.toggle('smooth', false); _C.style.setProperty('--i', iSlide); updateTablinks(tabI); + switch (tabI) { + case 0: window.location.hash = "Colors"; break; + case 1: window.location.hash = "Effects"; break; + case 2: window.location.hash = "Segments"; break; + case 3: window.location.hash = "Presets"; break; + } +} + +function handleLocationHash() { + switch (window.location.hash) { + case "#Colors": openTab(0); break; + case "#Effects": openTab(1); break; + case "#Segments": openTab(2); break; + case "#Presets": openTab(3); break; + } } var timeout; @@ -3650,6 +3666,7 @@ size(); _C.style.setProperty('--n', N); window.addEventListener('resize', size, true); +window.addEventListener('hashchange', handleLocationHash); _C.addEventListener('mousedown', lock, false); _C.addEventListener('touchstart', lock, false); From c22c5b75a092e30e77ac7f9c48449cf151175247 Mon Sep 17 00:00:00 2001 From: Pasquale Pizzuti Date: Thu, 2 May 2024 09:04:07 +0200 Subject: [PATCH 035/200] using global brightness --- wled00/overlay.cpp | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/wled00/overlay.cpp b/wled00/overlay.cpp index 71f7e081..6ffc6c8e 100644 --- a/wled00/overlay.cpp +++ b/wled00/overlay.cpp @@ -11,7 +11,6 @@ void _overlayAnalogClock() { _overlayAnalogCountdown(); return; } - uint8_t brightness = strip.getBrightness(); float hourP = ((float)(hour(localTime)%12))/12.0f; float minuteP = ((float)minute(localTime))/60.0f; hourP = hourP + minuteP/12.0f; @@ -26,11 +25,11 @@ void _overlayAnalogClock() { if (secondPixel < analogClock12pixel) { - strip.setRange(analogClock12pixel, overlayMax, color_fade(0xFF0000, brightness)); - strip.setRange(overlayMin, secondPixel, color_fade(0xFF0000, brightness)); + strip.setRange(analogClock12pixel, overlayMax, color_fade(0xFF0000, bri)); + strip.setRange(overlayMin, secondPixel, color_fade(0xFF0000, bri)); } else { - strip.setRange(analogClock12pixel, secondPixel, color_fade(0xFF0000, brightness)); + strip.setRange(analogClock12pixel, secondPixel, color_fade(0xFF0000, bri)); } } if (analogClock5MinuteMarks) @@ -39,12 +38,12 @@ void _overlayAnalogClock() { int pix = analogClock12pixel + roundf((overlaySize / 12.0f) *i); if (pix > overlayMax) pix -= overlaySize; - strip.setPixelColor(pix, color_fade(0x00FFAA, brightness)); + strip.setPixelColor(pix, color_fade(0x00FFAA, bri)); } } - if (!analogClockSecondsTrail) strip.setPixelColor(secondPixel, color_fade(0xFF0000, brightness)); - strip.setPixelColor(minutePixel, color_fade(0x00FF00, brightness)); - strip.setPixelColor(hourPixel, color_fade(0x0000FF, brightness)); + if (!analogClockSecondsTrail) strip.setPixelColor(secondPixel, color_fade(0xFF0000, bri)); + strip.setPixelColor(minutePixel, color_fade(0x00FF00, bri)); + strip.setPixelColor(hourPixel, color_fade(0x0000FF, bri)); } From 87b5badd5b20f5db50ecd6a9a2840d0f77e6ae72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Kristan?= Date: Fri, 3 May 2024 09:56:14 +0200 Subject: [PATCH 036/200] Merge pull request #3944 from paspiz85/pas4 Using brightness in analog clock overlay From 2aad3f535618e5c58eda9728c88390e4932bf395 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Fri, 10 May 2024 00:02:28 +0200 Subject: [PATCH 037/200] Antialiased line & circle --- wled00/FX.cpp | 6 +- wled00/FX.h | 23 +++-- wled00/FX_2Dfcn.cpp | 217 +++++++++++++++++++++++++++----------------- 3 files changed, 155 insertions(+), 91 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 416884cf..01fd923c 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -2551,7 +2551,7 @@ uint16_t ripple_base() uint16_t cx = rippleorigin >> 8; uint16_t cy = rippleorigin & 0xFF; uint8_t mag = scale8(sin8((propF>>2)), amp); - if (propI > 0) SEGMENT.draw_circle(cx, cy, propI, color_blend(SEGMENT.getPixelColorXY(cx + propI, cy), col, mag)); + if (propI > 0) SEGMENT.drawCircle(cx, cy, propI, color_blend(SEGMENT.getPixelColorXY(cx + propI, cy), col, mag), true); } else #endif { @@ -6271,8 +6271,8 @@ uint16_t mode_2Dfloatingblobs(void) { } } uint32_t c = SEGMENT.color_from_palette(blob->color[i], false, false, 0); - if (blob->r[i] > 1.f) SEGMENT.fill_circle(blob->x[i], blob->y[i], roundf(blob->r[i]), c); - else SEGMENT.setPixelColorXY(blob->x[i], blob->y[i], c); + if (blob->r[i] > 1.f) SEGMENT.fillCircle(roundf(blob->x[i]), roundf(blob->y[i]), roundf(blob->r[i]), c); + else SEGMENT.setPixelColorXY((int)roundf(blob->x[i]), (int)roundf(blob->y[i]), c); // move x if (blob->x[i] + blob->r[i] >= cols - 1) blob->x[i] += (blob->sX[i] * ((cols - 1 - blob->x[i]) / blob->r[i] + 0.005f)); else if (blob->x[i] - blob->r[i] <= 0) blob->x[i] += (blob->sX[i] * (blob->x[i] / blob->r[i] + 0.005f)); diff --git a/wled00/FX.h b/wled00/FX.h index b8864db4..b53f3fce 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -122,6 +122,10 @@ bool strip_uses_global_leds(void); // WLEDMM implemented in FX_fcn. #define PURPLE (uint32_t)0x400080 #define ORANGE (uint32_t)0xFF3000 #define PINK (uint32_t)0xFF1493 +#define GREY (uint32_t)0x808080 +#define GRAY GREY +#define DARKGREY (uint32_t)0x333333 +#define DARKGRAY DARKGREY #define ULTRAWHITE (uint32_t)0xFFFFFFFF #define DARKSLATEGRAY (uint32_t)0x2F4F4F #define DARKSLATEGREY (uint32_t)0x2F4F4F @@ -665,6 +669,7 @@ typedef struct Segment { inline void setPixelColorXY(unsigned x, unsigned y, uint32_t c) { setPixelColorXY(int(x), int(y), c); } inline void setPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) { setPixelColorXY(x, y, RGBW32(r,g,b,w)); } inline void setPixelColorXY(int x, int y, CRGB c) { setPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0)); } + inline void setPixelColorXY(unsigned x, unsigned y, CRGB c) { setPixelColorXY(int(x), int(y), RGBW32(c.r,c.g,c.b,0)); } //#ifdef WLED_USE_AA_PIXELS void setPixelColorXY(float x, float y, uint32_t c, bool aa = true, bool fast=true); inline void setPixelColorXY(float x, float y, byte r, byte g, byte b, byte w = 0, bool aa = true) { setPixelColorXY(x, y, RGBW32(r,g,b,w), aa); } @@ -684,8 +689,10 @@ typedef struct Segment { void moveX(int8_t delta, bool wrap = false); void moveY(int8_t delta, bool wrap = false); void move(uint8_t dir, uint8_t delta, bool wrap = false); - void draw_circle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB c); - void fill_circle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB c); + void drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t c, bool soft = false); + inline void drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB c, bool soft = false) { drawCircle(cx, cy, radius, RGBW32(c.r,c.g,c.b,0), soft); } + void fillCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t c, bool soft = false); + inline void fillCircle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB c, bool soft = false) { fillCircle(cx, cy, radius, RGBW32(c.r,c.g,c.b,0), soft); } void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint32_t c); void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, CRGB c) { drawLine(x0, y0, x1, y1, RGBW32(c.r,c.g,c.b,0)); } // automatic inline void drawArc(uint16_t x0, uint16_t y0, uint16_t radius, uint32_t color, uint32_t fillColor = 0); @@ -693,7 +700,7 @@ typedef struct Segment { void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, uint32_t color, uint32_t col2 = 0); void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, CRGB c, CRGB c2) { drawCharacter(chr, x, y, w, h, RGBW32(c.r,c.g,c.b,0), RGBW32(c2.r,c2.g,c2.b,0)); } // automatic inline void wu_pixel(uint32_t x, uint32_t y, CRGB c); - void blur1d(fract8 blur_amount); // blur all rows in 1 dimension + //void blur1d(fract8 blur_amount); // blur all rows in 1 dimension void blur2d(fract8 blur_amount) { blur(blur_amount); } void fill_solid(CRGB c) { fill(RGBW32(c.r,c.g,c.b,0)); } void nscale8(uint8_t scale); @@ -704,6 +711,7 @@ typedef struct Segment { inline void setPixelColorXY(unsigned x, unsigned y, uint32_t c) { setPixelColor(int(x), c); } inline void setPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) { setPixelColor(x, RGBW32(r,g,b,w)); } inline void setPixelColorXY(int x, int y, CRGB c) { setPixelColor(x, RGBW32(c.r,c.g,c.b,0)); } + inline void setPixelColorXY(unsigned x, unsigned y, CRGB c) { setPixelColor(int(x), RGBW32(c.r,c.g,c.b,0)); } //#ifdef WLED_USE_AA_PIXELS inline void setPixelColorXY(float x, float y, uint32_t c, bool aa = true) { setPixelColor(x, c, aa); } inline void setPixelColorXY(float x, float y, byte r, byte g, byte b, byte w = 0, bool aa = true) { setPixelColor(x, RGBW32(r,g,b,w), aa); } @@ -722,9 +730,12 @@ typedef struct Segment { inline void moveX(int8_t delta, bool wrap = false) {} inline void moveY(int8_t delta, bool wrap = false) {} inline void move(uint8_t dir, uint8_t delta, bool wrap = false) {} - inline void fill_circle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB c) {} - inline void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint32_t c) {} - inline void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, CRGB c) {} + inline void drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t c, bool soft = false) {} + inline void drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB c, bool soft = false) {} + inline void fillCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t c, bool soft = false) {} + inline void fillCircle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB c, bool soft = false) {} + inline void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint32_t c, bool soft = false) {} + inline void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, CRGB c, bool soft = false) {} inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, uint32_t color, uint32_t = 0, int8_t = 0) {} inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, CRGB color) {} inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, CRGB c, CRGB c2, int8_t rotate = 0) {} diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index 1d217ff0..320636d6 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -427,56 +427,38 @@ void Segment::blurCol(uint32_t col, fract8 blur_amount, bool smear) { } // 1D Box blur (with added weight - blur_amount: [0=no blur, 255=max blur]) -void Segment::box_blur(uint16_t i, bool vertical, fract8 blur_amount) { //WLEDMM: use fast types - const uint_fast16_t cols = virtualWidth(); - const uint_fast16_t rows = virtualHeight(); - const uint_fast16_t dim1 = vertical ? rows : cols; - const uint_fast16_t dim2 = vertical ? cols : rows; +void Segment::box_blur(uint16_t i, bool vertical, fract8 blur_amount) { + if (!isActive() || blur_amount == 0) return; // not active + const int cols = virtualWidth(); + const int rows = virtualHeight(); + const int dim1 = vertical ? rows : cols; + const int dim2 = vertical ? cols : rows; if (i >= dim2) return; const float seep = blur_amount/255.f; const float keep = 3.f - 2.f*seep; // 1D box blur - CRGB tmp[dim1]; - for (uint_fast16_t j = 0; j < dim1; j++) { - uint_fast16_t x = vertical ? i : j; - uint_fast16_t y = vertical ? j : i; - int_fast16_t xp = vertical ? x : x-1; // "signed" to prevent underflow - int_fast16_t yp = vertical ? y-1 : y; // "signed" to prevent underflow - uint_fast16_t xn = vertical ? x : x+1; - uint_fast16_t yn = vertical ? y+1 : y; - CRGB curr = getPixelColorXY(x,y); - CRGB prev = (xp<0 || yp<0) ? CRGB::Black : getPixelColorXY(xp,yp); - CRGB next = ((vertical && yn>=dim1) || (!vertical && xn>=dim1)) ? CRGB::Black : getPixelColorXY(xn,yn); - uint16_t r, g, b; - r = (curr.r*keep + (prev.r + next.r)*seep) / 3; - g = (curr.g*keep + (prev.g + next.g)*seep) / 3; - b = (curr.b*keep + (prev.b + next.b)*seep) / 3; - tmp[j] = CRGB(r,g,b); + uint32_t out[dim1], in[dim1]; + for (int j = 0; j < dim1; j++) { + int x = vertical ? i : j; + int y = vertical ? j : i; + in[j] = getPixelColorXY(x, y); } - for (uint_fast16_t j = 0; j < dim1; j++) { - uint_fast16_t x = vertical ? i : j; - uint_fast16_t y = vertical ? j : i; - setPixelColorXY((int)x, (int)y, tmp[j]); + for (int j = 0; j < dim1; j++) { + uint32_t curr = in[j]; + uint32_t prev = j > 0 ? in[j-1] : BLACK; + uint32_t next = j < dim1-1 ? in[j+1] : BLACK; + uint8_t r, g, b, w; + r = (R(curr)*keep + (R(prev) + R(next))*seep) / 3; + g = (G(curr)*keep + (G(prev) + G(next))*seep) / 3; + b = (B(curr)*keep + (B(prev) + B(next))*seep) / 3; + w = (W(curr)*keep + (W(prev) + W(next))*seep) / 3; + out[j] = RGBW32(r,g,b,w); + } + for (int j = 0; j < dim1; j++) { + int x = vertical ? i : j; + int y = vertical ? j : i; + setPixelColorXY(x, y, out[j]); } -} - -// blur1d: one-dimensional blur filter. Spreads light to 2 line neighbors. -// blur2d: two-dimensional blur filter. Spreads light to 8 XY neighbors. -// -// 0 = no spread at all -// 64 = moderate spreading -// 172 = maximum smooth, even spreading -// -// 173..255 = wider spreading, but increasing flicker -// -// Total light is NOT entirely conserved, so many repeated -// calls to 'blur' will also result in the light fading, -// eventually all the way to black; this is by design so that -// it can be used to (slowly) clear the LEDs to black. - -void Segment::blur1d(fract8 blur_amount) { //WLEDMM: use fast types - const uint_fast16_t rows = virtualHeight(); - for (uint_fast16_t y = 0; y < rows; y++) blurRow(y, blur_amount); } void Segment::moveX(int8_t delta, bool wrap) { @@ -533,37 +515,71 @@ void Segment::move(uint8_t dir, uint8_t delta, bool wrap) { } } -void Segment::draw_circle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB col) { - if (!isActive()) return; // not active - // Bresenham’s Algorithm - int d = 3 - (2*radius); - int y = radius, x = 0; - while (y >= x) { - setPixelColorXY(cx+x, cy+y, col); - setPixelColorXY(cx-x, cy+y, col); - setPixelColorXY(cx+x, cy-y, col); - setPixelColorXY(cx-x, cy-y, col); - setPixelColorXY(cx+y, cy+x, col); - setPixelColorXY(cx-y, cy+x, col); - setPixelColorXY(cx+y, cy-x, col); - setPixelColorXY(cx-y, cy-x, col); - x++; - if (d > 0) { - y--; - d += 4 * (x - y) + 10; - } else { - d += 4 * x + 6; +void Segment::drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t col, bool soft) { + if (!isActive() || radius == 0) return; // not active + if (soft) { + // Xiaolin Wu’s algorithm + int rsq = radius*radius; + int x = 0; + int y = radius; + unsigned oldFade = 0; + while (x < y) { + float yf = sqrtf(float(rsq - x*x)); // needs to be floating point + unsigned fade = float(0xFFFF) * (ceilf(yf) - yf); // how much color to keep + if (oldFade > fade) y--; + oldFade = fade; + setPixelColorXY(cx+x, cy+y, color_blend(col, getPixelColorXY(cx+x, cy+y), fade, true)); + setPixelColorXY(cx-x, cy+y, color_blend(col, getPixelColorXY(cx-x, cy+y), fade, true)); + setPixelColorXY(cx+x, cy-y, color_blend(col, getPixelColorXY(cx+x, cy-y), fade, true)); + setPixelColorXY(cx-x, cy-y, color_blend(col, getPixelColorXY(cx-x, cy-y), fade, true)); + setPixelColorXY(cx+y, cy+x, color_blend(col, getPixelColorXY(cx+y, cy+x), fade, true)); + setPixelColorXY(cx-y, cy+x, color_blend(col, getPixelColorXY(cx-y, cy+x), fade, true)); + setPixelColorXY(cx+y, cy-x, color_blend(col, getPixelColorXY(cx+y, cy-x), fade, true)); + setPixelColorXY(cx-y, cy-x, color_blend(col, getPixelColorXY(cx-y, cy-x), fade, true)); + setPixelColorXY(cx+x, cy+y-1, color_blend(getPixelColorXY(cx+x, cy+y-1), col, fade, true)); + setPixelColorXY(cx-x, cy+y-1, color_blend(getPixelColorXY(cx-x, cy+y-1), col, fade, true)); + setPixelColorXY(cx+x, cy-y+1, color_blend(getPixelColorXY(cx+x, cy-y+1), col, fade, true)); + setPixelColorXY(cx-x, cy-y+1, color_blend(getPixelColorXY(cx-x, cy-y+1), col, fade, true)); + setPixelColorXY(cx+y-1, cy+x, color_blend(getPixelColorXY(cx+y-1, cy+x), col, fade, true)); + setPixelColorXY(cx-y+1, cy+x, color_blend(getPixelColorXY(cx-y+1, cy+x), col, fade, true)); + setPixelColorXY(cx+y-1, cy-x, color_blend(getPixelColorXY(cx+y-1, cy-x), col, fade, true)); + setPixelColorXY(cx-y+1, cy-x, color_blend(getPixelColorXY(cx-y+1, cy-x), col, fade, true)); + x++; + } + } else { + // Bresenham’s Algorithm + int d = 3 - (2*radius); + int y = radius, x = 0; + while (y >= x) { + setPixelColorXY(cx+x, cy+y, col); + setPixelColorXY(cx-x, cy+y, col); + setPixelColorXY(cx+x, cy-y, col); + setPixelColorXY(cx-x, cy-y, col); + setPixelColorXY(cx+y, cy+x, col); + setPixelColorXY(cx-y, cy+x, col); + setPixelColorXY(cx+y, cy-x, col); + setPixelColorXY(cx-y, cy-x, col); + x++; + if (d > 0) { + y--; + d += 4 * (x - y) + 10; + } else { + d += 4 * x + 6; + } } } } // by stepko, taken from https://editor.soulmatelights.com/gallery/573-blobs -void Segment::fill_circle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB col) { - if (!isActive()) return; // not active - const uint16_t cols = virtualWidth(); - const uint16_t rows = virtualHeight(); - for (int16_t y = -radius; y <= radius; y++) { - for (int16_t x = -radius; x <= radius; x++) { +void Segment::fillCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t col, bool soft) { + if (!isActive() || radius == 0) return; // not active + // draw soft bounding circle + if (soft) drawCircle(cx, cy, radius, col, soft); + // fill it + const int cols = virtualWidth(); + const int rows = virtualHeight(); + for (int y = -radius; y <= radius; y++) { + for (int x = -radius; x <= radius; x++) { if (x * x + y * y <= radius * radius && int16_t(cx)+x>=0 && int16_t(cy)+y>=0 && int16_t(cx)+x= cols || x1 >= cols || y0 >= rows || y1 >= rows) return; - const int16_t dx = abs(x1-x0), sx = x0dy ? dx : -dy)/2, e2; - for (;;) { - setPixelColorXY(x0,y0,c); - if (x0==x1 && y0==y1) break; - e2 = err; - if (e2 >-dx) { err -= dy; x0 += sx; } - if (e2 < dy) { err += dx; y0 += sy; } + + const int dx = abs(x1-x0), sx = x0 dx; + if (steep) { + // we need to go along longest dimension + std::swap(x0,y0); + std::swap(x1,y1); + } + if (x0 > x1) { + // we need to go in increasing fashion + std::swap(x0,x1); + std::swap(y0,y1); + } + float gradient = x1-x0 == 0 ? 1.0f : float(y1-y0) / float(x1-x0); + float intersectY = y0; + for (int x = x0; x <= x1; x++) { + unsigned keep = float(0xFFFF) * (intersectY-int(intersectY)); // how much color to keep + unsigned seep = 0xFFFF - keep; // how much background to keep + int y = int(intersectY); + if (steep) std::swap(x,y); // temporaryly swap if steep + // pixel coverage is determined by fractional part of y co-ordinate + setPixelColorXY(x, y, color_blend(c, getPixelColorXY(x, y), keep, true)); + setPixelColorXY(x+int(steep), y+int(!steep), color_blend(c, getPixelColorXY(x+int(steep), y+int(!steep)), seep, true)); + intersectY += gradient; + if (steep) std::swap(x,y); // restore if steep + } + } else { + // Bresenham's algorithm + int err = (dx>dy ? dx : -dy)/2; // error direction + for (;;) { + setPixelColorXY(x0, y0, c); + if (x0==x1 && y0==y1) break; + int e2 = err; + if (e2 >-dx) { err -= dy; x0 += sx; } + if (e2 < dy) { err += dx; y0 += sy; } + } } } From c07658a7cbcf8adffb39b5b2543e2f91e0fb40e3 Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 26 Jun 2024 23:41:38 +0200 Subject: [PATCH 038/200] post-merge * fix drawLine prototype * a few more "inline" * typo --- wled00/FX.h | 30 +++++++++++++++--------------- wled00/FX_2Dfcn.cpp | 2 +- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/wled00/FX.h b/wled00/FX.h index b53f3fce..4772dbe7 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -618,11 +618,11 @@ typedef struct Segment { // 1D strip uint16_t virtualLength(void) const; void setPixelColor(int n, uint32_t c); // set relative pixel within segment with color - void setPixelColor(int n, byte r, byte g, byte b, byte w = 0) { setPixelColor(n, RGBW32(r,g,b,w)); } // automatically inline - void setPixelColor(int n, CRGB c) { setPixelColor(n, RGBW32(c.r,c.g,c.b,0)); } // automatically inline + inline void setPixelColor(int n, byte r, byte g, byte b, byte w = 0) { setPixelColor(n, RGBW32(r,g,b,w)); } // automatically inline + inline void setPixelColor(int n, CRGB c) { setPixelColor(n, RGBW32(c.r,c.g,c.b,0)); } // automatically inline void setPixelColor(float i, uint32_t c, bool aa = true); - void setPixelColor(float i, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0, bool aa = true) { setPixelColor(i, RGBW32(r,g,b,w), aa); } - void setPixelColor(float i, CRGB c, bool aa = true) { setPixelColor(i, RGBW32(c.r,c.g,c.b,0), aa); } + inline void setPixelColor(float i, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0, bool aa = true) { setPixelColor(i, RGBW32(r,g,b,w), aa); } + inline void setPixelColor(float i, CRGB c, bool aa = true) { setPixelColor(i, RGBW32(c.r,c.g,c.b,0), aa); } uint32_t __attribute__((pure)) getPixelColor(int i); // WLEDMM attribute added // 1D support functions (some implement 2D as well) void blur(uint8_t, bool smear = false); @@ -630,10 +630,10 @@ typedef struct Segment { void fade_out(uint8_t r); void fadeToBlackBy(uint8_t fadeBy); void blendPixelColor(int n, uint32_t color, uint8_t blend); - void blendPixelColor(int n, CRGB c, uint8_t blend) { blendPixelColor(n, RGBW32(c.r,c.g,c.b,0), blend); } + inline void blendPixelColor(int n, CRGB c, uint8_t blend) { blendPixelColor(n, RGBW32(c.r,c.g,c.b,0), blend); } void addPixelColor(int n, uint32_t color, bool fast = false); - void addPixelColor(int n, byte r, byte g, byte b, byte w = 0, bool fast = false) { addPixelColor(n, RGBW32(r,g,b,w), fast); } // automatically inline - void addPixelColor(int n, CRGB c, bool fast = false) { addPixelColor(n, RGBW32(c.r,c.g,c.b,0), fast); } // automatically inline + inline void addPixelColor(int n, byte r, byte g, byte b, byte w = 0, bool fast = false) { addPixelColor(n, RGBW32(r,g,b,w), fast); } // automatically inline + inline void addPixelColor(int n, CRGB c, bool fast = false) { addPixelColor(n, RGBW32(c.r,c.g,c.b,0), fast); } // automatically inline void fadePixelColor(uint16_t n, uint8_t fade); uint8_t get_random_wheel_index(uint8_t pos); uint32_t __attribute__((pure)) color_from_palette(uint_fast16_t, bool mapping, bool wrap, uint8_t mcol, uint8_t pbri = 255); @@ -678,10 +678,10 @@ typedef struct Segment { uint32_t __attribute__((pure)) getPixelColorXY(int x, int y); // 2D support functions void blendPixelColorXY(uint16_t x, uint16_t y, uint32_t color, uint8_t blend); - void blendPixelColorXY(uint16_t x, uint16_t y, CRGB c, uint8_t blend) { blendPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), blend); } + inline void blendPixelColorXY(uint16_t x, uint16_t y, CRGB c, uint8_t blend) { blendPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), blend); } void addPixelColorXY(int x, int y, uint32_t color, bool fast = false); - void addPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0, bool fast = false) { addPixelColorXY(x, y, RGBW32(r,g,b,w), fast); } // automatically inline - void addPixelColorXY(int x, int y, CRGB c, bool fast = false) { addPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), fast); } + inline void addPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0, bool fast = false) { addPixelColorXY(x, y, RGBW32(r,g,b,w), fast); } // automatically inline + inline void addPixelColorXY(int x, int y, CRGB c, bool fast = false) { addPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), fast); } void fadePixelColorXY(uint16_t x, uint16_t y, uint8_t fade); void box_blur(uint16_t i, bool vertical, fract8 blur_amount); // 1D box blur (with weight) void blurRow(uint32_t row, fract8 blur_amount, bool smear = false); @@ -693,16 +693,16 @@ typedef struct Segment { inline void drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB c, bool soft = false) { drawCircle(cx, cy, radius, RGBW32(c.r,c.g,c.b,0), soft); } void fillCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t c, bool soft = false); inline void fillCircle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB c, bool soft = false) { fillCircle(cx, cy, radius, RGBW32(c.r,c.g,c.b,0), soft); } - void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint32_t c); - void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, CRGB c) { drawLine(x0, y0, x1, y1, RGBW32(c.r,c.g,c.b,0)); } // automatic inline + void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint32_t c, bool soft = false); + inline void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, CRGB c, bool soft = false) { drawLine(x0, y0, x1, y1, RGBW32(c.r,c.g,c.b,0), soft); } // automatic inline void drawArc(uint16_t x0, uint16_t y0, uint16_t radius, uint32_t color, uint32_t fillColor = 0); - void drawArc(uint16_t x0, uint16_t y0, uint16_t radius, CRGB color, CRGB fillColor = BLACK) { drawArc(x0, y0, radius, RGBW32(color.r,color.g,color.b,0), RGBW32(fillColor.r,fillColor.g,fillColor.b,0)); } // automatic inline + inline void drawArc(uint16_t x0, uint16_t y0, uint16_t radius, CRGB color, CRGB fillColor = BLACK) { drawArc(x0, y0, radius, RGBW32(color.r,color.g,color.b,0), RGBW32(fillColor.r,fillColor.g,fillColor.b,0)); } // automatic inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, uint32_t color, uint32_t col2 = 0); - void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, CRGB c, CRGB c2) { drawCharacter(chr, x, y, w, h, RGBW32(c.r,c.g,c.b,0), RGBW32(c2.r,c2.g,c2.b,0)); } // automatic inline + inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, CRGB c, CRGB c2) { drawCharacter(chr, x, y, w, h, RGBW32(c.r,c.g,c.b,0), RGBW32(c2.r,c2.g,c2.b,0)); } // automatic inline void wu_pixel(uint32_t x, uint32_t y, CRGB c); //void blur1d(fract8 blur_amount); // blur all rows in 1 dimension void blur2d(fract8 blur_amount) { blur(blur_amount); } - void fill_solid(CRGB c) { fill(RGBW32(c.r,c.g,c.b,0)); } + inline void fill_solid(CRGB c) { fill(RGBW32(c.r,c.g,c.b,0)); } void nscale8(uint8_t scale); bool jsonToPixels(char *name, uint8_t fileNr); //WLEDMM for artifx #else diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index 320636d6..061d8c19 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -632,7 +632,7 @@ void Segment::drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint3 unsigned keep = float(0xFFFF) * (intersectY-int(intersectY)); // how much color to keep unsigned seep = 0xFFFF - keep; // how much background to keep int y = int(intersectY); - if (steep) std::swap(x,y); // temporaryly swap if steep + if (steep) std::swap(x,y); // temporarily swap if steep // pixel coverage is determined by fractional part of y co-ordinate setPixelColorXY(x, y, color_blend(c, getPixelColorXY(x, y), keep, true)); setPixelColorXY(x+int(steep), y+int(!steep), color_blend(c, getPixelColorXY(x+int(steep), y+int(!steep)), seep, true)); From f432cb20dc2c3d0a38d6f12d850baac91ce26e3a Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 1 Jul 2024 02:14:00 +0200 Subject: [PATCH 039/200] remove experiments:freqRMS option the default value seems to work well. --- usermods/audioreactive/audio_reactive.h | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index 52f8e17f..a0414165 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -237,9 +237,9 @@ static uint8_t useInputFilter = 0; // enables low-cut fil //WLEDMM add experimental settings static uint8_t micLevelMethod = 0; // 0=old "floating" miclev, 1=new "freeze" mode, 2=fast freeze mode (mode 2 may not work for you) #if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3) -static uint8_t averageByRMS = false; // false: use mean value, true: use RMS (root mean squared). use simpler method on slower MCUs. +static constexpr uint8_t averageByRMS = false; // false: use mean value, true: use RMS (root mean squared). use simpler method on slower MCUs. #else -static uint8_t averageByRMS = true; // false: use mean value, true: use RMS (root mean squared). use better method on fast MCUs. +static constexpr uint8_t averageByRMS = true; // false: use mean value, true: use RMS (root mean squared). use better method on fast MCUs. #endif static uint8_t freqDist = 0; // 0=old 1=rightshift mode @@ -436,7 +436,7 @@ static float fftAddAvgRMS(int from, int to) { static float fftAddAvg(int from, int to) { if (from == to) return vReal[from]; // small optimization - if (averageByRMS) return fftAddAvgRMS(from, to); // use SMS + if (averageByRMS) return fftAddAvgRMS(from, to); // use RMS else return fftAddAvgLin(from, to); // use linear average } @@ -2657,7 +2657,7 @@ class AudioReactive : public Usermod { JsonObject poweruser = top.createNestedObject("experiments"); poweruser[F("micLev")] = micLevelMethod; poweruser[F("freqDist")] = freqDist; - poweruser[F("freqRMS")] = averageByRMS; + //poweruser[F("freqRMS")] = averageByRMS; JsonObject freqScale = top.createNestedObject("frequency"); freqScale[F("scale")] = FFTScalingMode; @@ -2729,7 +2729,7 @@ class AudioReactive : public Usermod { //WLEDMM: experimental settings configComplete &= getJsonValue(top["experiments"][F("micLev")], micLevelMethod); configComplete &= getJsonValue(top["experiments"][F("freqDist")], freqDist); - configComplete &= getJsonValue(top["experiments"][F("freqRMS")], averageByRMS); + //configComplete &= getJsonValue(top["experiments"][F("freqRMS")], averageByRMS); configComplete &= getJsonValue(top["frequency"][F("scale")], FFTScalingMode); configComplete &= getJsonValue(top["frequency"][F("profile")], pinkIndex); //WLEDMM @@ -2833,10 +2833,10 @@ class AudioReactive : public Usermod { oappend(SET_F("addOption(dd,'RightShift',1);")); oappend(SET_F("addInfo('AudioReactive:experiments:freqDist',1,'☾');")); - oappend(SET_F("dd=addDropdown('AudioReactive','experiments:freqRMS');")); - oappend(SET_F("addOption(dd,'Off (⎌)',0);")); - oappend(SET_F("addOption(dd,'On',1);")); - oappend(SET_F("addInfo('AudioReactive:experiments:freqRMS',1,'☾');")); + //oappend(SET_F("dd=addDropdown('AudioReactive','experiments:freqRMS');")); + //oappend(SET_F("addOption(dd,'Off (⎌)',0);")); + //oappend(SET_F("addOption(dd,'On',1);")); + //oappend(SET_F("addInfo('AudioReactive:experiments:freqRMS',1,'☾');")); oappend(SET_F("dd=addDropdown('AudioReactive','dynamics:limiter');")); oappend(SET_F("addOption(dd,'Off',0);")); From 4f9f250a1d467e3d64002639c85f225e3a09cc70 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 1 Jul 2024 02:29:14 +0200 Subject: [PATCH 040/200] only run FFT.dcRemoval() when no filtering was applied FFT.dcRemoval() may introduce unwanted artifacts into the FFT result. --- usermods/audioreactive/audio_reactive.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index a0414165..2543cf34 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -562,12 +562,14 @@ void FFTcode(void * parameter) // band pass filter - can reduce noise floor by a factor of 50 // downside: frequencies below 100Hz will be ignored + bool doDCRemoval = false; // DCRemove is only necessary if we don't use any kind of low-cut filtering if ((useInputFilter > 0) && (useInputFilter < 99)) { switch(useInputFilter) { case 1: runMicFilter(samplesFFT, vReal); break; // PDM microphone bandpass case 2: runDCBlocker(samplesFFT, vReal); break; // generic Low-Cut + DC blocker (~40hz cut-off) + default: doDCRemoval = true; break; } - } + } else doDCRemoval = true; // set imaginary parts to 0 memset(vImag, 0, sizeof(vImag)); @@ -617,7 +619,7 @@ void FFTcode(void * parameter) if ((skipSecondFFT == false) || (isFirstRun == true)) { // run FFT (takes 2-3ms on ESP32, ~12ms on ESP32-S2, ~30ms on -C3) #ifdef UM_AUDIOREACTIVE_USE_NEW_FFT - FFT.dcRemoval(); // remove DC offset + if (doDCRemoval) FFT.dcRemoval(); // remove DC offset #if !defined(FFT_PREFER_EXACT_PEAKS) FFT.windowing( FFTWindow::Flat_top, FFTDirection::Forward); // Weigh data using "Flat Top" function - better amplitude accuracy #else From f3cbe75506bf4ac022f352d80e78879ade2fea07 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 1 Jul 2024 02:39:44 +0200 Subject: [PATCH 041/200] remove support for old ArduinoFFT library < 1.9.2 there is no reason any more to still use the old arduinoFFT library version. --- usermods/audioreactive/audio_reactive.h | 27 ------------------------- usermods/audioreactive/readme.md | 12 ++--------- 2 files changed, 2 insertions(+), 37 deletions(-) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index 2543cf34..c466cee5 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -381,7 +381,6 @@ constexpr float binWidth = SAMPLE_RATE / (float)samplesFFT; // frequency range o // Create FFT object -#ifdef UM_AUDIOREACTIVE_USE_NEW_FFT // lib_deps += https://github.com/kosme/arduinoFFT#develop @ 1.9.2 #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) // these options actually cause slow-down on -S2 (-S2 doesn't have floating point hardware) @@ -390,13 +389,8 @@ constexpr float binWidth = SAMPLE_RATE / (float)samplesFFT; // frequency range o #endif #define sqrt(x) sqrtf(x) // little hack that reduces FFT time by 10-50% on ESP32 (as alternative to FFT_SQRT_APPROXIMATION) #define sqrt_internal sqrtf // see https://github.com/kosme/arduinoFFT/pull/83 -#else - // around 50% slower on -S2 -// lib_deps += https://github.com/blazoncek/arduinoFFT.git -#endif #include -#ifdef UM_AUDIOREACTIVE_USE_NEW_FFT #if defined(FFT_LIB_REV) && FFT_LIB_REV > 0x19 // arduinoFFT 2.x has a slightly different API static ArduinoFFT FFT = ArduinoFFT( vReal, vImag, samplesFFT, SAMPLE_RATE, true); @@ -405,9 +399,6 @@ constexpr float binWidth = SAMPLE_RATE / (float)samplesFFT; // frequency range o static float windowWeighingFactors[samplesFFT] = {0.0f}; // cache for FFT windowing factors static ArduinoFFT FFT = ArduinoFFT( vReal, vImag, samplesFFT, SAMPLE_RATE, windowWeighingFactors); #endif -#else -static arduinoFFT FFT = arduinoFFT(vReal, vImag, samplesFFT, SAMPLE_RATE); -#endif // Helper functions @@ -618,7 +609,6 @@ void FFTcode(void * parameter) if (fabsf(volumeSmth) > 0.25f) { // noise gate open if ((skipSecondFFT == false) || (isFirstRun == true)) { // run FFT (takes 2-3ms on ESP32, ~12ms on ESP32-S2, ~30ms on -C3) - #ifdef UM_AUDIOREACTIVE_USE_NEW_FFT if (doDCRemoval) FFT.dcRemoval(); // remove DC offset #if !defined(FFT_PREFER_EXACT_PEAKS) FFT.windowing( FFTWindow::Flat_top, FFTDirection::Forward); // Weigh data using "Flat Top" function - better amplitude accuracy @@ -628,19 +618,6 @@ void FFTcode(void * parameter) FFT.compute( FFTDirection::Forward ); // Compute FFT FFT.complexToMagnitude(); // Compute magnitudes vReal[0] = 0; // The remaining DC offset on the signal produces a strong spike on position 0 that should be eliminated to avoid issues. - #else - FFT.DCRemoval(); // let FFT lib remove DC component, so we don't need to care about this in getSamples() - - //FFT.Windowing( FFT_WIN_TYP_HAMMING, FFT_FORWARD ); // Weigh data - standard Hamming window - //FFT.Windowing( FFT_WIN_TYP_BLACKMAN, FFT_FORWARD ); // Blackman window - better side freq rejection - #if !defined(FFT_PREFER_EXACT_PEAKS) - FFT.Windowing( FFT_WIN_TYP_FLT_TOP, FFT_FORWARD ); // Flat Top Window - better amplitude accuracy - #else - FFT.Windowing( FFT_WIN_TYP_BLACKMAN_HARRIS, FFT_FORWARD );// Blackman-Harris - excellent sideband rejection - #endif - FFT.Compute( FFT_FORWARD ); // Compute FFT - FFT.ComplexToMagnitude(); // Compute magnitudes - #endif float last_majorpeak = FFT_MajorPeak; float last_magnitude = FFT_Magnitude; @@ -651,16 +628,12 @@ void FFTcode(void * parameter) vReal[binInd] *= pinkFactors[binInd]; #endif - #ifdef UM_AUDIOREACTIVE_USE_NEW_FFT #if defined(FFT_LIB_REV) && FFT_LIB_REV > 0x19 // arduinoFFT 2.x has a slightly different API FFT.majorPeak(&FFT_MajorPeak, &FFT_Magnitude); #else FFT.majorPeak(FFT_MajorPeak, FFT_Magnitude); // let the effects know which freq was most dominant #endif - #else - FFT.MajorPeak(&FFT_MajorPeak, &FFT_Magnitude); - #endif if (FFT_MajorPeak < (SAMPLE_RATE / samplesFFT)) {FFT_MajorPeak = 1.0f; FFT_Magnitude = 0;} // too low - use zero if (FFT_MajorPeak > (0.42f * SAMPLE_RATE)) {FFT_MajorPeak = last_majorpeak; FFT_Magnitude = last_magnitude;} // too high - keep last peak diff --git a/usermods/audioreactive/readme.md b/usermods/audioreactive/readme.md index 47804b61..a80fa681 100644 --- a/usermods/audioreactive/readme.md +++ b/usermods/audioreactive/readme.md @@ -27,17 +27,9 @@ Currently ESP8266 is not supported, due to low speed and small RAM of this chip. There are however plans to create a lightweight audioreactive for the 8266, with reduced features. ## Installation -### using customised _arduinoFFT_ library for use with this usermod -Add `-D USERMOD_AUDIOREACTIVE` to your PlatformIO environment `build_flags`, as well as `https://github.com/blazoncek/arduinoFFT.git` to your `lib_deps`. -If you are not using PlatformIO (which you should) try adding `#define USERMOD_AUDIOREACTIVE` to *my_config.h* and make sure you have _arduinoFFT_ library downloaded and installed. +### using latest _arduinoFFT_ library -Customised _arduinoFFT_ library for use with this usermod can be found at https://github.com/blazoncek/arduinoFFT.git - -### using latest (develop) _arduinoFFT_ library -Alternatively, you can use the latest arduinoFFT development version. -ArduinoFFT `develop` library is slightly more accurate, and slightly faster than our customised library, however also needs additional 2kB RAM. - -* `build_flags` = `-D USERMOD_AUDIOREACTIVE` `-D UM_AUDIOREACTIVE_USE_NEW_FFT` +* `build_flags` = `-D USERMOD_AUDIOREACTIVE` * `lib_deps`= `https://github.com/kosme/arduinoFFT#develop @ 1.9.2` ## Configuration From 9803cecee2b8c3c1559f586f3cc6fcfa2a45a1ab Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 1 Jul 2024 02:46:14 +0200 Subject: [PATCH 042/200] new idea to reduce stack buffer usage the JS string can be shortened, by putting the usermod into a variable `ux` that is used instead of repeating the string 'Aduioreactive'. For now its just an experiment, to see if the idea works on several browsers. --- usermods/audioreactive/audio_reactive.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index c466cee5..1a73fd49 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -2723,7 +2723,8 @@ class AudioReactive : public Usermod { void appendConfigData() { - oappend(SET_F("addInfo('AudioReactive:help',0,'');")); + oappend(SET_F("ux='AudioReactive';")); // fingers crossed that "ux" isn't already used as JS var, html post parameter or css style + oappend(SET_F("addInfo(ux+':help',0,'');")); #ifdef ARDUINO_ARCH_ESP32 //WLEDMM: add defaults #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) // -S3/-S2/-C3 don't support analog audio From 2987f0d0459cd7961333fdc2ef57007b08bc0d52 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Mon, 1 Jul 2024 15:46:51 +0200 Subject: [PATCH 043/200] SR_STATS: filter time adding "Filtering Time" to SR statistics (info page) --- usermods/audioreactive/audio_reactive.h | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index 1a73fd49..fdacaefc 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -330,6 +330,7 @@ static float FFT_MajPeakSmth = 1.0f; // FFT: (peak) frequency, smooth static float fftTaskCycle = 0; // avg cycle time for FFT task static float fftTime = 0; // avg time for single FFT static float sampleTime = 0; // avg (blocked) time for reading I2S samples +static float filterTime = 0; // avg time for filtering I2S samples #endif // FFT Task variables (filtering and post-processing) @@ -525,7 +526,7 @@ void FFTcode(void * parameter) uint64_t sampleTimeInMillis = (esp_timer_get_time() - start +5ULL) / 10ULL; // "+5" to ensure proper rounding sampleTime = (sampleTimeInMillis*3 + sampleTime*7)/10.0; // smooth } - start = esp_timer_get_time(); // start measuring FFT time + start = esp_timer_get_time(); // start measuring filter time #endif xLastWakeTime = xTaskGetTickCount(); // update "last unblocked time" for vTaskDelay @@ -562,6 +563,15 @@ void FFTcode(void * parameter) } } else doDCRemoval = true; +#if defined(WLED_DEBUG) || defined(SR_DEBUG)|| defined(SR_STATS) + // timing measurement + if (start < esp_timer_get_time()) { // filter out overflows + uint64_t filterTimeInMillis = (esp_timer_get_time() - start +5ULL) / 10ULL; // "+5" to ensure proper rounding + filterTime = (filterTimeInMillis*3 + filterTime*7)/10.0; // smooth + } + start = esp_timer_get_time(); // start measuring FFT time +#endif + // set imaginary parts to 0 memset(vImag, 0, sizeof(vImag)); @@ -2246,7 +2256,7 @@ class AudioReactive : public Usermod { void onUpdateBegin(bool init) { #ifdef WLED_DEBUG - fftTime = sampleTime = 0; + fftTime = sampleTime filterTime = 0; #endif // gracefully suspend FFT task (if running) disableSoundProcessing = true; @@ -2507,17 +2517,22 @@ class AudioReactive : public Usermod { infoArr.add(roundf(sampleTime)/100.0f); infoArr.add(" ms"); + infoArr = user.createNestedArray(F("Filtering time")); + infoArr.add(roundf(filterTime)/100.0f); + infoArr.add(" ms"); + infoArr = user.createNestedArray(F("FFT time")); infoArr.add(roundf(fftTime)/100.0f); if ((fftTime/100) >= FFT_MIN_CYCLE) // FFT time over budget -> I2S buffer will overflow infoArr.add("! ms"); - else if ((fftTime/80 + sampleTime/80) >= FFT_MIN_CYCLE) // FFT time >75% of budget -> risk of instability + else if ((fftTime/85 + filterTime/85 + sampleTime/85) >= FFT_MIN_CYCLE) // FFT time >75% of budget -> risk of instability infoArr.add(" ms!"); else infoArr.add(" ms"); DEBUGSR_PRINTF("AR I2S cycle time: %5.2f ms\n", roundf(fftTaskCycle)/100.0f); DEBUGSR_PRINTF("AR Sampling time : %5.2f ms\n", roundf(sampleTime)/100.0f); + DEBUGSR_PRINTF("AR filter time : %5.2f ms\n", roundf(filterTime)/100.0f); DEBUGSR_PRINTF("AR FFT time : %5.2f ms\n", roundf(fftTime)/100.0f); #endif #endif From 61afb26d21972488b68c5110853ca8d819dbe993 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Mon, 1 Jul 2024 15:53:20 +0200 Subject: [PATCH 044/200] Update audio_reactive.h - fix compile error --- usermods/audioreactive/audio_reactive.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index fdacaefc..d9247f37 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -2256,7 +2256,7 @@ class AudioReactive : public Usermod { void onUpdateBegin(bool init) { #ifdef WLED_DEBUG - fftTime = sampleTime filterTime = 0; + fftTime = sampleTime = filterTime = 0; #endif // gracefully suspend FFT task (if running) disableSoundProcessing = true; From e98858c751a9e29d59f6a1f46bd94acb2f76749b Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Mon, 1 Jul 2024 16:29:09 +0200 Subject: [PATCH 045/200] audio FASTPATH, part 2 * introducing sliding window FFT, which effectively doubles the rate of samples - and FFT results - produced per second. As a side-effect, it also makes FFT a bit less noisy. As sliding window FFT requires double the number of FFT runs, the feature is only enabled on esp32 and esp32-S3. --- usermods/audioreactive/audio_reactive.h | 107 ++++++++++++++++++++++-- 1 file changed, 98 insertions(+), 9 deletions(-) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index d9247f37..99e80f04 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -41,6 +41,12 @@ * .... */ + +#if defined(WLEDMM_FASTPATH) && defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32) +#define FFT_USE_SLIDING_WINDOW // perform FFT with sliding window = 50% overlap +#endif + + #define FFT_PREFER_EXACT_PEAKS // use different FFT windowing -> results in "sharper" peaks and less "leaking" into other frequencies //#define SR_STATS @@ -172,8 +178,13 @@ static bool limiterOn = false; // bool: enable / disable dynamic #else static bool limiterOn = true; #endif +#ifdef FFT_USE_SLIDING_WINDOW +static uint16_t attackTime = 14; // int: attack time in milliseconds. Default 0.014sec +static uint16_t decayTime = 250; // int: decay time in milliseconds. New default 250ms. +#else static uint16_t attackTime = 50; // int: attack time in milliseconds. Default 0.08sec static uint16_t decayTime = 300; // int: decay time in milliseconds. New default 300ms. Old default was 1.40sec +#endif // peak detection #ifdef ARDUINO_ARCH_ESP32 @@ -242,7 +253,9 @@ static constexpr uint8_t averageByRMS = false; // false: us static constexpr uint8_t averageByRMS = true; // false: use mean value, true: use RMS (root mean squared). use better method on fast MCUs. #endif static uint8_t freqDist = 0; // 0=old 1=rightshift mode - +#ifdef FFT_USE_SLIDING_WINDOW +static uint8_t doSlidingFFT = 1; // 1 = use sliding window FFT (faster & more accurate) +#endif // variables used in effects //static int16_t volumeRaw = 0; // either sampleRaw or rawSampleAgc depending on soundAgc @@ -345,7 +358,11 @@ constexpr SRate_t SAMPLE_RATE = 22050; // Base sample rate in Hz - 22Khz #ifndef WLEDMM_FASTPATH #define FFT_MIN_CYCLE 21 // minimum time before FFT task is repeated. Use with 22Khz sampling #else -#define FFT_MIN_CYCLE 15 // reduce min time, to allow faster catch-up when I2S is lagging + #ifdef FFT_USE_SLIDING_WINDOW + #define FFT_MIN_CYCLE 8 // we only have 12ms to take 1/2 batch of samples + #else + #define FFT_MIN_CYCLE 15 // reduce min time, to allow faster catch-up when I2S is lagging + #endif #endif //#define FFT_MIN_CYCLE 30 // Use with 16Khz sampling //#define FFT_MIN_CYCLE 23 // minimum time before FFT task is repeated. Use with 20Khz sampling @@ -363,7 +380,7 @@ constexpr SRate_t SAMPLE_RATE = 18000; // 18Khz; Physical sample time - // 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. +constexpr uint16_t samplesFFT_2 = 256; // meaningful part of FFT results - only the "lower half" contains useful information. // 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 @@ -473,6 +490,12 @@ void FFTcode(void * parameter) const TickType_t xFrequencyDouble = FFT_MIN_CYCLE * portTICK_PERIOD_MS * 2; static bool isFirstRun = false; +#ifdef FFT_USE_SLIDING_WINDOW + static float oldSamples[samplesFFT_2] = {0.0f}; // previous 50% of samples + static bool haveOldSamples = false; // for sliding window FFT + bool usingOldSamples = false; +#endif + #ifdef FFT_MAJORPEAK_HUMAN_EAR // pre-compute pink noise scaling table for(uint_fast16_t binInd = 0; binInd < samplesFFT; binInd++) { @@ -492,6 +515,9 @@ void FFTcode(void * parameter) // Don't run FFT computing code if we're in Receive mode or in realtime mode if (disableSoundProcessing || (audioSyncEnabled == AUDIOSYNC_REC)) { isFirstRun = false; + #ifdef FFT_USE_SLIDING_WINDOW + haveOldSamples = false; + #endif vTaskDelayUntil( &xLastWakeTime, xFrequency); // release CPU, and let I2S fill its buffers continue; } @@ -511,7 +537,26 @@ void FFTcode(void * parameter) #endif // get a fresh batch of samples from I2S + memset(vReal, 0, sizeof(vReal)); // start clean +#ifdef FFT_USE_SLIDING_WINDOW + uint16_t readOffset; + if (haveOldSamples && (doSlidingFFT > 0)) { + memcpy(vReal, oldSamples, sizeof(float) * samplesFFT_2); // copy first 50% from buffer + usingOldSamples = true; + readOffset = samplesFFT_2; + } else { + usingOldSamples = false; + readOffset = 0; + } + // read fresh samples, in chunks of 50% + do { + // this looks a bit cumbersome, but it onlyworks this way - any second instance of the getSamples() call delivers junk data. + if (audioSource) audioSource->getSamples(vReal+readOffset, samplesFFT_2); + readOffset += samplesFFT_2; + } while (readOffset < samplesFFT); +#else if (audioSource) audioSource->getSamples(vReal, samplesFFT); +#endif #if defined(WLED_DEBUG) || defined(SR_DEBUG)|| defined(SR_STATS) // debug info in case that stack usage changes @@ -552,13 +597,23 @@ void FFTcode(void * parameter) if (strip.isServicing()) delay(2); #endif + // normal mode: filter everything + float *samplesStart = vReal; + uint16_t sampleCount = samplesFFT; + #ifdef FFT_USE_SLIDING_WINDOW + if (usingOldSamples) { + // sliding window mode: only latest 50% need filtering + samplesStart = vReal + samplesFFT_2; + sampleCount = samplesFFT_2; + } + #endif // band pass filter - can reduce noise floor by a factor of 50 // downside: frequencies below 100Hz will be ignored bool doDCRemoval = false; // DCRemove is only necessary if we don't use any kind of low-cut filtering if ((useInputFilter > 0) && (useInputFilter < 99)) { switch(useInputFilter) { - case 1: runMicFilter(samplesFFT, vReal); break; // PDM microphone bandpass - case 2: runDCBlocker(samplesFFT, vReal); break; // generic Low-Cut + DC blocker (~40hz cut-off) + case 1: runMicFilter(sampleCount, samplesStart); break; // PDM microphone bandpass + case 2: runDCBlocker(sampleCount, samplesStart); break; // generic Low-Cut + DC blocker (~40hz cut-off) default: doDCRemoval = true; break; } } else doDCRemoval = true; @@ -575,14 +630,24 @@ void FFTcode(void * parameter) // set imaginary parts to 0 memset(vImag, 0, sizeof(vImag)); + #ifdef FFT_USE_SLIDING_WINDOW + memcpy(oldSamples, vReal+samplesFFT_2, sizeof(float) * samplesFFT_2); // copy last 50% to buffer (for sliding window FFT) + haveOldSamples = true; + #endif + // find highest sample in the batch, and count zero crossings float maxSample = 0.0f; // max sample from FFT batch uint_fast16_t newZeroCrossingCount = 0; for (int i=0; i < samplesFFT; i++) { // pick our our current mic sample - we take the max value from all samples that go into FFT - if ((vReal[i] <= (INT16_MAX - 1024)) && (vReal[i] >= (INT16_MIN + 1024))) //skip extreme values - normally these are artefacts + if ((vReal[i] <= (INT16_MAX - 1024)) && (vReal[i] >= (INT16_MIN + 1024))) { //skip extreme values - normally these are artefacts + #ifdef FFT_USE_SLIDING_WINDOW + if (usingOldSamples) { + if ((i >= samplesFFT_2) && (fabsf(vReal[i]) > maxSample)) maxSample = fabsf(vReal[i]); // only look at newest 50% + } else + #endif if (fabsf((float)vReal[i]) > maxSample) maxSample = fabsf((float)vReal[i]); - + } // WLED-MM/TroyHacks: Calculate zero crossings // if (i < (samplesFFT-1)) { @@ -812,6 +877,11 @@ void FFTcode(void * parameter) if ((audioSource == nullptr) || (audioSource->getType() != AudioSource::Type_I2SAdc)) // the "delay trick" does not help for analog ADC #endif { + #ifdef FFT_USE_SLIDING_WINDOW + if (!usingOldSamples) { + vTaskDelayUntil( &xLastWakeTime, xFrequencyDouble); // we need a double wait when no old data was used + } else + #endif if ((skipSecondFFT == false) || (fabsf(volumeSmth) < 0.25f)) { vTaskDelayUntil( &xLastWakeTime, xFrequency); // release CPU, and let I2S fill its buffers } else if (isFirstRun == true) { @@ -2523,9 +2593,15 @@ class AudioReactive : public Usermod { infoArr = user.createNestedArray(F("FFT time")); infoArr.add(roundf(fftTime)/100.0f); - if ((fftTime/100) >= FFT_MIN_CYCLE) // FFT time over budget -> I2S buffer will overflow + +#ifdef FFT_USE_SLIDING_WINDOW + unsigned timeBudget = doSlidingFFT ? (FFT_MIN_CYCLE) : fftTaskCycle / 115; +#else + unsigned timeBudget = (FFT_MIN_CYCLE); +#endif + if ((fftTime/100) >= timeBudget) // FFT time over budget -> I2S buffer will overflow infoArr.add("! ms"); - else if ((fftTime/85 + filterTime/85 + sampleTime/85) >= FFT_MIN_CYCLE) // FFT time >75% of budget -> risk of instability + else if ((fftTime/85 + filterTime/85 + sampleTime/85) >= timeBudget) // FFT time >75% of budget -> risk of instability infoArr.add(" ms!"); else infoArr.add(" ms"); @@ -2649,6 +2725,9 @@ class AudioReactive : public Usermod { poweruser[F("freqDist")] = freqDist; //poweruser[F("freqRMS")] = averageByRMS; +#ifdef FFT_USE_SLIDING_WINDOW + poweruser[F("I2S_FastPath")] = doSlidingFFT; +#endif JsonObject freqScale = top.createNestedObject("frequency"); freqScale[F("scale")] = FFTScalingMode; freqScale[F("profile")] = pinkIndex; //WLEDMM @@ -2720,6 +2799,9 @@ class AudioReactive : public Usermod { configComplete &= getJsonValue(top["experiments"][F("micLev")], micLevelMethod); configComplete &= getJsonValue(top["experiments"][F("freqDist")], freqDist); //configComplete &= getJsonValue(top["experiments"][F("freqRMS")], averageByRMS); +#ifdef FFT_USE_SLIDING_WINDOW + configComplete &= getJsonValue(top["experiments"][F("I2S_FastPath")], doSlidingFFT); +#endif configComplete &= getJsonValue(top["frequency"][F("scale")], FFTScalingMode); configComplete &= getJsonValue(top["frequency"][F("profile")], pinkIndex); //WLEDMM @@ -2829,6 +2911,13 @@ class AudioReactive : public Usermod { //oappend(SET_F("addOption(dd,'On',1);")); //oappend(SET_F("addInfo('AudioReactive:experiments:freqRMS',1,'☾');")); +#ifdef FFT_USE_SLIDING_WINDOW + oappend(SET_F("dd=addDropdown(ux,'experiments:I2S_FastPath');")); + oappend(SET_F("addOption(dd,'Off',0);")); + oappend(SET_F("addOption(dd,'On (⎌)',1);")); + oappend(SET_F("addInfo(ux+':experiments:I2S_FastPath',1,'☾');")); +#endif + oappend(SET_F("dd=addDropdown('AudioReactive','dynamics:limiter');")); oappend(SET_F("addOption(dd,'Off',0);")); oappend(SET_F("addOption(dd,'On',1);")); From a317af42fc991348d47aafa173fd89f4d1e3ee33 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Mon, 1 Jul 2024 16:37:44 +0200 Subject: [PATCH 046/200] trying to make effects respond to changes quicker in fastpath mode filter parameter tuning --- usermods/audioreactive/audio_reactive.h | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index 99e80f04..6f94415f 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -239,7 +239,11 @@ const double agcFollowFast[AGC_NUM_PRESETS] = { 1/192.f, 1/128.f, 1/256.f}; // 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 const double agcControlKi[AGC_NUM_PRESETS] = { 1.7f, 1.85f, 1.2f}; // AGC - PI control, integral gain parameter +#if defined(WLEDMM_FASTPATH) +const float agcSampleSmooth[AGC_NUM_PRESETS] = { 1/6.f, 1/5.f, 1/10.f}; // smoothing factor for sampleAgc (use rawSampleAgc if you want the non-smoothed value) +#else const float agcSampleSmooth[AGC_NUM_PRESETS] = { 1/12.f, 1/6.f, 1/16.f}; // smoothing factor for sampleAgc (use rawSampleAgc if you want the non-smoothed value) +#endif // AGC presets end static AudioSource *audioSource = nullptr; @@ -594,7 +598,9 @@ void FFTcode(void * parameter) #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); +#if FFTTASK_PRIORITY > 1 + if (strip.isServicing()) delay(1); +#endif #endif // normal mode: filter everything @@ -1364,7 +1370,11 @@ class AudioReactive : public Usermod { // update global vars ONCE - multAgc, sampleAGC, rawSampleAgc multAgc = multAgcTemp; +#if defined(WLEDMM_FASTPATH) + rawSampleAgc = 0.65f * tmpAgc + 0.35f * (float)rawSampleAgc; +#else rawSampleAgc = 0.8f * tmpAgc + 0.2f * (float)rawSampleAgc; +#endif // update smoothed AGC sample if (fabsf(tmpAgc) < 1.0f) sampleAgc = 0.5f * tmpAgc + 0.5f * sampleAgc; // fast path to zero @@ -1483,7 +1493,11 @@ class AudioReactive : public Usermod { } if (sampleMax < 0.5f) sampleMax = 0.0f; +#if defined(WLEDMM_FASTPATH) + sampleAvg = ((sampleAvg * 7.0f) + sampleAdj) / 8.0f; // make reactions a bit more "crisp" in fastpath mode +#else sampleAvg = ((sampleAvg * 15.0f) + sampleAdj) / 16.0f; // Smooth it out over the last 16 samples. +#endif sampleAvg = fabsf(sampleAvg); // make sure we have a positive value } // getSample() From 31a12f5098f4fd80e7e7c2ed5c8db67e22017e89 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 1 Jul 2024 21:10:45 +0200 Subject: [PATCH 047/200] AudioReactive -> ux before: String buffer usage: 3530 of 4037 bytes After: String buffer usage: 3183 of 4037 bytes --- usermods/audioreactive/audio_reactive.h | 86 ++++++++++++------------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index d9247f37..10748b57 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -2744,12 +2744,12 @@ class AudioReactive : public Usermod { //WLEDMM: add defaults #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) // -S3/-S2/-C3 don't support analog audio #ifdef AUDIOPIN - oappend(SET_F("xOpt('AudioReactive:analogmic:pin',1,' ⎌',")); oappendi(AUDIOPIN); oappend(");"); + oappend(SET_F("xOpt(ux+':analogmic:pin',1,' ⎌',")); oappendi(AUDIOPIN); oappend(");"); #endif - oappend(SET_F("aOpt('AudioReactive:analogmic:pin',1);")); //only analog options + oappend(SET_F("aOpt(ux+':analogmic:pin',1);")); //only analog options #endif - oappend(SET_F("dd=addDropdown('AudioReactive','digitalmic:type');")); + oappend(SET_F("dd=addDropdown(ux,'digitalmic:type');")); #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) #if SR_DMTYPE==0 oappend(SET_F("addOption(dd,'Generic Analog (⎌)',0);")); @@ -2800,50 +2800,50 @@ class AudioReactive : public Usermod { oappend(SET_F("addOption(dd,'WM8978 ☾',7);")); #endif #ifdef SR_SQUELCH - oappend(SET_F("addInfo('AudioReactive:config:squelch',1,'⎌ ")); oappendi(SR_SQUELCH); oappend("');"); // 0 is field type, 1 is actual field + oappend(SET_F("addInfo(ux+':config:squelch',1,'⎌ ")); oappendi(SR_SQUELCH); oappend("');"); // 0 is field type, 1 is actual field #endif #ifdef SR_GAIN - oappend(SET_F("addInfo('AudioReactive:config:gain',1,'⎌ ")); oappendi(SR_GAIN); oappend("');"); // 0 is field type, 1 is actual field + oappend(SET_F("addInfo(ux+':config:gain',1,'⎌ ")); oappendi(SR_GAIN); oappend("');"); // 0 is field type, 1 is actual field #endif - oappend(SET_F("dd=addDropdown('AudioReactive','config:AGC');")); + oappend(SET_F("dd=addDropdown(ux,'config:AGC');")); oappend(SET_F("addOption(dd,'Off',0);")); oappend(SET_F("addOption(dd,'Normal',1);")); oappend(SET_F("addOption(dd,'Vivid',2);")); oappend(SET_F("addOption(dd,'Lazy',3);")); //WLEDMM: experimental settings - oappend(SET_F("dd=addDropdown('AudioReactive','experiments:micLev');")); + oappend(SET_F("dd=addDropdown(ux,'experiments:micLev');")); oappend(SET_F("addOption(dd,'Floating (⎌)',0);")); oappend(SET_F("addOption(dd,'Freeze',1);")); oappend(SET_F("addOption(dd,'Fast Freeze',2);")); - oappend(SET_F("addInfo('AudioReactive:experiments:micLev',1,'☾');")); + oappend(SET_F("addInfo(ux+':experiments:micLev',1,'☾');")); - oappend(SET_F("dd=addDropdown('AudioReactive','experiments:freqDist');")); + oappend(SET_F("dd=addDropdown(ux,'experiments:freqDist');")); oappend(SET_F("addOption(dd,'Normal (⎌)',0);")); oappend(SET_F("addOption(dd,'RightShift',1);")); - oappend(SET_F("addInfo('AudioReactive:experiments:freqDist',1,'☾');")); + oappend(SET_F("addInfo(ux+':experiments:freqDist',1,'☾');")); - //oappend(SET_F("dd=addDropdown('AudioReactive','experiments:freqRMS');")); + //oappend(SET_F("dd=addDropdown(ux,'experiments:freqRMS');")); //oappend(SET_F("addOption(dd,'Off (⎌)',0);")); //oappend(SET_F("addOption(dd,'On',1);")); - //oappend(SET_F("addInfo('AudioReactive:experiments:freqRMS',1,'☾');")); + //oappend(SET_F("addInfo(ux+':experiments:freqRMS',1,'☾');")); - oappend(SET_F("dd=addDropdown('AudioReactive','dynamics:limiter');")); + oappend(SET_F("dd=addDropdown(ux,'dynamics:limiter');")); oappend(SET_F("addOption(dd,'Off',0);")); oappend(SET_F("addOption(dd,'On',1);")); - oappend(SET_F("addInfo('AudioReactive:dynamics:limiter',0,' On ');")); // 0 is field type, 1 is actual field - oappend(SET_F("addInfo('AudioReactive:dynamics:rise',1,'ms (♪ effects only)');")); - oappend(SET_F("addInfo('AudioReactive:dynamics:fall',1,'ms (♪ effects only)');")); + oappend(SET_F("addInfo(ux+':dynamics:limiter',0,' On ');")); // 0 is field type, 1 is actual field + oappend(SET_F("addInfo(ux+':dynamics:rise',1,'ms (♪ effects only)');")); + oappend(SET_F("addInfo(ux+':dynamics:fall',1,'ms (♪ effects only)');")); - oappend(SET_F("dd=addDropdown('AudioReactive','frequency:scale');")); + oappend(SET_F("dd=addDropdown(ux,'frequency:scale');")); oappend(SET_F("addOption(dd,'None',0);")); oappend(SET_F("addOption(dd,'Linear (Amplitude)',2);")); oappend(SET_F("addOption(dd,'Square Root (Energy)',3);")); oappend(SET_F("addOption(dd,'Logarithmic (Loudness)',1);")); //WLEDMM add defaults - oappend(SET_F("dd=addDropdown('AudioReactive','frequency:profile');")); + oappend(SET_F("dd=addDropdown(ux,'frequency:profile');")); #if SR_FREQ_PROF==0 oappend(SET_F("addOption(dd,'Generic Microphone (⎌)',0);")); #else @@ -2899,9 +2899,9 @@ class AudioReactive : public Usermod { #else oappend(SET_F("addOption(dd,'userdefined #2',9);")); #endif - oappend(SET_F("addInfo('AudioReactive:frequency:profile',1,'☾');")); + oappend(SET_F("addInfo(ux+':frequency:profile',1,'☾');")); #endif - oappend(SET_F("dd=addDropdown('AudioReactive','sync:mode');")); + oappend(SET_F("dd=addDropdown(ux,'sync:mode');")); oappend(SET_F("addOption(dd,'Off',0);")); // AUDIOSYNC_NONE #ifdef ARDUINO_ARCH_ESP32 oappend(SET_F("addOption(dd,'Send',1);")); // AUDIOSYNC_SEND @@ -2911,53 +2911,53 @@ class AudioReactive : public Usermod { 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("dd=addDropdown(ux,'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,'when receiving
Sync audio data with other WLEDs');")); // must append this to the last field of 'sync' + oappend(SET_F("addInfo(ux+':sync:check_sequence',1,'when receiving
Sync audio data with other WLEDs');")); // must append this to the last field of 'sync' - oappend(SET_F("addInfo('AudioReactive:digitalmic:type',1,'requires reboot!');")); // 0 is field type, 1 is actual field + oappend(SET_F("addInfo(ux+':digitalmic:type',1,'requires reboot!');")); // 0 is field type, 1 is actual field #ifdef ARDUINO_ARCH_ESP32 - oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',0,'sd/data/dout','I2S SD');")); + oappend(SET_F("addInfo(ux+':digitalmic:pin[]',0,'sd/data/dout','I2S SD');")); #ifdef I2S_SDPIN - oappend(SET_F("xOpt('AudioReactive:digitalmic:pin[]',0,' ⎌',")); oappendi(I2S_SDPIN); oappend(");"); + oappend(SET_F("xOpt(ux+':digitalmic:pin[]',0,' ⎌',")); oappendi(I2S_SDPIN); oappend(");"); #endif - oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',1,'ws/clk/lrck','I2S WS');")); - oappend(SET_F("dRO('AudioReactive:digitalmic:pin[]',1);")); // disable read only pins + oappend(SET_F("addInfo(ux+':digitalmic:pin[]',1,'ws/clk/lrck','I2S WS');")); + oappend(SET_F("dRO(ux+':digitalmic:pin[]',1);")); // disable read only pins #ifdef I2S_WSPIN - oappend(SET_F("xOpt('AudioReactive:digitalmic:pin[]',1,' ⎌',")); oappendi(I2S_WSPIN); oappend(");"); + oappend(SET_F("xOpt(ux+':digitalmic:pin[]',1,' ⎌',")); oappendi(I2S_WSPIN); oappend(");"); #endif - oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',2,'sck/bclk','I2S SCK');")); - oappend(SET_F("dRO('AudioReactive:digitalmic:pin[]',2);")); // disable read only pins + oappend(SET_F("addInfo(ux+':digitalmic:pin[]',2,'sck/bclk','I2S SCK');")); + oappend(SET_F("dRO(ux+':digitalmic:pin[]',2);")); // disable read only pins #ifdef I2S_CKPIN - oappend(SET_F("xOpt('AudioReactive:digitalmic:pin[]',2,' ⎌',")); oappendi(I2S_CKPIN); oappend(");"); + oappend(SET_F("xOpt(ux+':digitalmic:pin[]',2,' ⎌',")); oappendi(I2S_CKPIN); oappend(");"); #endif - oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',3,'master clock','I2S MCLK');")); - oappend(SET_F("dRO('AudioReactive:digitalmic:pin[]',3);")); // disable read only pins + oappend(SET_F("addInfo(ux+':digitalmic:pin[]',3,'master clock','I2S MCLK');")); + oappend(SET_F("dRO(ux+':digitalmic:pin[]',3);")); // disable read only pins #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) - oappend(SET_F("dOpt('AudioReactive:digitalmic:pin[]',3,2,2);")); //only use -1, 0, 1 or 3 - oappend(SET_F("dOpt('AudioReactive:digitalmic:pin[]',3,4,39);")); //only use -1, 0, 1 or 3 + oappend(SET_F("dOpt(ux+':digitalmic:pin[]',3,2,2);")); //only use -1, 0, 1 or 3 + oappend(SET_F("dOpt(ux+':digitalmic:pin[]',3,4,39);")); //only use -1, 0, 1 or 3 #endif #ifdef MCLK_PIN - oappend(SET_F("xOpt('AudioReactive:digitalmic:pin[]',3,' ⎌',")); oappendi(MCLK_PIN); oappend(");"); + oappend(SET_F("xOpt(ux+':digitalmic:pin[]',3,' ⎌',")); oappendi(MCLK_PIN); oappend(");"); #endif - oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',4,'','I2C SDA');")); - oappend(SET_F("rOpt('AudioReactive:digitalmic:pin[]',4,'use global (")); oappendi(i2c_sda); oappend(")',-1);"); + oappend(SET_F("addInfo(ux+':digitalmic:pin[]',4,'','I2C SDA');")); + oappend(SET_F("rOpt(ux+':digitalmic:pin[]',4,'use global (")); oappendi(i2c_sda); oappend(")',-1);"); #ifdef ES7243_SDAPIN - oappend(SET_F("xOpt('AudioReactive:digitalmic:pin[]',4,' ⎌',")); oappendi(ES7243_SDAPIN); oappend(");"); + oappend(SET_F("xOpt(ux+':digitalmic:pin[]',4,' ⎌',")); oappendi(ES7243_SDAPIN); oappend(");"); #endif - oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',5,'','I2C SCL');")); - oappend(SET_F("rOpt('AudioReactive:digitalmic:pin[]',5,'use global (")); oappendi(i2c_scl); oappend(")',-1);"); + oappend(SET_F("addInfo(ux+':digitalmic:pin[]',5,'','I2C SCL');")); + oappend(SET_F("rOpt(ux+':digitalmic:pin[]',5,'use global (")); oappendi(i2c_scl); oappend(")',-1);"); #ifdef ES7243_SCLPIN - oappend(SET_F("xOpt('AudioReactive:digitalmic:pin[]',5,' ⎌',")); oappendi(ES7243_SCLPIN); oappend(");"); + oappend(SET_F("xOpt(ux+':digitalmic:pin[]',5,' ⎌',")); oappendi(ES7243_SCLPIN); oappend(");"); #endif - oappend(SET_F("dRO('AudioReactive:digitalmic:pin[]',5);")); // disable read only pins + oappend(SET_F("dRO(ux+':digitalmic:pin[]',5);")); // disable read only pins #endif } From 29013a3f839bf52f378bd50e0e3c3c8829648bf0 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 1 Jul 2024 22:54:24 +0200 Subject: [PATCH 048/200] auto-replace long functions with short names adI = addInfo adD = addDropdown adO = addOption adF = addField before: String buffer usage: 3183 of 4037 bytes after: String buffer usage: 2805 of 4037 bytes --- wled00/data/settings_um.htm | 12 ++++++++++++ wled00/fcn_declare.h | 1 + wled00/util.cpp | 18 +++++++++++++++--- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/wled00/data/settings_um.htm b/wled00/data/settings_um.htm index 759643e6..19a8590f 100644 --- a/wled00/data/settings_um.htm +++ b/wled00/data/settings_um.htm @@ -146,6 +146,9 @@ urows += `
`; } } + function adF(k,f,o,a=false) { //shortcut for addField(key, field, (sub)object, isArray) + return addField(k,f,o,a); + } // https://stackoverflow.com/questions/39729741/javascript-change-input-text-to-select-option function addDropdown(um,fld) { let sel = d.createElement('select'); @@ -170,6 +173,9 @@ } return null; } + function adD(um,fld) { // shortcut for addDropdown(um,fld) + return addDropdown(um,fld); + } function addOption(sel,txt,val) { if (sel===null) return; // select object missing let opt = d.createElement("option"); @@ -181,6 +187,9 @@ if (c.value == sel.dataset.val) sel.selectedIndex = i; } } + function adO(sel,txt,val) { // shortcut for addOption(sel,txt,val) + return addOption(sel,txt,val); + } //WLEDMM: replace Option to set globals function rOpt(name,el,txt,val) { let obj = d.getElementsByName(name); @@ -285,6 +294,9 @@ if (txt2!="") obj[el].insertAdjacentHTML('beforebegin', txt2 + ' '); //add pre texts } } + function adI(name,el,txt, txt2="") { // shortcut for addInfo(name,el,txt, txt2="") + return addInfo(name,el,txt, txt2); + } // add Help Button function addHB(um) { diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 38a18b09..aa0645d0 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -362,6 +362,7 @@ int getNumVal(const String* req, uint16_t pos); void parseNumber(const char* str, byte* val, byte minv=0, byte maxv=255); bool getVal(JsonVariant elem, byte* val, byte minv=0, byte maxv=255); bool updateVal(const char* req, const char* key, byte* val, byte minv=0, byte maxv=255); +void oappendUseDeflate(bool OnOff); // enable / disable string squeezing bool oappend(const char* txt); // append new c string to temp buffer efficiently bool oappendi(int i); // append new number to temp buffer efficiently void sappend(char stype, const char* key, int val); diff --git a/wled00/util.cpp b/wled00/util.cpp index 896b57e3..1bcf2ad0 100644 --- a/wled00/util.cpp +++ b/wled00/util.cpp @@ -144,22 +144,34 @@ bool oappendi(int i) return oappend(s); } +static bool squeezeStrings = false; +void oappendUseDeflate(bool OnOff) { squeezeStrings = OnOff; } bool oappend(const char* txt) { - uint16_t len = strlen(txt); + String str = squeezeStrings ? String(txt) : String(""); + if (squeezeStrings) { + // simple fixed-dictionary deflate + str.replace(F("addField("), F("adF(")); + str.replace(F("addDropdown("), F("adD(")); + str.replace(F("addOption("), F("adO(")); + str.replace(F("addInfo("), F("adI(")); + } + const char* finalTxt = squeezeStrings ? str.c_str() : txt; + + size_t len = strlen(finalTxt); if ((obuf == nullptr) || (olen + len >= SETTINGS_STACK_BUF_SIZE)) { // sanity checks if (obuf == nullptr) { USER_PRINTLN(F("oappend() error: obuf == nullptr.")); } else { USER_PRINT(F("oappend() error: buffer full. Increase SETTINGS_STACK_BUF_SIZE for ")); USER_PRINTF("%2u bytes \t\"", len /*1 + olen + len - SETTINGS_STACK_BUF_SIZE*/); - USER_PRINT(txt); + USER_PRINT(finalTxt); USER_PRINTLN(F("\"")); errorFlag = ERR_LOW_AJAX_MEM; } return false; // buffer full } - strcpy(obuf + olen, txt); + strcpy(obuf + olen, finalTxt); olen += len; return true; } From 8cfd0df71037d9a073a9a856edb4edd3ffdbffcb Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 1 Jul 2024 22:54:51 +0200 Subject: [PATCH 049/200] Update xml.cpp --- wled00/xml.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/wled00/xml.cpp b/wled00/xml.cpp index 72fdf219..efc5840c 100644 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -792,6 +792,7 @@ void getSettingsJS(AsyncWebServerRequest* request, byte subPage, char* dest) //W if (subPage == 8) //usermods { appendGPIOinfo(); + oappendUseDeflate(true); // allow replacing long functions with shorter equivalents - only works for usermods if (!request->hasParam("um") ) { // oappend(SET_F("numM=")); // oappendi(usermods.getModCount()); @@ -834,6 +835,7 @@ void getSettingsJS(AsyncWebServerRequest* request, byte subPage, char* dest) //W Usermod *usermod = usermods.lookupName(request->getParam("um")->value().c_str()); if (usermod) usermod->appendConfigData(); } + oappendUseDeflate(false); // oappend(SET_F("console.log('getSettingsJS fix ro pins', d.max_gpio, d.ro_gpio);")); oappend(SET_F("pinPost();")); From 27dd6bddac5d951637788aba52ac476f9806681c Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 1 Jul 2024 22:56:18 +0200 Subject: [PATCH 050/200] AudioReactive:digitalmic:pin[] --> uxp before: String buffer usage: 2805 of 4037 bytes after: String buffer usage: 2566 of 4037 bytes --- usermods/audioreactive/audio_reactive.h | 43 +++++++++++++------------ 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index 10748b57..10e95153 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -2738,7 +2738,8 @@ class AudioReactive : public Usermod { void appendConfigData() { - oappend(SET_F("ux='AudioReactive';")); // fingers crossed that "ux" isn't already used as JS var, html post parameter or css style + oappend(SET_F("ux='AudioReactive';")); // ux = shortcut for Audioreactive - fingers crossed that "ux" isn't already used as JS var, html post parameter or css style + oappend(SET_F("uxp=ux+':digitalmic:pin[]';")); // uxp = shortcut for AudioReactive:digitalmic:pin[] oappend(SET_F("addInfo(ux+':help',0,'');")); #ifdef ARDUINO_ARCH_ESP32 //WLEDMM: add defaults @@ -2919,45 +2920,45 @@ class AudioReactive : public Usermod { oappend(SET_F("addInfo(ux+':digitalmic:type',1,'requires reboot!');")); // 0 is field type, 1 is actual field #ifdef ARDUINO_ARCH_ESP32 - oappend(SET_F("addInfo(ux+':digitalmic:pin[]',0,'sd/data/dout','I2S SD');")); + oappend(SET_F("addInfo(uxp,0,'sd/data/dout','I2S SD');")); #ifdef I2S_SDPIN - oappend(SET_F("xOpt(ux+':digitalmic:pin[]',0,' ⎌',")); oappendi(I2S_SDPIN); oappend(");"); + oappend(SET_F("xOpt(uxp,0,' ⎌',")); oappendi(I2S_SDPIN); oappend(");"); #endif - oappend(SET_F("addInfo(ux+':digitalmic:pin[]',1,'ws/clk/lrck','I2S WS');")); - oappend(SET_F("dRO(ux+':digitalmic:pin[]',1);")); // disable read only pins + oappend(SET_F("addInfo(uxp,1,'ws/clk/lrck','I2S WS');")); + oappend(SET_F("dRO(uxp,1);")); // disable read only pins #ifdef I2S_WSPIN - oappend(SET_F("xOpt(ux+':digitalmic:pin[]',1,' ⎌',")); oappendi(I2S_WSPIN); oappend(");"); + oappend(SET_F("xOpt(uxp,1,' ⎌',")); oappendi(I2S_WSPIN); oappend(");"); #endif - oappend(SET_F("addInfo(ux+':digitalmic:pin[]',2,'sck/bclk','I2S SCK');")); - oappend(SET_F("dRO(ux+':digitalmic:pin[]',2);")); // disable read only pins + oappend(SET_F("addInfo(uxp,2,'sck/bclk','I2S SCK');")); + oappend(SET_F("dRO(uxp,2);")); // disable read only pins #ifdef I2S_CKPIN - oappend(SET_F("xOpt(ux+':digitalmic:pin[]',2,' ⎌',")); oappendi(I2S_CKPIN); oappend(");"); + oappend(SET_F("xOpt(uxp,2,' ⎌',")); oappendi(I2S_CKPIN); oappend(");"); #endif - oappend(SET_F("addInfo(ux+':digitalmic:pin[]',3,'master clock','I2S MCLK');")); - oappend(SET_F("dRO(ux+':digitalmic:pin[]',3);")); // disable read only pins + oappend(SET_F("addInfo(uxp,3,'master clock','I2S MCLK');")); + oappend(SET_F("dRO(uxp,3);")); // disable read only pins #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) - oappend(SET_F("dOpt(ux+':digitalmic:pin[]',3,2,2);")); //only use -1, 0, 1 or 3 - oappend(SET_F("dOpt(ux+':digitalmic:pin[]',3,4,39);")); //only use -1, 0, 1 or 3 + oappend(SET_F("dOpt(uxp,3,2,2);")); //only use -1, 0, 1 or 3 + oappend(SET_F("dOpt(uxp,3,4,39);")); //only use -1, 0, 1 or 3 #endif #ifdef MCLK_PIN - oappend(SET_F("xOpt(ux+':digitalmic:pin[]',3,' ⎌',")); oappendi(MCLK_PIN); oappend(");"); + oappend(SET_F("xOpt(uxp,3,' ⎌',")); oappendi(MCLK_PIN); oappend(");"); #endif - oappend(SET_F("addInfo(ux+':digitalmic:pin[]',4,'','I2C SDA');")); - oappend(SET_F("rOpt(ux+':digitalmic:pin[]',4,'use global (")); oappendi(i2c_sda); oappend(")',-1);"); + oappend(SET_F("addInfo(uxp,4,'','I2C SDA');")); + oappend(SET_F("rOpt(uxp,4,'use global (")); oappendi(i2c_sda); oappend(")',-1);"); #ifdef ES7243_SDAPIN - oappend(SET_F("xOpt(ux+':digitalmic:pin[]',4,' ⎌',")); oappendi(ES7243_SDAPIN); oappend(");"); + oappend(SET_F("xOpt(uxp,4,' ⎌',")); oappendi(ES7243_SDAPIN); oappend(");"); #endif - oappend(SET_F("addInfo(ux+':digitalmic:pin[]',5,'','I2C SCL');")); - oappend(SET_F("rOpt(ux+':digitalmic:pin[]',5,'use global (")); oappendi(i2c_scl); oappend(")',-1);"); + oappend(SET_F("addInfo(uxp,5,'','I2C SCL');")); + oappend(SET_F("rOpt(uxp,5,'use global (")); oappendi(i2c_scl); oappend(")',-1);"); #ifdef ES7243_SCLPIN - oappend(SET_F("xOpt(ux+':digitalmic:pin[]',5,' ⎌',")); oappendi(ES7243_SCLPIN); oappend(");"); + oappend(SET_F("xOpt(uxp,5,' ⎌',")); oappendi(ES7243_SCLPIN); oappend(");"); #endif - oappend(SET_F("dRO(ux+':digitalmic:pin[]',5);")); // disable read only pins + oappend(SET_F("dRO(uxp,5);")); // disable read only pins #endif } From 28cb5c88b6efffb6f618b069cd1561ff2385ea73 Mon Sep 17 00:00:00 2001 From: Troy <5659019+troyhacks@users.noreply.github.com> Date: Wed, 3 Jul 2024 16:20:44 -0400 Subject: [PATCH 051/200] Skeleton, not working yet. --- usermods/audioreactive/audio_reactive.h | 22 +++ usermods/audioreactive/audio_source.h | 174 ++++++++++++++++++++++++ 2 files changed, 196 insertions(+) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index b958184c..ff1c9ed5 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -1923,6 +1923,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 8: + #ifdef use_ac101_mic + DEBUGSR_PRINTLN(F("AR: AC101 Source (Mic)")); + #else + DEBUGSR_PRINTLN(F("AR: AC101 Source (Line-In)")); + #endif + audioSource = new AC101Source(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; @@ -2802,6 +2819,11 @@ class AudioReactive : public Usermod { #else oappend(SET_F("addOption(dd,'WM8978 ☾',7);")); #endif + #if SR_DMTYPE==8 + oappend(SET_F("addOption(dd,'AC101 ☾ (⎌)',8);")); + #else + oappend(SET_F("addOption(dd,'AC101 ☾',8);")); + #endif #ifdef SR_SQUELCH oappend(SET_F("addInfo('AudioReactive:config:squelch',1,'⎌ ")); oappendi(SR_SQUELCH); oappend("');"); // 0 is field type, 1 is actual field #endif diff --git a/usermods/audioreactive/audio_source.h b/usermods/audioreactive/audio_source.h index cf3e3b74..c83af476 100644 --- a/usermods/audioreactive/audio_source.h +++ b/usermods/audioreactive/audio_source.h @@ -736,6 +736,180 @@ class WM8978Source : public I2SSource { }; +class AC101Source : public I2SSource { + private: + // I2C initialization functions for WM8978 + void _ac101I2cBegin() { + Wire.setClock(400000); + } + + void _ac101I2cWrite(uint8_t reg_addr, uint16_t val) { + #ifndef AC101_ADDR + #define AC101_ADDR 0x1A + #endif + char send_buff[3]; + send_buff[0] = reg_addr; + send_buff[1] = (val >> 8) & 0xff; + send_buff[2] = val & 0xff; + Wire.beginTransmission(AC101_ADDR); + Wire.write((const uint8_t*)send_buff, 3); + uint8_t i2cErr = Wire.endTransmission(); // i2cErr == 0 means OK + if (i2cErr != 0) { + DEBUGSR_PRINTF("AR: AC101 I2C write failed with error=%d (addr=0x%X, reg 0x%X, val 0x%X).\n", i2cErr, AC101_ADDR, reg_addr, val); + } + } + + void _ac101InitAdc() { + // https://files.seeedstudio.com/wiki/ReSpeaker_6-Mics_Circular_Array_kit_for_Raspberry_Pi/reg/AC101_User_Manual_v1.1.pdf + // + _ac101I2cBegin(); + + #define CHIP_AUDIO_RS 0x00 + #define PLL_CTRL1 0x01 + #define PLL_CTRL2 0x02 + #define SYSCLK_CTRL 0x03 + #define MOD_CLK_ENA 0x04 + #define MOD_RST_CTRL 0x05 + #define I2S_SR_CTRL 0x06 + #define I2S1LCK_CTRL 0x10 + #define I2S1_SDOUT_CTRL 0x11 + #define I2S1_SDIN_CTRL 0x12 + #define I2S1_MXR_SRC 0x13 + #define I2S1_VOL_CTRL1 0x14 + #define I2S1_VOL_CTRL2 0x15 + #define I2S1_VOL_CTRL3 0x16 + #define I2S1_VOL_CTRL4 0x17 + #define I2S1_MXR_GAIN 0x18 + #define ADC_DIG_CTRL 0x40 + #define ADC_VOL_CTRL 0x41 + #define HMIC_CTRL1 0x44 + #define HMIC_CTRL2 0x45 + #define HMIC_STATUS 0x46 + #define DAC_DIG_CTRL 0x48 + #define DAC_VOL_CTRL 0x49 + #define DAC_MXR_SRC 0x4c + #define DAC_MXR_GAIN 0x4d + #define ADC_APC_CTRL 0x50 + #define ADC_SRC 0x51 + #define ADC_SRCBST_CTRL 0x52 + #define OMIXER_DACA_CTRL 0x53 + #define OMIXER_SR 0x54 + #define OMIXER_BST1_CTRL 0x55 + #define HPOUT_CTRL 0x56 + #define SPKOUT_CTRL 0x58 + #define AC_DAC_DAPCTRL 0xa0 + #define AC_DAC_DAPHHPFC 0xa1 + #define AC_DAC_DAPLHPFC 0xa2 + #define AC_DAC_DAPLHAVC 0xa3 + #define AC_DAC_DAPLLAVC 0xa4 + #define AC_DAC_DAPRHAVC 0xa5 + #define AC_DAC_DAPRLAVC 0xa6 + #define AC_DAC_DAPHGDEC 0xa7 + #define AC_DAC_DAPLGDEC 0xa8 + #define AC_DAC_DAPHGATC 0xa9 + #define AC_DAC_DAPLGATC 0xaa + #define AC_DAC_DAPHETHD 0xab + #define AC_DAC_DAPLETHD 0xac + #define AC_DAC_DAPHGKPA 0xad + #define AC_DAC_DAPLGKPA 0xae + #define AC_DAC_DAPHGOPA 0xaf + #define AC_DAC_DAPLGOPA 0xb0 + #define AC_DAC_DAPOPT 0xb1 + #define DAC_DAP_ENA 0xb5 + + _ac101I2cWrite(CHIP_AUDIO_RS, 0x123); // Reset (any value written does a reset) + vTaskDelay(1000 / portTICK_PERIOD_MS); + _ac101I2cWrite(SPKOUT_CTRL, 0xe880); + + //Enable the PLL from 256*44.1KHz MCLK source + _ac101I2cWrite(PLL_CTRL1, 0x014f); + //res |= ac101_write_reg(PLL_CTRL2, 0x83c0); + _ac101I2cWrite(PLL_CTRL2, 0x8600); + + //Clocking system + _ac101I2cWrite(SYSCLK_CTRL, 0x8b08); + _ac101I2cWrite(MOD_CLK_ENA, 0x800c); + _ac101I2cWrite(MOD_RST_CTRL, 0x800c); + _ac101I2cWrite(I2S_SR_CTRL, 0b0111000000000000); //sample rate 22050 Hz + //AIF config + _ac101I2cWrite(I2S1LCK_CTRL, 0x8850); //BCLK/LRCK + _ac101I2cWrite(I2S1_SDOUT_CTRL, 0xc000); // + _ac101I2cWrite(I2S1_SDIN_CTRL, 0xc000); + _ac101I2cWrite(I2S1_MXR_SRC, 0x2200); // + + _ac101I2cWrite(ADC_SRCBST_CTRL, 0xccc4); + _ac101I2cWrite(ADC_SRC, 0x2020); + _ac101I2cWrite(ADC_DIG_CTRL, 0x8000); + _ac101I2cWrite(ADC_APC_CTRL, 0xbbc3); + + //Path Configuration + _ac101I2cWrite(DAC_MXR_SRC, 0xcc00); + _ac101I2cWrite(DAC_DIG_CTRL, 0x8000); + _ac101I2cWrite(OMIXER_SR, 0x0081); + _ac101I2cWrite(OMIXER_DACA_CTRL, 0xf080); //} + + //* Enable Speaker output + // _ac101I2cWrite(0x58, 0xeabd); + + _ac101I2cWrite(ADC_SRC, 0b0000010000001000); // Line-in to ADC + _ac101I2cWrite(ADC_DIG_CTRL, 0x8000); + _ac101I2cWrite(ADC_APC_CTRL, 0x3bc0); + //I2S1_SDOUT_CTRL + //res |= _ac101I2cWrite(PLL_CTRL2, 0x8120); + _ac101I2cWrite(MOD_CLK_ENA, 0x800c); + _ac101I2cWrite(MOD_RST_CTRL, 0x800c); + //res |= _ac101I2cWrite(0x06, 0x3000); + //* Enable Headphoe output + _ac101I2cWrite(OMIXER_DACA_CTRL, 0xff80); + _ac101I2cWrite(HPOUT_CTRL, 0xc3c1); + _ac101I2cWrite(HPOUT_CTRL, 0xcb00); + vTaskDelay(100 / portTICK_PERIOD_MS); + _ac101I2cWrite(HPOUT_CTRL, 0xfbc0); + + _ac101I2cWrite(OMIXER_SR, 0b0000010000001000); // default all 0 + + //* Enable Speaker output + _ac101I2cWrite(SPKOUT_CTRL, 0xeabd); + + + } + + public: + AC101Source(SRate_t sampleRate, int blockSize, float sampleScale = 1.0f, bool i2sMaster=true) : + I2SSource(sampleRate, blockSize, sampleScale, i2sMaster) { + _config.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT; + }; + + void initialize(int8_t i2swsPin, int8_t i2ssdPin, int8_t i2sckPin, int8_t mclkPin) { + DEBUGSR_PRINTLN("AC101Source:: initialize();"); + + // if ((i2sckPin < 0) || (mclkPin < 0)) { // WLEDMM not sure if this check is needed here, too + // ERRORSR_PRINTF("\nAR: invalid I2S WM8978 pin: SCK=%d, MCLK=%d\n", i2sckPin, mclkPin); + // return; + // } + // BUG: "use global I2C pins" are valid as -1, and -1 is seen as invalid here. + // Workaround: Set I2C pins here, which will also set them globally. + // Bug also exists in ES7243. + if ((i2c_sda < 0) || (i2c_scl < 0)) { // check that global I2C pins are not "undefined" + ERRORSR_PRINTF("\nAR: invalid AC101 global I2C pins: SDA=%d, SCL=%d\n", i2c_sda, i2c_scl); + return; + } + if (!pinManager.joinWire(i2c_sda, i2c_scl)) { // WLEDMM specific: start I2C with globally defined pins + ERRORSR_PRINTF("\nAR: failed to join I2C bus with SDA=%d, SCL=%d\n", i2c_sda, i2c_scl); + return; + } + + // First route mclk, then configure ADC over I2C, then configure I2S + _ac101InitAdc(); + I2SSource::initialize(i2swsPin, i2ssdPin, i2sckPin, mclkPin); + } + + void deinitialize() { + I2SSource::deinitialize(); + } + +}; + #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 2, 0) #if !defined(SOC_I2S_SUPPORTS_ADC) && !defined(SOC_I2S_SUPPORTS_ADC_DAC) #warning this MCU does not support analog sound input From 788347440466e84df4c58d899aa1081c6b3251b3 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Thu, 4 Jul 2024 18:37:31 +0200 Subject: [PATCH 052/200] postProcessFFTResults minor refactoring * limiter on/off : made the logic a bit clearer. * use a simpler way of writing filters: a = n*a + (1-n)*b --> a = a+ n*(b-a) no functional impact. --- usermods/audioreactive/audio_reactive.h | 42 ++++++++++++++----------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index b6983c6d..79172591 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -946,27 +946,33 @@ static void postProcessFFTResults(bool noiseGateOpen, int numberOfChannels) // p if(fftCalc[i] < 0) fftCalc[i] = 0; } - // smooth results - rise fast, fall slower - if(fftCalc[i] > fftAvg[i]) // rise fast - fftAvg[i] = fftCalc[i] *0.78f + 0.22f*fftAvg[i]; // will need approx 1-2 cycles (50ms) for converging against fftCalc[i] - else { // fall slow - if (decayTime < 250) fftAvg[i] = fftCalc[i]*0.4f + 0.6f*fftAvg[i]; - else if (decayTime < 500) fftAvg[i] = fftCalc[i]*0.33f + 0.67f*fftAvg[i]; - else if (decayTime < 1000) fftAvg[i] = fftCalc[i]*0.22f + 0.78f*fftAvg[i]; // approx 5 cycles (225ms) for falling to zero - else if (decayTime < 2000) fftAvg[i] = fftCalc[i]*0.17f + 0.83f*fftAvg[i]; // default - approx 9 cycles (225ms) for falling to zero - else if (decayTime < 3000) fftAvg[i] = fftCalc[i]*0.14f + 0.86f*fftAvg[i]; // approx 14 cycles (350ms) for falling to zero - else if (decayTime < 4000) fftAvg[i] = fftCalc[i]*0.1f + 0.9f*fftAvg[i]; - else fftAvg[i] = fftCalc[i]*0.05f + 0.95f*fftAvg[i]; - } - // constrain internal vars - just to be sure - fftCalc[i] = constrain(fftCalc[i], 0.0f, 1023.0f); - fftAvg[i] = constrain(fftAvg[i], 0.0f, 1023.0f); - float currentResult; - if(limiterOn == true) + if(limiterOn == true) { + // Limiter ON -> smooth results -> rise fast, fall slower + if(fftCalc[i] > fftAvg[i]) { // rise fast + fftAvg[i] += 0.78f * (fftCalc[i] - fftAvg[i]); // will need approx 1-2 cycles (50ms) for converging against fftCalc[i] + } else { // fall slow + if (decayTime < 150) fftAvg[i] += 0.50f * (fftCalc[i] - fftAvg[i]); + else if (decayTime < 250) fftAvg[i] += 0.40f * (fftCalc[i] - fftAvg[i]); + else if (decayTime < 500) fftAvg[i] += 0.33f * (fftCalc[i] - fftAvg[i]); + else if (decayTime < 1000) fftAvg[i] += 0.22f * (fftCalc[i] - fftAvg[i]); // approx 5 cycles (225ms) for falling to zero + else if (decayTime < 2000) fftAvg[i] += 0.17f * (fftCalc[i] - fftAvg[i]); // default - approx 9 cycles (225ms) for falling to zero + else if (decayTime < 3000) fftAvg[i] += 0.14f * (fftCalc[i] - fftAvg[i]); // approx 14 cycles (350ms) for falling to zero + else if (decayTime < 4000) fftAvg[i] += 0.10f * (fftCalc[i] - fftAvg[i]); + else fftAvg[i] += 0.05f * (fftCalc[i] - fftAvg[i]); + } + // constrain internal vars - just to be sure + fftCalc[i] = constrain(fftCalc[i], 0.0f, 1023.0f); + fftAvg[i] = constrain(fftAvg[i], 0.0f, 1023.0f); + // use filtered result currentResult = fftAvg[i]; - else + } else { + // Limiter OFF -> no adjustments + fftCalc[i] = constrain(fftCalc[i], 0.0f, 1023.0f); + fftAvg[i] = fftCalc[i]; // keep filters up-to-date + // use not filtered result currentResult = fftCalc[i]; + } switch (FFTScalingMode) { case 1: From 7d04660b913fedecc62f635f519444866e2ac4a2 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Thu, 4 Jul 2024 20:06:13 +0200 Subject: [PATCH 053/200] postProcessFFTResults adjustments for FastPath samples arrive 2x as fast in fast mode - adjust filters accordingly --- usermods/audioreactive/audio_reactive.h | 58 ++++++++++++++----------- 1 file changed, 33 insertions(+), 25 deletions(-) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index 79172591..66175681 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -285,7 +285,7 @@ static float mapf(float x, float in_min, float in_max, float out_min, float out_ static float fftAddAvg(int from, int to); // average of several FFT result bins void FFTcode(void * parameter); // audio processing task: read samples, run FFT, fill GEQ channels from FFT results static void runMicFilter(uint16_t numSamples, float *sampleBuffer); // pre-filtering of raw samples (band-pass) -static void postProcessFFTResults(bool noiseGateOpen, int numberOfChannels); // post-processing and post-amp of GEQ channels +static void postProcessFFTResults(bool noiseGateOpen, int numberOfChannels, bool i2sFastpath); // post-processing and post-amp of GEQ channels static TaskHandle_t FFT_Task = nullptr; @@ -860,8 +860,8 @@ void FFTcode(void * parameter) // 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 + + postProcessFFTResults((fabsf(volumeSmth) > 0.25f)? true : false, NUM_GEQ_CHANNELS, usingOldSamples); // this function modifies fftCalc, fftAvg and fftResult #if defined(WLED_DEBUG) || defined(SR_DEBUG)|| defined(SR_STATS) // timing @@ -933,7 +933,7 @@ static void runMicFilter(uint16_t numSamples, float *sampleBuffer) // p } } -static void postProcessFFTResults(bool noiseGateOpen, int numberOfChannels) // post-processing and post-amp of GEQ channels +static void postProcessFFTResults(bool noiseGateOpen, int numberOfChannels, bool i2sFastpath) // post-processing and post-amp of GEQ channels { for (int i=0; i < numberOfChannels; i++) { @@ -946,34 +946,42 @@ static void postProcessFFTResults(bool noiseGateOpen, int numberOfChannels) // p if(fftCalc[i] < 0) fftCalc[i] = 0; } - float currentResult; + float speed = 1.0f; // filter correction for sampling speed -> 1.0 in normal mode (43hz) + if (i2sFastpath) speed = 0.6931471805599453094f * 1.1f; // -> ln(2) from math, *1.1 from my gut feeling ;-) in fast mode (86hz) + if(limiterOn == true) { - // Limiter ON -> smooth results -> rise fast, fall slower + // Limiter ON -> smooth results if(fftCalc[i] > fftAvg[i]) { // rise fast - fftAvg[i] += 0.78f * (fftCalc[i] - fftAvg[i]); // will need approx 1-2 cycles (50ms) for converging against fftCalc[i] + fftAvg[i] += speed * 0.78f * (fftCalc[i] - fftAvg[i]); // will need approx 1-2 cycles (50ms) for converging against fftCalc[i] } else { // fall slow - if (decayTime < 150) fftAvg[i] += 0.50f * (fftCalc[i] - fftAvg[i]); - else if (decayTime < 250) fftAvg[i] += 0.40f * (fftCalc[i] - fftAvg[i]); - else if (decayTime < 500) fftAvg[i] += 0.33f * (fftCalc[i] - fftAvg[i]); - else if (decayTime < 1000) fftAvg[i] += 0.22f * (fftCalc[i] - fftAvg[i]); // approx 5 cycles (225ms) for falling to zero - else if (decayTime < 2000) fftAvg[i] += 0.17f * (fftCalc[i] - fftAvg[i]); // default - approx 9 cycles (225ms) for falling to zero - else if (decayTime < 3000) fftAvg[i] += 0.14f * (fftCalc[i] - fftAvg[i]); // approx 14 cycles (350ms) for falling to zero - else if (decayTime < 4000) fftAvg[i] += 0.10f * (fftCalc[i] - fftAvg[i]); - else fftAvg[i] += 0.05f * (fftCalc[i] - fftAvg[i]); + if (decayTime < 150) fftAvg[i] += speed * 0.50f * (fftCalc[i] - fftAvg[i]); + else if (decayTime < 250) fftAvg[i] += speed * 0.40f * (fftCalc[i] - fftAvg[i]); + else if (decayTime < 500) fftAvg[i] += speed * 0.33f * (fftCalc[i] - fftAvg[i]); + else if (decayTime < 1000) fftAvg[i] += speed * 0.22f * (fftCalc[i] - fftAvg[i]); // approx 5 cycles (225ms) for falling to zero + else if (decayTime < 2000) fftAvg[i] += speed * 0.17f * (fftCalc[i] - fftAvg[i]); // default - approx 9 cycles (225ms) for falling to zero + else if (decayTime < 3000) fftAvg[i] += speed * 0.14f * (fftCalc[i] - fftAvg[i]); // approx 14 cycles (350ms) for falling to zero + else if (decayTime < 4000) fftAvg[i] += speed * 0.10f * (fftCalc[i] - fftAvg[i]); + else fftAvg[i] += speed * 0.05f * (fftCalc[i] - fftAvg[i]); } - // constrain internal vars - just to be sure - fftCalc[i] = constrain(fftCalc[i], 0.0f, 1023.0f); - fftAvg[i] = constrain(fftAvg[i], 0.0f, 1023.0f); - // use filtered result - currentResult = fftAvg[i]; } else { - // Limiter OFF -> no adjustments - fftCalc[i] = constrain(fftCalc[i], 0.0f, 1023.0f); - fftAvg[i] = fftCalc[i]; // keep filters up-to-date - // use not filtered result - currentResult = fftCalc[i]; + // Limiter OFF + if (i2sFastpath) { + // fast mode -> average last two results + float tmp = fftCalc[i]; + fftCalc[i] = 0.7f * tmp + 0.3f * fftAvg[i]; + fftAvg[i] = tmp; // store current sample for next run + } else { + // normal mode -> no adjustments + fftAvg[i] = fftCalc[i]; // keep filters up-to-date + } } + // constrain internal vars - just to be sure + fftCalc[i] = constrain(fftCalc[i], 0.0f, 1023.0f); + fftAvg[i] = constrain(fftAvg[i], 0.0f, 1023.0f); + + float currentResult = limiterOn ? fftAvg[i] : fftCalc[i]; // continue with filtered result (limiter on) or unfiltered result (limiter off) + switch (FFTScalingMode) { case 1: // Logarithmic scaling From 9b14de06b2b1229c9f43bd3e3134691a1b687498 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Thu, 4 Jul 2024 20:13:57 +0200 Subject: [PATCH 054/200] fix build error on -S2, -C3 --- usermods/audioreactive/audio_reactive.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index 66175681..8f4ed3af 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -861,7 +861,11 @@ void FFTcode(void * parameter) // post-processing of frequency channels (pink noise adjustment, AGC, smoothing, scaling) if (pinkIndex > MAX_PINK) pinkIndex = MAX_PINK; +#ifdef FFT_USE_SLIDING_WINDOW postProcessFFTResults((fabsf(volumeSmth) > 0.25f)? true : false, NUM_GEQ_CHANNELS, usingOldSamples); // this function modifies fftCalc, fftAvg and fftResult +#else + postProcessFFTResults((fabsf(volumeSmth) > 0.25f)? true : false, NUM_GEQ_CHANNELS, false); // this function modifies fftCalc, fftAvg and fftResult +#endif #if defined(WLED_DEBUG) || defined(SR_DEBUG)|| defined(SR_STATS) // timing From 160e66a766b2905d4fb496fc108e178f3542624d Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Thu, 4 Jul 2024 21:44:09 +0200 Subject: [PATCH 055/200] user-selectable FFT window functions experiments: user-selectable FFT "windowing" options https://en.wikipedia.org/wiki/Window_function#Cosine-sum_windows --- usermods/audioreactive/audio_reactive.h | 164 ++++++++++++++---------- 1 file changed, 99 insertions(+), 65 deletions(-) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index 8f4ed3af..255815fd 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -179,7 +179,7 @@ static bool limiterOn = false; // bool: enable / disable dynamic static bool limiterOn = true; #endif #ifdef FFT_USE_SLIDING_WINDOW -static uint16_t attackTime = 14; // int: attack time in milliseconds. Default 0.014sec +static uint16_t attackTime = 24; // int: attack time in milliseconds. Default 0.024sec static uint16_t decayTime = 250; // int: decay time in milliseconds. New default 250ms. #else static uint16_t attackTime = 50; // int: attack time in milliseconds. Default 0.08sec @@ -257,6 +257,7 @@ static constexpr uint8_t averageByRMS = false; // false: us static constexpr uint8_t averageByRMS = true; // false: use mean value, true: use RMS (root mean squared). use better method on fast MCUs. #endif static uint8_t freqDist = 0; // 0=old 1=rightshift mode +static uint8_t fftWindow = 0; // FFT windowing function (0 = default) #ifdef FFT_USE_SLIDING_WINDOW static uint8_t doSlidingFFT = 1; // 1 = use sliding window FFT (faster & more accurate) #endif @@ -685,17 +686,41 @@ void FFTcode(void * parameter) micReal_max2 = datMax; #endif #endif + + float wc = 1.0; // FFT window correction factor, relative to Blackman_Harris + // run FFT (takes 3-5ms on ESP32) //if (fabsf(sampleAvg) > 0.25f) { // noise gate open if (fabsf(volumeSmth) > 0.25f) { // noise gate open if ((skipSecondFFT == false) || (isFirstRun == true)) { // run FFT (takes 2-3ms on ESP32, ~12ms on ESP32-S2, ~30ms on -C3) if (doDCRemoval) FFT.dcRemoval(); // remove DC offset - #if !defined(FFT_PREFER_EXACT_PEAKS) - FFT.windowing( FFTWindow::Flat_top, FFTDirection::Forward); // Weigh data using "Flat Top" function - better amplitude accuracy - #else - FFT.windowing(FFTWindow::Blackman_Harris, FFTDirection::Forward); // Weigh data using "Blackman- Harris" window - sharp peaks due to excellent sideband rejection + switch(fftWindow) { // apply FFT window + case 1: + FFT.windowing(FFTWindow::Hann, FFTDirection::Forward); // recommended for 50% overlap + wc = 0.66415918066; // 1.8554726898 * 2.0 + break; + case 2: + FFT.windowing( FFTWindow::Nuttall, FFTDirection::Forward); + wc = 0.9916873881f; // 2.8163172034 * 2.0 + break; + case 3: + FFT.windowing( FFTWindow::Hamming, FFTDirection::Forward); + wc = 0.664159180663f; // 1.8549343278 * 2.0 + break; + case 4: + FFT.windowing( FFTWindow::Flat_top, FFTDirection::Forward); // Weigh data using "Flat Top" function - better amplitude preservation, low frequency accuracy + wc = 1.276771793156f; // 3.5659039231 * 2.0 + break; + case 0: // falls through + default: + FFT.windowing(FFTWindow::Blackman_Harris, FFTDirection::Forward); // Weigh data using "Blackman- Harris" window - sharp peaks due to excellent sideband rejection + wc = 1.0f; // 2.7929062517 * 2.0 + } + #ifdef FFT_USE_SLIDING_WINDOW + if (usingOldSamples) wc = wc * 1.10f; // compensate for loss caused by averaging #endif + FFT.compute( FFTDirection::Forward ); // Compute FFT FFT.complexToMagnitude(); // Compute magnitudes vReal[0] = 0; // The remaining DC offset on the signal produces a strong spike on position 0 that should be eliminated to avoid issues. @@ -715,6 +740,7 @@ void FFTcode(void * parameter) #else FFT.majorPeak(FFT_MajorPeak, FFT_Magnitude); // let the effects know which freq was most dominant #endif + FFT_Magnitude *= wc; // apply correction factor if (FFT_MajorPeak < (SAMPLE_RATE / samplesFFT)) {FFT_MajorPeak = 1.0f; FFT_Magnitude = 0;} // too low - use zero if (FFT_MajorPeak > (0.42f * SAMPLE_RATE)) {FFT_MajorPeak = last_majorpeak; FFT_Magnitude = last_magnitude;} // too high - keep last peak @@ -764,23 +790,23 @@ void FFTcode(void * parameter) * End frequency = Start frequency * multiplier ^ 16 * Multiplier = (End frequency/ Start frequency) ^ 1/16 * Multiplier = 1.320367784 - */ // Range - fftCalc[ 0] = fftAddAvg(2,4); // 60 - 100 - fftCalc[ 1] = fftAddAvg(4,5); // 80 - 120 - fftCalc[ 2] = fftAddAvg(5,7); // 100 - 160 - fftCalc[ 3] = fftAddAvg(7,9); // 140 - 200 - 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,29); // 420 - 600 - fftCalc[ 8] = fftAddAvg(29,37); // 580 - 760 - fftCalc[ 9] = fftAddAvg(37,48); // 740 - 980 - fftCalc[10] = fftAddAvg(48,64); // 960 - 1300 - fftCalc[11] = fftAddAvg(64,84); // 1280 - 1700 - fftCalc[12] = fftAddAvg(84,111); // 1680 - 2240 - fftCalc[13] = fftAddAvg(111,147); // 2220 - 2960 - fftCalc[14] = fftAddAvg(147,194); // 2940 - 3900 - fftCalc[15] = fftAddAvg(194,250); // 3880 - 5000 // avoid the last 5 bins, which are usually inaccurate + */ // Range + fftCalc[ 0] = wc * fftAddAvg(2,4); // 60 - 100 + fftCalc[ 1] = wc * fftAddAvg(4,5); // 80 - 120 + fftCalc[ 2] = wc * fftAddAvg(5,7); // 100 - 160 + fftCalc[ 3] = wc * fftAddAvg(7,9); // 140 - 200 + fftCalc[ 4] = wc * fftAddAvg(9,12); // 180 - 260 + fftCalc[ 5] = wc * fftAddAvg(12,16); // 240 - 340 + fftCalc[ 6] = wc * fftAddAvg(16,21); // 320 - 440 + fftCalc[ 7] = wc * fftAddAvg(21,29); // 420 - 600 + fftCalc[ 8] = wc * fftAddAvg(29,37); // 580 - 760 + fftCalc[ 9] = wc * fftAddAvg(37,48); // 740 - 980 + fftCalc[10] = wc * fftAddAvg(48,64); // 960 - 1300 + fftCalc[11] = wc * fftAddAvg(64,84); // 1280 - 1700 + fftCalc[12] = wc * fftAddAvg(84,111); // 1680 - 2240 + fftCalc[13] = wc * fftAddAvg(111,147); // 2220 - 2960 + fftCalc[14] = wc * fftAddAvg(147,194); // 2940 - 3900 + fftCalc[15] = wc * fftAddAvg(194,250); // 3880 - 5000 // avoid the last 5 bins, which are usually inaccurate #else //WLEDMM: different distributions if (freqDist == 0) { @@ -788,60 +814,60 @@ void FFTcode(void * parameter) // bins frequency range if (useInputFilter==1) { // skip frequencies below 100hz - fftCalc[ 0] = 0.8f * fftAddAvg(3,3); - fftCalc[ 1] = 0.9f * fftAddAvg(4,4); - fftCalc[ 2] = fftAddAvg(5,5); - fftCalc[ 3] = fftAddAvg(6,6); + fftCalc[ 0] = wc * 0.8f * fftAddAvg(3,3); + fftCalc[ 1] = wc * 0.9f * fftAddAvg(4,4); + fftCalc[ 2] = wc * fftAddAvg(5,5); + fftCalc[ 3] = wc * fftAddAvg(6,6); // don't use the last bins from 206 to 255. - fftCalc[15] = fftAddAvg(165,205) * 0.75f; // 40 7106 - 8828 high -- with some damping + fftCalc[15] = wc * fftAddAvg(165,205) * 0.75f; // 40 7106 - 8828 high -- with some damping } else { - fftCalc[ 0] = fftAddAvg(1,1); // 1 43 - 86 sub-bass - fftCalc[ 1] = fftAddAvg(2,2); // 1 86 - 129 bass - fftCalc[ 2] = fftAddAvg(3,4); // 2 129 - 216 bass - fftCalc[ 3] = fftAddAvg(5,6); // 2 216 - 301 bass + midrange + fftCalc[ 0] = wc * fftAddAvg(1,1); // 1 43 - 86 sub-bass + fftCalc[ 1] = wc * fftAddAvg(2,2); // 1 86 - 129 bass + fftCalc[ 2] = wc * fftAddAvg(3,4); // 2 129 - 216 bass + fftCalc[ 3] = wc * fftAddAvg(5,6); // 2 216 - 301 bass + midrange // don't use the last bins from 216 to 255. They are usually contaminated by aliasing (aka noise) - fftCalc[15] = fftAddAvg(165,215) * 0.70f; // 50 7106 - 9259 high -- with some damping + fftCalc[15] = wc * fftAddAvg(165,215) * 0.70f; // 50 7106 - 9259 high -- with some damping } - fftCalc[ 4] = fftAddAvg(7,9); // 3 301 - 430 midrange - fftCalc[ 5] = fftAddAvg(10,12); // 3 430 - 560 midrange - fftCalc[ 6] = fftAddAvg(13,18); // 5 560 - 818 midrange - fftCalc[ 7] = fftAddAvg(19,25); // 7 818 - 1120 midrange -- 1Khz should always be the center ! - fftCalc[ 8] = fftAddAvg(26,32); // 7 1120 - 1421 midrange - fftCalc[ 9] = fftAddAvg(33,43); // 9 1421 - 1895 midrange - fftCalc[10] = fftAddAvg(44,55); // 12 1895 - 2412 midrange + high mid - fftCalc[11] = fftAddAvg(56,69); // 14 2412 - 3015 high mid - fftCalc[12] = fftAddAvg(70,85); // 16 3015 - 3704 high mid - 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 + fftCalc[ 4] = wc * fftAddAvg(7,9); // 3 301 - 430 midrange + fftCalc[ 5] = wc * fftAddAvg(10,12); // 3 430 - 560 midrange + fftCalc[ 6] = wc * fftAddAvg(13,18); // 5 560 - 818 midrange + fftCalc[ 7] = wc * fftAddAvg(19,25); // 7 818 - 1120 midrange -- 1Khz should always be the center ! + fftCalc[ 8] = wc * fftAddAvg(26,32); // 7 1120 - 1421 midrange + fftCalc[ 9] = wc * fftAddAvg(33,43); // 9 1421 - 1895 midrange + fftCalc[10] = wc * fftAddAvg(44,55); // 12 1895 - 2412 midrange + high mid + fftCalc[11] = wc * fftAddAvg(56,69); // 14 2412 - 3015 high mid + fftCalc[12] = wc * fftAddAvg(70,85); // 16 3015 - 3704 high mid + fftCalc[13] = wc * fftAddAvg(86,103); // 18 3704 - 4479 high mid + fftCalc[14] = wc * fftAddAvg(104,164) * 0.88f; // 61 4479 - 7106 high mid + high -- with slight damping } 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); - fftCalc[ 1] = 0.9f * fftAddAvg(2,2); - fftCalc[ 2] = fftAddAvg(3,3); - fftCalc[ 3] = fftAddAvg(4,4); + fftCalc[ 0] = wc * 0.8f * fftAddAvg(1,1); + fftCalc[ 1] = wc * 0.9f * fftAddAvg(2,2); + fftCalc[ 2] = wc * fftAddAvg(3,3); + fftCalc[ 3] = wc * fftAddAvg(4,4); // don't use the last bins from 206 to 255. - fftCalc[15] = fftAddAvg(165,205) * 0.75f; // 40 7106 - 8828 high -- with some damping + fftCalc[15] = wc * fftAddAvg(165,205) * 0.75f; // 40 7106 - 8828 high -- with some damping } else { - fftCalc[ 0] = fftAddAvg(1,1); // 1 43 - 86 sub-bass - fftCalc[ 1] = fftAddAvg(2,2); // 1 86 - 129 bass - fftCalc[ 2] = fftAddAvg(3,3); // 2 129 - 216 bass - fftCalc[ 3] = fftAddAvg(4,4); // 2 216 - 301 bass + midrange + fftCalc[ 0] = wc * fftAddAvg(1,1); // 1 43 - 86 sub-bass + fftCalc[ 1] = wc * fftAddAvg(2,2); // 1 86 - 129 bass + fftCalc[ 2] = wc * fftAddAvg(3,3); // 2 129 - 216 bass + fftCalc[ 3] = wc * fftAddAvg(4,4); // 2 216 - 301 bass + midrange // don't use the last bins from 216 to 255. They are usually contaminated by aliasing (aka noise) - fftCalc[15] = fftAddAvg(165,215) * 0.70f; // 50 7106 - 9259 high -- with some damping + fftCalc[15] = wc * fftAddAvg(165,215) * 0.70f; // 50 7106 - 9259 high -- with some damping } - fftCalc[ 4] = fftAddAvg(5,6); // 3 301 - 430 midrange - fftCalc[ 5] = fftAddAvg(7,8); // 3 430 - 560 midrange - fftCalc[ 6] = fftAddAvg(9,10); // 5 560 - 818 midrange - fftCalc[ 7] = fftAddAvg(11,13); // 7 818 - 1120 midrange -- 1Khz should always be the center ! - fftCalc[ 8] = fftAddAvg(14,18); // 7 1120 - 1421 midrange - fftCalc[ 9] = fftAddAvg(19,25); // 9 1421 - 1895 midrange - fftCalc[10] = fftAddAvg(26,36); // 12 1895 - 2412 midrange + high mid - fftCalc[11] = fftAddAvg(37,45); // 14 2412 - 3015 high mid - fftCalc[12] = fftAddAvg(46,66); // 16 3015 - 3704 high mid - fftCalc[13] = fftAddAvg(67,97); // 18 3704 - 4479 high mid - fftCalc[14] = fftAddAvg(98,164) * 0.88f; // 61 4479 - 7106 high mid + high -- with slight damping + fftCalc[ 4] = wc * fftAddAvg(5,6); // 3 301 - 430 midrange + fftCalc[ 5] = wc * fftAddAvg(7,8); // 3 430 - 560 midrange + fftCalc[ 6] = wc * fftAddAvg(9,10); // 5 560 - 818 midrange + fftCalc[ 7] = wc * fftAddAvg(11,13); // 7 818 - 1120 midrange -- 1Khz should always be the center ! + fftCalc[ 8] = wc * fftAddAvg(14,18); // 7 1120 - 1421 midrange + fftCalc[ 9] = wc * fftAddAvg(19,25); // 9 1421 - 1895 midrange + fftCalc[10] = wc * fftAddAvg(26,36); // 12 1895 - 2412 midrange + high mid + fftCalc[11] = wc * fftAddAvg(37,45); // 14 2412 - 3015 high mid + fftCalc[12] = wc * fftAddAvg(46,66); // 16 3015 - 3704 high mid + fftCalc[13] = wc * fftAddAvg(67,97); // 18 3704 - 4479 high mid + fftCalc[14] = wc * fftAddAvg(98,164) * 0.88f; // 61 4479 - 7106 high mid + high -- with slight damping } #endif } else { // noise gate closed - just decay old values @@ -2756,7 +2782,7 @@ class AudioReactive : public Usermod { poweruser[F("micLev")] = micLevelMethod; poweruser[F("freqDist")] = freqDist; //poweruser[F("freqRMS")] = averageByRMS; - + poweruser[F("FFT_Window")] = fftWindow; #ifdef FFT_USE_SLIDING_WINDOW poweruser[F("I2S_FastPath")] = doSlidingFFT; #endif @@ -2831,6 +2857,7 @@ class AudioReactive : public Usermod { configComplete &= getJsonValue(top["experiments"][F("micLev")], micLevelMethod); configComplete &= getJsonValue(top["experiments"][F("freqDist")], freqDist); //configComplete &= getJsonValue(top["experiments"][F("freqRMS")], averageByRMS); + configComplete &= getJsonValue(top["experiments"][F("FFT_Window")], fftWindow); #ifdef FFT_USE_SLIDING_WINDOW configComplete &= getJsonValue(top["experiments"][F("I2S_FastPath")], doSlidingFFT); #endif @@ -2944,6 +2971,13 @@ class AudioReactive : public Usermod { //oappend(SET_F("addOption(dd,'On',1);")); //oappend(SET_F("addInfo(ux+':experiments:freqRMS',1,'☾');")); + oappend(SET_F("dd=addDropdown(ux,'experiments:FFT_Window');")); + oappend(SET_F("addOption(dd,'Blackman-Harris (MM standard)',0);")); + oappend(SET_F("addOption(dd,'Hann (balanced)',1);")); + oappend(SET_F("addOption(dd,'Nuttall (more accurate)',2);")); + oappend(SET_F("addOption(dd,'Hamming',3);")); + oappend(SET_F("addOption(dd,'Flat-Top (AC WLED, inaccurate)',4);")); + #ifdef FFT_USE_SLIDING_WINDOW oappend(SET_F("dd=addDropdown(ux,'experiments:I2S_FastPath');")); oappend(SET_F("addOption(dd,'Off',0);")); From cba883c663143b2068b0c2c9bca72edb50707635 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Fri, 5 Jul 2024 15:12:41 +0200 Subject: [PATCH 056/200] parameter tuning *some filter parameter tinkering * restore FFT_MajPeakSmth in UDP SoundSync receiver --- usermods/audioreactive/audio_reactive.h | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index 255815fd..496f6866 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -240,7 +240,7 @@ const double agcFollowSlow[AGC_NUM_PRESETS] = {1/6144.f,1/4096.f,1/8192.f}; // const double agcControlKp[AGC_NUM_PRESETS] = { 0.6f, 1.5f, 0.65f}; // AGC - PI control, proportional gain parameter const double agcControlKi[AGC_NUM_PRESETS] = { 1.7f, 1.85f, 1.2f}; // AGC - PI control, integral gain parameter #if defined(WLEDMM_FASTPATH) -const float agcSampleSmooth[AGC_NUM_PRESETS] = { 1/6.f, 1/5.f, 1/10.f}; // smoothing factor for sampleAgc (use rawSampleAgc if you want the non-smoothed value) +const float agcSampleSmooth[AGC_NUM_PRESETS] = { 1/8.f, 1/5.f, 1/12.f}; // smoothing factor for sampleAgc (use rawSampleAgc if you want the non-smoothed value) #else const float agcSampleSmooth[AGC_NUM_PRESETS] = { 1/12.f, 1/6.f, 1/16.f}; // smoothing factor for sampleAgc (use rawSampleAgc if you want the non-smoothed value) #endif @@ -1434,13 +1434,8 @@ class AudioReactive : public Usermod { { float sampleAdj; // Gain adjusted sample value 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; -#else - const float weighting = 0.2f; // Exponential filter weighting. Will be adjustable in a future release. + const float weighting = 0.18f; // Exponential filter weighting. Will be adjustable in a future release. const float weighting2 = 0.073f; // Exponential filter weighting, for rising signal (a bit more robust against spikes) -#endif 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 static bool isFrozen = false; static bool haveSilence = true; @@ -1492,9 +1487,11 @@ class AudioReactive : public Usermod { if ((micLevelMethod == 2) && !haveSilence && (expAdjF >= (1.5f * float(soundSquelch)))) isFrozen = true; // fast freeze mode: freeze micLevel once the volume rises 50% above squelch - //expAdjF = (micInNoDC <= soundSquelch) ? 0: expAdjF; // simple noise gate - experimental - expAdjF = (expAdjF <= soundSquelch) ? 0: expAdjF; // simple noise gate - if ((soundSquelch == 0) && (expAdjF < 0.25f)) expAdjF = 0; // do something meaningfull when "squelch = 0" + // simple noise gate + if ((expAdjF <= soundSquelch) || ((soundSquelch == 0) && (expAdjF < 0.25f))) { + expAdjF = 0.0f; + micInNoDC = 0.0f; + } if (expAdjF <= 0.5f) haveSilence = true; @@ -1515,7 +1512,7 @@ class AudioReactive : public Usermod { sampleAdj = tmpSample * sampleGain / 40.0f * inputLevel/128.0f + tmpSample / 16.0f; // Adjust the gain. with inputLevel adjustment sampleReal = tmpSample; - sampleAdj = fmax(fmin(sampleAdj, 255), 0); // Question: why are we limiting the value to 8 bits ??? + sampleAdj = fmax(fmin(sampleAdj, 255.0f), 0.0f); // Question: why are we limiting the value to 8 bits ??? sampleRaw = (int16_t)sampleAdj; // ONLY update sample ONCE!!!! // keep "peak" sample, but decay value if current sample is below peak @@ -1538,7 +1535,7 @@ class AudioReactive : public Usermod { if (sampleMax < 0.5f) sampleMax = 0.0f; #if defined(WLEDMM_FASTPATH) - sampleAvg = ((sampleAvg * 7.0f) + sampleAdj) / 8.0f; // make reactions a bit more "crisp" in fastpath mode + sampleAvg = ((sampleAvg * 11.0f) + sampleAdj) / 12.0f; // make reactions a bit more "crisp" in fastpath mode #else sampleAvg = ((sampleAvg * 15.0f) + sampleAdj) / 16.0f; // Smooth it out over the last 16 samples. #endif @@ -1791,6 +1788,9 @@ 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 +#ifdef ARDUINO_ARCH_ESP32 + FFT_MajPeakSmth = FFT_MajPeakSmth + 0.42f * (FFT_MajorPeak - FFT_MajPeakSmth); // simulate smooth value +#endif agcSensitivity = 128.0f; // substitute - V2 format does not include this value zeroCrossingCount = receivedPacket.zeroCrossingCount; From bd997f7056d0940a77ea325ba5ea185cfd402f89 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Fri, 5 Jul 2024 15:37:03 +0200 Subject: [PATCH 057/200] added Blackman window --- usermods/audioreactive/audio_reactive.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index 496f6866..6ec8fb23 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -704,6 +704,10 @@ void FFTcode(void * parameter) FFT.windowing( FFTWindow::Nuttall, FFTDirection::Forward); wc = 0.9916873881f; // 2.8163172034 * 2.0 break; + case 5: + FFT.windowing( FFTWindow::Blackman, FFTDirection::Forward); + wc = 0.84762867875f; // 2.3673474360 * 2.0 + break; case 3: FFT.windowing( FFTWindow::Hamming, FFTDirection::Forward); wc = 0.664159180663f; // 1.8549343278 * 2.0 @@ -2975,6 +2979,7 @@ class AudioReactive : public Usermod { oappend(SET_F("addOption(dd,'Blackman-Harris (MM standard)',0);")); oappend(SET_F("addOption(dd,'Hann (balanced)',1);")); oappend(SET_F("addOption(dd,'Nuttall (more accurate)',2);")); + oappend(SET_F("addOption(dd,'Blackman',5);")); oappend(SET_F("addOption(dd,'Hamming',3);")); oappend(SET_F("addOption(dd,'Flat-Top (AC WLED, inaccurate)',4);")); From 4d03af6466f68891fe1ec696188542c51aac4c13 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Fri, 5 Jul 2024 17:53:43 +0200 Subject: [PATCH 058/200] mic quality dropdown, cleanup * mic quality: When set to "low noise" or "perfect", only minimal smoothing is performed on the "Mic Volume" input. Every filter adds a delay, so this option can lead to better on-spot responses from effects. * cleanup: removed a few unused variables and unused code. --- usermods/audioreactive/audio_reactive.h | 236 +++++++++++------------- 1 file changed, 104 insertions(+), 132 deletions(-) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index 6ec8fb23..2de84087 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -178,6 +178,7 @@ static bool limiterOn = false; // bool: enable / disable dynamic #else static bool limiterOn = true; #endif +static uint8_t micQuality = 0; // affects input filtering; 0 normal, 1 minimal filtering, 2 no filtering #ifdef FFT_USE_SLIDING_WINDOW static uint16_t attackTime = 24; // int: attack time in milliseconds. Default 0.024sec static uint16_t decayTime = 250; // int: decay time in milliseconds. New default 250ms. @@ -690,7 +691,6 @@ void FFTcode(void * parameter) float wc = 1.0; // FFT window correction factor, relative to Blackman_Harris // run FFT (takes 3-5ms on ESP32) - //if (fabsf(sampleAvg) > 0.25f) { // noise gate open if (fabsf(volumeSmth) > 0.25f) { // noise gate open if ((skipSecondFFT == false) || (isFirstRun == true)) { // run FFT (takes 2-3ms on ESP32, ~12ms on ESP32-S2, ~30ms on -C3) @@ -776,7 +776,6 @@ void FFTcode(void * parameter) } if ((skipSecondFFT == false) || (isFirstRun == true)) { - 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 vReal[i] = t / 16.0f; // Reduce magnitude. Want end result to be scaled linear and ~4096 max. @@ -785,102 +784,72 @@ void FFTcode(void * parameter) // mapping of FFT result bins to frequency channels //if (fabsf(sampleAvg) > 0.25f) { // noise gate open if (fabsf(volumeSmth) > 0.25f) { // noise gate open -#if 0 - /* This FFT post processing is a DIY endeavour. What we really need is someone with sound engineering expertise to do a great job here AND most importantly, that the animations look GREAT as a result. - * - * 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 determine the bins. - * End frequency = Start frequency * multiplier ^ 16 - * Multiplier = (End frequency/ Start frequency) ^ 1/16 - * Multiplier = 1.320367784 - */ // Range - fftCalc[ 0] = wc * fftAddAvg(2,4); // 60 - 100 - fftCalc[ 1] = wc * fftAddAvg(4,5); // 80 - 120 - fftCalc[ 2] = wc * fftAddAvg(5,7); // 100 - 160 - fftCalc[ 3] = wc * fftAddAvg(7,9); // 140 - 200 - fftCalc[ 4] = wc * fftAddAvg(9,12); // 180 - 260 - fftCalc[ 5] = wc * fftAddAvg(12,16); // 240 - 340 - fftCalc[ 6] = wc * fftAddAvg(16,21); // 320 - 440 - fftCalc[ 7] = wc * fftAddAvg(21,29); // 420 - 600 - fftCalc[ 8] = wc * fftAddAvg(29,37); // 580 - 760 - fftCalc[ 9] = wc * fftAddAvg(37,48); // 740 - 980 - fftCalc[10] = wc * fftAddAvg(48,64); // 960 - 1300 - fftCalc[11] = wc * fftAddAvg(64,84); // 1280 - 1700 - fftCalc[12] = wc * fftAddAvg(84,111); // 1680 - 2240 - fftCalc[13] = wc * fftAddAvg(111,147); // 2220 - 2960 - fftCalc[14] = wc * fftAddAvg(147,194); // 2940 - 3900 - fftCalc[15] = wc * fftAddAvg(194,250); // 3880 - 5000 // avoid the last 5 bins, which are usually inaccurate -#else - //WLEDMM: different distributions - if (freqDist == 0) { - /* new mapping, optimized for 22050 Hz by softhack007 --- update: removed overlap */ - // bins frequency range - if (useInputFilter==1) { - // skip frequencies below 100hz - fftCalc[ 0] = wc * 0.8f * fftAddAvg(3,3); - fftCalc[ 1] = wc * 0.9f * fftAddAvg(4,4); - fftCalc[ 2] = wc * fftAddAvg(5,5); - fftCalc[ 3] = wc * fftAddAvg(6,6); - // don't use the last bins from 206 to 255. - fftCalc[15] = wc * fftAddAvg(165,205) * 0.75f; // 40 7106 - 8828 high -- with some damping - } else { - fftCalc[ 0] = wc * fftAddAvg(1,1); // 1 43 - 86 sub-bass - fftCalc[ 1] = wc * fftAddAvg(2,2); // 1 86 - 129 bass - fftCalc[ 2] = wc * fftAddAvg(3,4); // 2 129 - 216 bass - fftCalc[ 3] = wc * fftAddAvg(5,6); // 2 216 - 301 bass + midrange - // don't use the last bins from 216 to 255. They are usually contaminated by aliasing (aka noise) - fftCalc[15] = wc * fftAddAvg(165,215) * 0.70f; // 50 7106 - 9259 high -- with some damping - } - fftCalc[ 4] = wc * fftAddAvg(7,9); // 3 301 - 430 midrange - fftCalc[ 5] = wc * fftAddAvg(10,12); // 3 430 - 560 midrange - fftCalc[ 6] = wc * fftAddAvg(13,18); // 5 560 - 818 midrange - fftCalc[ 7] = wc * fftAddAvg(19,25); // 7 818 - 1120 midrange -- 1Khz should always be the center ! - fftCalc[ 8] = wc * fftAddAvg(26,32); // 7 1120 - 1421 midrange - fftCalc[ 9] = wc * fftAddAvg(33,43); // 9 1421 - 1895 midrange - fftCalc[10] = wc * fftAddAvg(44,55); // 12 1895 - 2412 midrange + high mid - fftCalc[11] = wc * fftAddAvg(56,69); // 14 2412 - 3015 high mid - fftCalc[12] = wc * fftAddAvg(70,85); // 16 3015 - 3704 high mid - fftCalc[13] = wc * fftAddAvg(86,103); // 18 3704 - 4479 high mid - fftCalc[14] = wc * fftAddAvg(104,164) * 0.88f; // 61 4479 - 7106 high mid + high -- with slight damping - } - else if (freqDist == 1) { //WLEDMM: Rightshift: note ewowi: frequencies in comments are not correct - if (useInputFilter==1) { - // skip frequencies below 100hz - fftCalc[ 0] = wc * 0.8f * fftAddAvg(1,1); - fftCalc[ 1] = wc * 0.9f * fftAddAvg(2,2); - fftCalc[ 2] = wc * fftAddAvg(3,3); - fftCalc[ 3] = wc * fftAddAvg(4,4); - // don't use the last bins from 206 to 255. - fftCalc[15] = wc * fftAddAvg(165,205) * 0.75f; // 40 7106 - 8828 high -- with some damping - } else { - fftCalc[ 0] = wc * fftAddAvg(1,1); // 1 43 - 86 sub-bass - fftCalc[ 1] = wc * fftAddAvg(2,2); // 1 86 - 129 bass - fftCalc[ 2] = wc * fftAddAvg(3,3); // 2 129 - 216 bass - fftCalc[ 3] = wc * fftAddAvg(4,4); // 2 216 - 301 bass + midrange - // don't use the last bins from 216 to 255. They are usually contaminated by aliasing (aka noise) - fftCalc[15] = wc * fftAddAvg(165,215) * 0.70f; // 50 7106 - 9259 high -- with some damping - } - fftCalc[ 4] = wc * fftAddAvg(5,6); // 3 301 - 430 midrange - fftCalc[ 5] = wc * fftAddAvg(7,8); // 3 430 - 560 midrange - fftCalc[ 6] = wc * fftAddAvg(9,10); // 5 560 - 818 midrange - fftCalc[ 7] = wc * fftAddAvg(11,13); // 7 818 - 1120 midrange -- 1Khz should always be the center ! - fftCalc[ 8] = wc * fftAddAvg(14,18); // 7 1120 - 1421 midrange - fftCalc[ 9] = wc * fftAddAvg(19,25); // 9 1421 - 1895 midrange - fftCalc[10] = wc * fftAddAvg(26,36); // 12 1895 - 2412 midrange + high mid - fftCalc[11] = wc * fftAddAvg(37,45); // 14 2412 - 3015 high mid - fftCalc[12] = wc * fftAddAvg(46,66); // 16 3015 - 3704 high mid - fftCalc[13] = wc * fftAddAvg(67,97); // 18 3704 - 4479 high mid - fftCalc[14] = wc * fftAddAvg(98,164) * 0.88f; // 61 4479 - 7106 high mid + high -- with slight damping - } -#endif - } else { // noise gate closed - just decay old values - isFirstRun = false; - 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; + //WLEDMM: different distributions + if (freqDist == 0) { + /* new mapping, optimized for 22050 Hz by softhack007 --- update: removed overlap */ + // bins frequency range + if (useInputFilter==1) { + // skip frequencies below 100hz + fftCalc[ 0] = wc * 0.8f * fftAddAvg(3,3); + fftCalc[ 1] = wc * 0.9f * fftAddAvg(4,4); + fftCalc[ 2] = wc * fftAddAvg(5,5); + fftCalc[ 3] = wc * fftAddAvg(6,6); + // don't use the last bins from 206 to 255. + fftCalc[15] = wc * fftAddAvg(165,205) * 0.75f; // 40 7106 - 8828 high -- with some damping + } else { + fftCalc[ 0] = wc * fftAddAvg(1,1); // 1 43 - 86 sub-bass + fftCalc[ 1] = wc * fftAddAvg(2,2); // 1 86 - 129 bass + fftCalc[ 2] = wc * fftAddAvg(3,4); // 2 129 - 216 bass + fftCalc[ 3] = wc * fftAddAvg(5,6); // 2 216 - 301 bass + midrange + // don't use the last bins from 216 to 255. They are usually contaminated by aliasing (aka noise) + fftCalc[15] = wc * fftAddAvg(165,215) * 0.70f; // 50 7106 - 9259 high -- with some damping + } + fftCalc[ 4] = wc * fftAddAvg(7,9); // 3 301 - 430 midrange + fftCalc[ 5] = wc * fftAddAvg(10,12); // 3 430 - 560 midrange + fftCalc[ 6] = wc * fftAddAvg(13,18); // 5 560 - 818 midrange + fftCalc[ 7] = wc * fftAddAvg(19,25); // 7 818 - 1120 midrange -- 1Khz should always be the center ! + fftCalc[ 8] = wc * fftAddAvg(26,32); // 7 1120 - 1421 midrange + fftCalc[ 9] = wc * fftAddAvg(33,43); // 9 1421 - 1895 midrange + fftCalc[10] = wc * fftAddAvg(44,55); // 12 1895 - 2412 midrange + high mid + fftCalc[11] = wc * fftAddAvg(56,69); // 14 2412 - 3015 high mid + fftCalc[12] = wc * fftAddAvg(70,85); // 16 3015 - 3704 high mid + fftCalc[13] = wc * fftAddAvg(86,103); // 18 3704 - 4479 high mid + fftCalc[14] = wc * fftAddAvg(104,164) * 0.88f; // 61 4479 - 7106 high mid + high -- with slight damping + } else if (freqDist == 1) { //WLEDMM: Rightshift: note ewowi: frequencies in comments are not correct + if (useInputFilter==1) { + // skip frequencies below 100hz + fftCalc[ 0] = wc * 0.8f * fftAddAvg(1,1); + fftCalc[ 1] = wc * 0.9f * fftAddAvg(2,2); + fftCalc[ 2] = wc * fftAddAvg(3,3); + fftCalc[ 3] = wc * fftAddAvg(4,4); + // don't use the last bins from 206 to 255. + fftCalc[15] = wc * fftAddAvg(165,205) * 0.75f; // 40 7106 - 8828 high -- with some damping + } else { + fftCalc[ 0] = wc * fftAddAvg(1,1); // 1 43 - 86 sub-bass + fftCalc[ 1] = wc * fftAddAvg(2,2); // 1 86 - 129 bass + fftCalc[ 2] = wc * fftAddAvg(3,3); // 2 129 - 216 bass + fftCalc[ 3] = wc * fftAddAvg(4,4); // 2 216 - 301 bass + midrange + // don't use the last bins from 216 to 255. They are usually contaminated by aliasing (aka noise) + fftCalc[15] = wc * fftAddAvg(165,215) * 0.70f; // 50 7106 - 9259 high -- with some damping } + fftCalc[ 4] = wc * fftAddAvg(5,6); // 3 301 - 430 midrange + fftCalc[ 5] = wc * fftAddAvg(7,8); // 3 430 - 560 midrange + fftCalc[ 6] = wc * fftAddAvg(9,10); // 5 560 - 818 midrange + fftCalc[ 7] = wc * fftAddAvg(11,13); // 7 818 - 1120 midrange -- 1Khz should always be the center ! + fftCalc[ 8] = wc * fftAddAvg(14,18); // 7 1120 - 1421 midrange + fftCalc[ 9] = wc * fftAddAvg(19,25); // 9 1421 - 1895 midrange + fftCalc[10] = wc * fftAddAvg(26,36); // 12 1895 - 2412 midrange + high mid + fftCalc[11] = wc * fftAddAvg(37,45); // 14 2412 - 3015 high mid + fftCalc[12] = wc * fftAddAvg(46,66); // 16 3015 - 3704 high mid + fftCalc[13] = wc * fftAddAvg(67,97); // 18 3704 - 4479 high mid + fftCalc[14] = wc * fftAddAvg(98,164) * 0.88f; // 61 4479 - 7106 high mid + high -- with slight damping } + } else { // noise gate closed - just decay old values + isFirstRun = false; + 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; + } } memcpy(lastFftCalc, fftCalc, sizeof(lastFftCalc)); // make a backup of last "good" channels @@ -1211,7 +1180,6 @@ class AudioReactive : public Usermod { 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 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. @@ -1268,7 +1236,6 @@ class AudioReactive : public Usermod { //PLOT_PRINT("sampleAgc:"); PLOT_PRINT(sampleAgc); PLOT_PRINT("\t"); //PLOT_PRINT("sampleAvg:"); PLOT_PRINT(sampleAvg); PLOT_PRINT("\t"); //PLOT_PRINT("sampleReal:"); PLOT_PRINT(sampleReal); PLOT_PRINT("\t"); - //PLOT_PRINT("micIn:"); PLOT_PRINT(micIn); PLOT_PRINT("\t"); //PLOT_PRINT("sample:"); PLOT_PRINT(sample); PLOT_PRINT("\t"); //PLOT_PRINT("sampleMax:"); PLOT_PRINT(sampleMax); PLOT_PRINT("\t"); //PLOT_PRINT("multAgc:"); PLOT_PRINT(multAgc, 4); PLOT_PRINT("\t"); @@ -1418,6 +1385,15 @@ class AudioReactive : public Usermod { // update global vars ONCE - multAgc, sampleAGC, rawSampleAgc multAgc = multAgcTemp; + if (micQuality > 0) { + if (micQuality > 1) { + rawSampleAgc = 0.95f * tmpAgc + 0.05f * (float)rawSampleAgc; // raw path + sampleAgc += 0.95f * (tmpAgc - sampleAgc); // smooth path + } else { + rawSampleAgc = 0.70f * tmpAgc + 0.30f * (float)rawSampleAgc; // min filtering path + sampleAgc += 0.70f * (tmpAgc - sampleAgc); + } + } else { #if defined(WLEDMM_FASTPATH) rawSampleAgc = 0.65f * tmpAgc + 0.35f * (float)rawSampleAgc; #else @@ -1428,7 +1404,7 @@ class AudioReactive : public Usermod { sampleAgc = 0.5f * tmpAgc + 0.5f * sampleAgc; // fast path to zero else sampleAgc += agcSampleSmooth[AGC_preset] * (tmpAgc - sampleAgc); // smooth path - + } sampleAgc = fabsf(sampleAgc); // // make sure we have a positive value last_soundAgc = soundAgc; } // agcAvg() @@ -1446,27 +1422,6 @@ class AudioReactive : public Usermod { static unsigned long lastSoundTime = 0; // for delaying un-freeze static unsigned long startuptime = 0; // "fast freeze" mode: do not interfere during first 12 seconds (filter startup time) - #ifdef WLED_DISABLE_SOUND - micIn = inoise8(millis(), millis()); // Simulated analog read - micDataReal = micIn; - #else - #ifdef ARDUINO_ARCH_ESP32 - micIn = int(micDataReal); // micDataSm = ((micData * 3) + micData)/4; - #else - // this is the minimal code for reading analog mic input on 8266. - // warning!! Absolutely experimental code. Audio on 8266 is still not working. Expects a million follow-on problems. - static unsigned long lastAnalogTime = 0; - static float lastAnalogValue = 0.0f; - if (millis() - lastAnalogTime > 20) { - micDataReal = analogRead(A0); // read one sample with 10bit resolution. This is a dirty hack, supporting volumereactive effects only. - lastAnalogTime = millis(); - lastAnalogValue = micDataReal; - yield(); - } else micDataReal = lastAnalogValue; - micIn = int(micDataReal); - #endif - #endif - if (startuptime == 0) startuptime = millis(); // fast freeze mode - remember filter startup time if ((micLevelMethod < 1) || !isFrozen) { // following the input level, UNLESS mic Level was frozen micLev += (micDataReal-micLev) / 12288.0f; @@ -1477,7 +1432,6 @@ class AudioReactive : public Usermod { if (!haveSilence) isFrozen = true; // freeze mode: freeze micLevel so it cannot rise again } - micIn -= micLev; // Let's center it to 0 now // Using an exponential filter to smooth out the signal. We'll add controls for this in a future release. float micInNoDC = fabsf(micDataReal - micLev); @@ -1511,10 +1465,15 @@ class AudioReactive : public Usermod { if ((micLevelMethod == 2) && (millis() - startuptime < 12000)) isFrozen = false; // fast freeze: no freeze in first 12 seconds (filter startup phase) tmpSample = expAdjF; - micIn = abs(micIn); // And get the absolute value of each sample - sampleAdj = tmpSample * sampleGain / 40.0f * inputLevel/128.0f + tmpSample / 16.0f; // Adjust the gain. with inputLevel adjustment - sampleReal = tmpSample; + // Adjust the gain. with inputLevel adjustment. + if (micQuality > 0) { + sampleAdj = micInNoDC * sampleGain / 40.0f * inputLevel/128.0f + micInNoDC / 16.0f; // ... using unfiltered sample + sampleReal = micInNoDC; + } else { + sampleAdj = tmpSample * sampleGain / 40.0f * inputLevel/128.0f + tmpSample / 16.0f; // ... using pre-filtered sample + sampleReal = tmpSample; + } sampleAdj = fmax(fmin(sampleAdj, 255.0f), 0.0f); // Question: why are we limiting the value to 8 bits ??? sampleRaw = (int16_t)sampleAdj; // ONLY update sample ONCE!!!! @@ -1538,11 +1497,16 @@ class AudioReactive : public Usermod { } if (sampleMax < 0.5f) sampleMax = 0.0f; + if (micQuality > 0) { + if (micQuality > 1) sampleAvg += 0.95f * (sampleAdj - sampleAvg); + else sampleAvg += 0.70f * (sampleAdj - sampleAvg); + } else { #if defined(WLEDMM_FASTPATH) sampleAvg = ((sampleAvg * 11.0f) + sampleAdj) / 12.0f; // make reactions a bit more "crisp" in fastpath mode #else sampleAvg = ((sampleAvg * 15.0f) + sampleAdj) / 16.0f; // Smooth it out over the last 16 samples. #endif + } sampleAvg = fabsf(sampleAvg); // make sure we have a positive value } // getSample() @@ -2784,6 +2748,7 @@ class AudioReactive : public Usermod { //WLEDMM: experimental settings JsonObject poweruser = top.createNestedObject("experiments"); poweruser[F("micLev")] = micLevelMethod; + poweruser[F("Mic_Quality")] = micQuality; poweruser[F("freqDist")] = freqDist; //poweruser[F("freqRMS")] = averageByRMS; poweruser[F("FFT_Window")] = fftWindow; @@ -2859,6 +2824,7 @@ class AudioReactive : public Usermod { //WLEDMM: experimental settings configComplete &= getJsonValue(top["experiments"][F("micLev")], micLevelMethod); + configComplete &= getJsonValue(top["experiments"][F("Mic_Quality")], micQuality); configComplete &= getJsonValue(top["experiments"][F("freqDist")], freqDist); //configComplete &= getJsonValue(top["experiments"][F("freqRMS")], averageByRMS); configComplete &= getJsonValue(top["experiments"][F("FFT_Window")], fftWindow); @@ -2959,23 +2925,29 @@ class AudioReactive : public Usermod { oappend(SET_F("addOption(dd,'Lazy',3);")); //WLEDMM: experimental settings - oappend(SET_F("dd=addDropdown(ux,'experiments:micLev');")); + oappend(SET_F("xx='experiments';")); // shortcut + oappend(SET_F("dd=addDropdown(ux,xx+':micLev');")); oappend(SET_F("addOption(dd,'Floating (⎌)',0);")); oappend(SET_F("addOption(dd,'Freeze',1);")); oappend(SET_F("addOption(dd,'Fast Freeze',2);")); - oappend(SET_F("addInfo(ux+':experiments:micLev',1,'☾');")); + oappend(SET_F("addInfo(ux+':'+xx+':micLev',1,'☾');")); - oappend(SET_F("dd=addDropdown(ux,'experiments:freqDist');")); + oappend(SET_F("dd=addDropdown(ux,xx+':Mic_Quality');")); + oappend(SET_F("addOption(dd,'average (standard)',0);")); + oappend(SET_F("addOption(dd,'low noise',1);")); + oappend(SET_F("addOption(dd,'perfect',2);")); + + oappend(SET_F("dd=addDropdown(ux,xx+':freqDist');")); oappend(SET_F("addOption(dd,'Normal (⎌)',0);")); oappend(SET_F("addOption(dd,'RightShift',1);")); - oappend(SET_F("addInfo(ux+':experiments:freqDist',1,'☾');")); + oappend(SET_F("addInfo(ux+':'+xx+':freqDist',1,'☾');")); - //oappend(SET_F("dd=addDropdown(ux,'experiments:freqRMS');")); + //oappend(SET_F("dd=addDropdown(ux,xx+':freqRMS');")); //oappend(SET_F("addOption(dd,'Off (⎌)',0);")); //oappend(SET_F("addOption(dd,'On',1);")); //oappend(SET_F("addInfo(ux+':experiments:freqRMS',1,'☾');")); - oappend(SET_F("dd=addDropdown(ux,'experiments:FFT_Window');")); + oappend(SET_F("dd=addDropdown(ux,xx+':FFT_Window');")); oappend(SET_F("addOption(dd,'Blackman-Harris (MM standard)',0);")); oappend(SET_F("addOption(dd,'Hann (balanced)',1);")); oappend(SET_F("addOption(dd,'Nuttall (more accurate)',2);")); @@ -2984,10 +2956,10 @@ class AudioReactive : public Usermod { oappend(SET_F("addOption(dd,'Flat-Top (AC WLED, inaccurate)',4);")); #ifdef FFT_USE_SLIDING_WINDOW - oappend(SET_F("dd=addDropdown(ux,'experiments:I2S_FastPath');")); + oappend(SET_F("dd=addDropdown(ux,xx+':I2S_FastPath');")); oappend(SET_F("addOption(dd,'Off',0);")); oappend(SET_F("addOption(dd,'On (⎌)',1);")); - oappend(SET_F("addInfo(ux+':experiments:I2S_FastPath',1,'☾');")); + oappend(SET_F("addInfo(ux+':'+xx+':I2S_FastPath',1,'☾');")); #endif oappend(SET_F("dd=addDropdown(ux,'dynamics:limiter');")); From 528ec1fa4cfb46025519183c387eafdc3ce3c262 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Fri, 5 Jul 2024 18:25:48 +0200 Subject: [PATCH 059/200] version of today, -b32.41 featuring FastPath v2 --- package-lock.json | 4 ++-- package.json | 2 +- wled00/wled.h | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 80dcf5ec..dce951b8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "wled", - "version": "0.14.1-b32.40", + "version": "0.14.1-b32.41", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "wled", - "version": "0.14.1-b32.40", + "version": "0.14.1-b32.41", "license": "GPL-3.0-or-later", "dependencies": { "clean-css": "^4.2.3", diff --git a/package.json b/package.json index 0536796c..2faab264 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wled", - "version": "0.14.1-b32.40", + "version": "0.14.1-b32.41", "description": "Tools for WLED project", "main": "tools/cdata.js", "directories": { diff --git a/wled00/wled.h b/wled00/wled.h index 8ff90dd5..f06f3a36 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -8,7 +8,7 @@ */ // version code in format yymmddb (b = daily build) -#define VERSION 2406240 +#define VERSION 2407050 // WLEDMM - you can check for this define in usermods, to only enabled WLEDMM specific code in the "right" fork. Its not defined in AC WLED. #define _MoonModules_WLED_ From 5123128e9c7bcc69a2397a0bd36ebb566548e481 Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Tue, 9 Jul 2024 18:39:58 +0100 Subject: [PATCH 060/200] Limit hub75 chain length by height 64 not width to allow for 2 x 64x32 --- wled00/bus_manager.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index fff5ecb3..cb00ad29 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -518,9 +518,7 @@ BusHub75Matrix::BusHub75Matrix(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWh break; } - mxconfig.chain_length = max((u_int8_t) 1, min(bc.pins[0], (u_int8_t) 4)); // prevent bad data preventing boot due to low memory - - if(mxconfig.mx_width >= 64 && (bc.pins[0] > 1)) { + if(mxconfig.mx_height >= 64 && (bc.pins[0] > 1)) { USER_PRINT("WARNING, only single panel can be used of 64 pixel boards due to memory") mxconfig.chain_length = 1; } @@ -609,6 +607,7 @@ BusHub75Matrix::BusHub75Matrix(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWh #endif + mxconfig.chain_length = max((u_int8_t) 1, min(bc.pins[0], (u_int8_t) 4)); // prevent bad data preventing boot due to low memory USER_PRINTF("MatrixPanel_I2S_DMA config - %ux%u length: %u\n", mxconfig.mx_width, mxconfig.mx_height, mxconfig.chain_length); From fb30f9c64142dad49cdda3baa73f959e52969929 Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Tue, 9 Jul 2024 21:35:53 +0100 Subject: [PATCH 061/200] Add Hub75Matrix 64x32 (Outdoor 8S) --- wled00/bus_manager.cpp | 8 +++++++- wled00/data/settings_leds.htm | 3 ++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index cb00ad29..2bd0a338 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -650,12 +650,18 @@ BusHub75Matrix::BusHub75Matrix(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWh switch(bc.type) { case 105: - USER_PRINTLN("MatrixPanel_I2S_DMA FOUR_SCAN_32PX_HIGH"); + USER_PRINTLN("MatrixPanel_I2S_DMA FOUR_SCAN_32PX_HIGH - 32x32"); fourScanPanel = new VirtualMatrixPanel((*display), 1, 1, 32, 32); fourScanPanel->setPhysicalPanelScanRate(FOUR_SCAN_32PX_HIGH); fourScanPanel->setRotation(0); break; case 106: + USER_PRINTLN("MatrixPanel_I2S_DMA FOUR_SCAN_32PX_HIGH - 64x32"); + fourScanPanel = new VirtualMatrixPanel((*display), 1, 1, 64, 32); + fourScanPanel->setPhysicalPanelScanRate(FOUR_SCAN_32PX_HIGH); + fourScanPanel->setRotation(0); + break; + case 107: USER_PRINTLN("MatrixPanel_I2S_DMA FOUR_SCAN_64PX_HIGH"); fourScanPanel = new VirtualMatrixPanel((*display), 1, 1, 64, 64); fourScanPanel->setPhysicalPanelScanRate(FOUR_SCAN_64PX_HIGH); diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index e1979fdc..5e31ee4c 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -384,7 +384,8 @@ ${i+1}: - + +
Color Order: RO
DMX TX: DI
DMX Enable: RE+DE
-
DMX Port:
+ DMX Port:

This firmware build does not include DMX Input support.
diff --git a/wled00/dmx_input.cpp b/wled00/dmx_input.cpp index 77ebc859..821481e5 100644 --- a/wled00/dmx_input.cpp +++ b/wled00/dmx_input.cpp @@ -106,6 +106,7 @@ bool DMXInput::installDriver() { const auto config = createConfig(); + USER_PRINTF("DMX port: %u\n", inputPortNum); if (!dmx_driver_install(inputPortNum, &config, DMX_INTR_FLAGS_DEFAULT)) { USER_PRINTF("Error: Failed to install dmx driver\n"); return false; @@ -134,7 +135,7 @@ void DMXInput::init(uint8_t rxPin, uint8_t txPin, uint8_t enPin, uint8_t inputPo // } #endif - if (inputPortNum < 3 && inputPortNum > 0) { + if (inputPortNum <= (SOC_UART_NUM - 1) && inputPortNum > 0) { this->inputPortNum = inputPortNum; } else { From dddd1574ec7a63694d81c923f2df419a87909adb Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 14 Jul 2024 14:11:11 +0200 Subject: [PATCH 083/200] (experimental) setPixelColorXY_fast speedup by setPixelColorXY_fast * adding a _fast_ variant of SetPixelColorXY, that does not perform any error checking. * drawLine uses the fast setPixelColor variant (bresenham algo only) * for TESTING: segment option "reverse" switches back to the "original slow" code. surely needs some more optimization and improvements. First test on GEQ 3D shows 10%-30% speedup --- wled00/FX.h | 4 +++ wled00/FX_2Dfcn.cpp | 85 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 88 insertions(+), 1 deletion(-) diff --git a/wled00/FX.h b/wled00/FX.h index d838cd0f..0d7be0bf 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -666,6 +666,9 @@ typedef struct Segment { if (height == 0) return (x%width); // softhack007 avoid div/0 return (x%width) + (y%height) * width; } + + //void setPixelColorXY_fast(int x, int y,uint32_t c); // set relative pixel within segment with color - wrapper for _fast + void setPixelColorXY_fast(int x, int y,uint32_t c, uint32_t scaled_col, int cols, int rows); // set relative pixel within segment with color - faster, but no error checking!!! void setPixelColorXY(int x, int y, uint32_t c); // set relative pixel within segment with color inline void setPixelColorXY(unsigned x, unsigned y, uint32_t c) { setPixelColorXY(int(x), int(y), c); } inline void setPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) { setPixelColorXY(x, y, RGBW32(r,g,b,w)); } @@ -981,6 +984,7 @@ class WS2812FX { // 96 bytes void setUpMatrix(), + setPixelColorXY_fast(int x, int y, uint32_t c), setPixelColorXY(int x, int y, uint32_t c); // outsmart the compiler :) by correctly overloading diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index 93810301..f3bd6ce1 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -176,6 +176,15 @@ void WS2812FX::setUpMatrix() { #endif } +// absolute matrix version of setPixelColor(), without error checking +void IRAM_ATTR WS2812FX::setPixelColorXY_fast(int x, int y, uint32_t col) //WLEDMM: IRAM_ATTR conditionally +{ + uint_fast16_t index = y * Segment::maxWidth + x; + if (index < customMappingSize) index = customMappingTable[index]; + if (index >= _length) return; + busses.setPixelColor(index, col); +} + // absolute matrix version of setPixelColor() void IRAM_ATTR_YN WS2812FX::setPixelColorXY(int x, int y, uint32_t col) //WLEDMM: IRAM_ATTR conditionally { @@ -211,6 +220,67 @@ uint32_t WS2812FX::getPixelColorXY(uint16_t x, uint16_t y) { // XY(x,y) - gets pixel index within current segment (often used to reference leds[] array element) // WLEDMM Segment::XY()is declared inline, see FX.h + +// Simplified version of Segment::setPixelColorXY - without error checking. Does not support grouping or spacing +// * expects scaled color (final brightness) as additional input parameter, plus segment virtualWidth() and virtualHeight() +void IRAM_ATTR Segment::setPixelColorXY_fast(int x, int y, uint32_t col, uint32_t scaled_col, int cols, int rows) //WLEDMM +{ + // if (Segment::maxHeight==1) return; // not a matrix set-up + // const int_fast16_t cols = virtualWidth(); // WLEDMM optimization + // const int_fast16_t rows = virtualHeight(); + // if (x<0 || y<0 || x >= cols || y >= rows) return; // if pixel would fall out of virtual segment just exit + unsigned i = UINT_MAX; + bool sameColor = false; + if (ledsrgb) { // WLEDMM small optimization + //i = XY(x,y); + //i = (x%cols) + (y%rows) * cols; // avoid error checking done in XY() + i = x + y*cols; // avoid error checking done by XY() - be optimistic about ranges of x and y + CRGB fastled_col = CRGB(col); + if (ledsrgb[i] == fastled_col) sameColor = true; + else ledsrgb[i] = fastled_col; + } +#if 0 + // we are NOT doing brightness here - must be done by the calling function! + //uint32_t scaled_col = col; + uint8_t _bri_t = currentBri(on ? opacity : 0); + if (!_bri_t && !transitional) return; + if (_bri_t < 255) scaled_col = color_fade(col, _bri_t); + else scaled_col = col; +#endif + +#if 0 // this is still a dangerous optimization + if ((i < UINT_MAX) && sameColor && (call > 0) && (!transitional) && (ledsrgb[i] == CRGB(col)) && (_globalLeds == nullptr)) return; // WLEDMM looks like nothing to do (but we don't trust globalleds) +#endif + + // handle reverse and transpose + if (reverse ) x = cols - x - 1; + if (reverse_y) y = rows - y - 1; + if (transpose) std::swap(x,y); // swap X & Y if segment transposed + + // set the requested pixel + strip.setPixelColorXY_fast(start + x, startY + y, scaled_col); + bool simpleSegment = !mirror && !mirror_y; + //if (simpleSegment) return; // WLEDMM shortcut when no mirroring needed + + // handle mirroring + const int_fast16_t wid_ = stop - start; + const int_fast16_t hei_ = stopY - startY; + //if (x >= wid_ || y >= hei_) return; // if pixel would fall out of segment just exit - should never happen, because width() >= virtualWidth() + if (mirror) { //set the corresponding horizontally mirrored pixel + if (transpose) strip.setPixelColorXY_fast(start + x, startY + hei_ - y - 1, scaled_col); + else strip.setPixelColorXY_fast(start + wid_ - x - 1, startY + y, scaled_col); + } + if (mirror_y) { //set the corresponding vertically mirrored pixel + if (transpose) strip.setPixelColorXY_fast(start + wid_ - x - 1, startY + y, scaled_col); + else strip.setPixelColorXY_fast(start + x, startY + hei_ - y - 1, scaled_col); + } + if (mirror_y && mirror) { //set the corresponding vertically AND horizontally mirrored pixel + strip.setPixelColorXY_fast(wid_ - x - 1, hei_ - y - 1, scaled_col); + } +} + + +// normal Segment::setPixelColorXY with error checking, and support for grouping / spacing void IRAM_ATTR_YN Segment::setPixelColorXY(int x, int y, uint32_t col) //WLEDMM: IRAM_ATTR conditionally { if (Segment::maxHeight==1) return; // not a matrix set-up @@ -608,6 +678,7 @@ void Segment::nscale8(uint8_t scale) { //WLEDMM: use fast types //line function void Segment::drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint32_t c, bool soft, uint16_t distance) { if (!isActive()) return; // not active + // if (Segment::maxHeight==1) return; // not a matrix set-up const int cols = virtualWidth(); const int rows = virtualHeight(); if (x0 >= cols || x1 >= cols || y0 >= rows || y1 >= rows) return; @@ -621,6 +692,16 @@ void Segment::drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint3 return; } + // WLEDMM shortcut when no grouping/spacing used + bool simpleSegment = !reverse && (grouping == 1) && (spacing == 0); // !reverse is just for back-to-back testing against "slow" functions + uint32_t scaled_col = c; + if (simpleSegment) { + // segment brightness must be pre-calculated for the "fast" setPixelColorXY variant! + uint8_t _bri_t = currentBri(on ? opacity : 0); + if (!_bri_t && !transitional) return; + if (_bri_t < 255) scaled_col = color_fade(c, _bri_t); + } + if (soft) { // Xiaolin Wu’s algorithm const bool steep = dy > dx; @@ -651,7 +732,9 @@ void Segment::drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint3 // Bresenham's algorithm int err = (dx>dy ? dx : -dy)/2; // error direction for (uint_fast16_t d=0; d= cols || y0 >= rows) break; // WLEDMM we hit the edge - should never happen + if (simpleSegment) setPixelColorXY_fast(x0, y0, c, scaled_col, cols, rows); + else setPixelColorXY(x0, y0, c); if (x0==x1 && y0==y1) break; int e2 = err; if (e2 >-dx) { err -= dy; x0 += sx; } From 6aaf7a908dd4703d04dc5b48f67cff11bf9b729c Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 14 Jul 2024 23:49:37 +0200 Subject: [PATCH 084/200] more sPC optimizations * made setPixelColorXY_fast private * optimized Segment::fill() to use setPixelColorXY_fast * "bar" 1D expand optimization --- wled00/FX.h | 2 +- wled00/FX_2Dfcn.cpp | 21 +++++++++++---------- wled00/FX_fcn.cpp | 25 ++++++++++++++++++++----- 3 files changed, 32 insertions(+), 16 deletions(-) diff --git a/wled00/FX.h b/wled00/FX.h index 0d7be0bf..fbc84737 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -432,6 +432,7 @@ typedef struct Segment { }; size_t _dataLen; // WLEDMM uint16_t is too small static size_t _usedSegmentData; // WLEDMM uint16_t is too small + void setPixelColorXY_fast(int x, int y,uint32_t c, uint32_t scaled_col, int cols, int rows); // set relative pixel within segment with color - faster, but no error checking!!! // perhaps this should be per segment, not static static CRGBPalette16 _currentPalette; // palette used for current effect (includes transition, used in color_from_palette()) @@ -668,7 +669,6 @@ typedef struct Segment { } //void setPixelColorXY_fast(int x, int y,uint32_t c); // set relative pixel within segment with color - wrapper for _fast - void setPixelColorXY_fast(int x, int y,uint32_t c, uint32_t scaled_col, int cols, int rows); // set relative pixel within segment with color - faster, but no error checking!!! void setPixelColorXY(int x, int y, uint32_t c); // set relative pixel within segment with color inline void setPixelColorXY(unsigned x, unsigned y, uint32_t c) { setPixelColorXY(int(x), int(y), c); } inline void setPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) { setPixelColorXY(x, y, RGBW32(r,g,b,w)); } diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index f3bd6ce1..95786b8d 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -260,7 +260,7 @@ void IRAM_ATTR Segment::setPixelColorXY_fast(int x, int y, uint32_t col, uint32_ // set the requested pixel strip.setPixelColorXY_fast(start + x, startY + y, scaled_col); bool simpleSegment = !mirror && !mirror_y; - //if (simpleSegment) return; // WLEDMM shortcut when no mirroring needed + if (simpleSegment) return; // WLEDMM shortcut when no mirroring needed // handle mirroring const int_fast16_t wid_ = stop - start; @@ -683,15 +683,6 @@ void Segment::drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint3 const int rows = virtualHeight(); if (x0 >= cols || x1 >= cols || y0 >= rows || y1 >= rows) return; - const int dx = abs(x1-x0), sx = x0 dx; diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 34e59e40..fae5199e 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -920,7 +920,8 @@ void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col) //WLEDMM: IRAM_ATT case M12_pBar: // expand 1D effect vertically or have it play on virtual strips if (vStrip>0) setPixelColorXY(vStrip - 1, vH - i - 1, col); - else for (int x = 0; x < vW; x++) setPixelColorXY(x, vH - i - 1, col); + //else for (int x = 0; x < vW; x++) setPixelColorXY(x, vH - i - 1, col); + else drawLine(0, vH - i - 1, vW-1, vH - i - 1, col, false); // WLEDMM draw line instead of plotting each pixel break; case M12_pArc: // expand in circular fashion from center @@ -1315,15 +1316,29 @@ void Segment::refreshLightCapabilities() { } /* - * Fills segment with color + * Fills segment with color - WLEDMM using faster sPC if possible */ void Segment::fill(uint32_t c) { if (!isActive()) return; // not active const uint_fast16_t cols = is2D() ? virtualWidth() : virtualLength(); // WLEDMM use fast int types const uint_fast16_t rows = virtualHeight(); // will be 1 for 1D - for(uint_fast16_t y = 0; y < rows; y++) for (uint_fast16_t x = 0; x < cols; x++) { - if (is2D()) setPixelColorXY((uint16_t)x, (uint16_t)y, c); - else setPixelColor((uint16_t)x, c); + + if (is2D()) { + // pre-calculate scaled color + uint32_t scaled_col = c; + bool simpleSegment = !reverse && (grouping == 1) && (spacing == 0); // !reverse is just for back-to-back testing against "slow" functions + if (simpleSegment) { + uint8_t _bri_t = currentBri(on ? opacity : 0); + if (!_bri_t && !transitional) return; + if (_bri_t < 255) scaled_col = color_fade(c, _bri_t); + } + // fill 2D segment + for(int y = 0; y < rows; y++) for (int x = 0; x < cols; x++) { + if (simpleSegment) setPixelColorXY_fast(x, y, c, scaled_col, cols, rows); + else setPixelColorXY(x, y, c); + } + } else { // fill 1D strip + for (int x = 0; x < cols; x++) setPixelColor(x, c); } } From 3857862e0384c2f4e71a3cbd177b4c644a6bacc3 Mon Sep 17 00:00:00 2001 From: Troy <5659019+troyhacks@users.noreply.github.com> Date: Sun, 14 Jul 2024 22:32:42 -0400 Subject: [PATCH 085/200] Optimized side-wall drawing Only draw what will be visible. This is a pretty solid speed increase when the adjacent bar is close in height, or if it's taller and would occlude it anyway, it'll be skipped. --- wled00/FX.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 72ec8310..be19f00e 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -8412,7 +8412,7 @@ uint16_t mode_GEQLASER(void) { ledColorTemp = color_fade(ledColor,32,true); - for (int y = 0; y <= heights[i]; y++) { + for (int y = heights[i+1]; y <= heights[i]; y++) { // don't bother drawing what we'll hide anyway SEGMENT.drawLine(linex+(cols/NUM_BANDS)-1,rows-y-1,*projector,horizon,ledColorTemp,false,distance); // right side perspective } @@ -8441,7 +8441,7 @@ uint16_t mode_GEQLASER(void) { ledColorTemp = color_fade(ledColor,32,true); - for (uint_fast8_t y = 0; y <= heights[i]; y++) { + for (uint_fast8_t y = heights[i-1]; y <= heights[i]; y++) { // don't bother drawing what we'll hide anyway SEGMENT.drawLine(linex,rows-y-1,*projector,horizon,ledColorTemp,false,distance); // left side perspective } From d2762c35b03bd7747cbfe43947589725fddb97df Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Mon, 15 Jul 2024 11:57:46 +0200 Subject: [PATCH 086/200] Update FX.cpp * add more accurate version of map() - reduces slight flickering in some 2D effects * cache fftResult[] in effects (um_data->fftresult might get updated while the effect still calculates) --- wled00/FX.cpp | 83 ++++++++++++++++++++++++++++++++++----------------- 1 file changed, 56 insertions(+), 27 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 01fd923c..f599c511 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -79,6 +79,24 @@ 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; } +// more accurate integer version of map() - based on map3() proposed in https://forum.arduino.cc/t/how-map-loses-precision-and-how-to-fix-it/371026/3 +// rounding instead of truncation, better handling of inverted ranges +static long map2(long x, long in_min, long in_max, long out_min, long out_max) +{ + long out_range = out_max - out_min; + if (out_range > 0) out_range ++; + else if (out_range < 0) out_range --; + else return out_min; // output range is 0 + + long in_range = in_max - in_min; + if (in_range > 0) in_range++; + else if (in_range < 0) in_range --; + else return out_min; // input range is 0 - Result is actually infinity but long has no such thing. The least negative long is another choice. + + return ((x - in_min) * out_range) / in_range + out_min; +} + + // effect functions /* @@ -1986,7 +2004,7 @@ uint16_t mode_partyjerk() { if (SEGENV.aux1 > 254) { SEGENV.aux1 = 0; } - if (SEGENV.aux0 > map(SEGMENT.custom1, 0, 255, 0, 14)) { + if (SEGENV.aux0 > map2(SEGMENT.custom1, 0, 255, 0, 14)) { SEGENV.aux0 = 0; SEGENV.aux1++; } @@ -1995,7 +2013,7 @@ uint16_t mode_partyjerk() { uint16_t counter = 0; if (volumeSmth * 2 > (255 - SEGMENT.intensity)) { - speed = SEGMENT.speed * map(SEGMENT.custom2, 0, 255, 0, 100); + speed = SEGMENT.speed * map2(SEGMENT.custom2, 0, 255, 0, 100); } else { speed = SEGMENT.speed; }; @@ -4768,7 +4786,7 @@ uint16_t mode_aurora(void) { if(SEGENV.aux0 != SEGMENT.intensity || SEGENV.call == 0) { //Intensity slider changed or first call - SEGENV.aux1 = map(SEGMENT.intensity, 0, 255, 2, W_MAX_COUNT); + SEGENV.aux1 = map2(SEGMENT.intensity, 0, 255, 2, W_MAX_COUNT); SEGENV.aux0 = SEGMENT.intensity; if(!SEGENV.allocateData(sizeof(AuroraWave) * SEGENV.aux1)) { // 26 on 32 segment ESP32, 9 on 16 segment ESP8266 @@ -5187,9 +5205,9 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https: bool allColors = SEGMENT.check1; bool overlayBG = SEGMENT.check2; bool wrap = SEGMENT.check3; - byte blur = map(SEGMENT.custom1, 0, 255, 255, 0); + byte blur = map2(SEGMENT.custom1, 0, 255, 255, 0); bool bgBlendMode = SEGMENT.custom1 > 220 && !overlayBG; // if blur is high and not overlaying, use bg blend mode - byte bgBlur = map(SEGMENT.custom1 - 220, 0, 35, 255, 128); + byte bgBlur = map2(SEGMENT.custom1 - 220, 0, 35, 255, 128); uint32_t bgColor = SEGCOLOR(1); uint32_t color = allColors ? random16() * random16() : SEGMENT.color_from_palette(0, false, PALETTE_SOLID_WRAP, 0); uint16_t cIndex; @@ -5748,7 +5766,7 @@ uint16_t mode_2DPolarLights(void) { // By: Kostyantyn Matviyevskyy https } float adjustHeight = mapf(rows, 8, maxRows, 28, minScale); // maybe use mapf() ??? // WLEDMM yes! - uint16_t adjScale = map(cols, 8, 64, 310, 63); + uint16_t adjScale = map2(cols, 8, 64, 310, 63); adjustHeight = max(min(adjustHeight, 28.0f), minScale); // WLEDMM bugfix for larger fixtures adjScale = max(min(adjScale, uint16_t(310)), uint16_t(63)); // WLEDMM @@ -5767,8 +5785,8 @@ uint16_t mode_2DPolarLights(void) { // By: Kostyantyn Matviyevskyy https } } */ - uint16_t _scale = map(SEGMENT.intensity, 0, 255, 30, adjScale); - byte _speed = map(SEGMENT.speed, 0, 255, 128, 16); + uint16_t _scale = map2(SEGMENT.intensity, 0, 255, 30, adjScale); + byte _speed = map2(SEGMENT.speed, 0, 255, 128, 16); //WLEDMM add SuperSync control uint16_t xStart, xEnd, yStart, yEnd; @@ -6439,6 +6457,10 @@ static const char _data_FX_MODE_2DDRIFTROSE[] PROGMEM = "Drift Rose@Fade,Blur;;; volumeSmth = *(float*) um_data->u_data[0]; volumeRaw = *(int16_t*) um_data->u_data[1]; fftResult = (uint8_t*) um_data->u_data[2]; +or + uint8_t fftResult[NUM_GEQ_CHANNELS] = {0}; + if (um_data->u_data != nullptr) memcpy(fftResult, um_data->u_data[2], sizeof(fftResult)); // WLEDMM to buffer curent values + samplePeak = *(uint8_t*) um_data->u_data[3]; FFT_MajorPeak = *(float*) um_data->u_data[4]; my_magnitude = *(float*) um_data->u_data[5]; @@ -6456,6 +6478,8 @@ static const char _data_FX_MODE_2DDRIFTROSE[] PROGMEM = "Drift Rose@Fade,Blur;;; // a few constants needed for AudioReactive effects +#define NUM_GEQ_CHANNELS 16 // number of audioreactive frequency channels. + // for 22Khz sampling #define MIN_FREQUENCY 80 // 80 HZ - due to lower resolution #define MIN_FREQ_LOG10 1.90309f // log10(MIN_FREQUENCY) @@ -7018,7 +7042,7 @@ uint16_t mode_noisemeter(void) { // Noisemeter. By Andrew Tuline. if (SEGENV.call == 0) {SEGENV.setUpLeds(); SEGMENT.fill(BLACK);} // WLEDMM use lossless getPixelColor() //uint8_t fadeRate = map(SEGMENT.speed,0,255,224,255); - uint8_t fadeRate = map(SEGMENT.speed,0,255,200,254); + uint8_t fadeRate = map2(SEGMENT.speed,0,255,200,254); SEGMENT.fade_out(fadeRate); float tmpSound2 = volumeRaw * 2.0 * (float)SEGMENT.intensity / 255.0; @@ -7128,7 +7152,7 @@ static const char _data_FX_MODE_PLASMOID[] PROGMEM = "Plasmoid@Phase,# of pixels uint16_t mode_puddlepeak(void) { // Puddlepeak. By Andrew Tuline. uint16_t size = 0; - uint8_t fadeVal = map(SEGMENT.speed,0,255, 224, 254); + uint8_t fadeVal = map2(SEGMENT.speed,0,255, 224, 254); uint16_t pos = random16(SEGLEN); // Set a random starting position. um_data_t *um_data; @@ -7174,7 +7198,7 @@ static const char _data_FX_MODE_PUDDLEPEAK[] PROGMEM = "Puddlepeak@Fade rate,Pud ////////////////////// uint16_t mode_puddles(void) { // Puddles. By Andrew Tuline. uint16_t size = 0; - uint8_t fadeVal = map(SEGMENT.speed, 0, 255, 224, 254); + uint8_t fadeVal = map2(SEGMENT.speed, 0, 255, 224, 254); uint16_t pos = random16(SEGLEN); // Set a random starting position. if (SEGENV.call == 0) { @@ -7380,7 +7404,7 @@ uint16_t mode_DJLight(void) { // Written by Stefan Petrick, Ad //if (color.getLuma() > 12) color.maximizeBrightness(); // for testing //SEGMENT.setPixelColor(mid, color.fadeToBlackBy(map(fftResult[4], 0, 255, 255, 4))); // 0.13.x fade -> 180hz-260hz - uint8_t fadeVal = map(fftResult[3], 0, 255, 255, 4); // 0.14.x fade -> 216hz-301hz + uint8_t fadeVal = map2(fftResult[3], 0, 255, 255, 4); // 0.14.x fade -> 216hz-301hz if (SEGENV.check1) fadeVal = constrain(fadeVal, 0, 176); // "candy factory" mode - avoid complete fade-out SEGMENT.setPixelColor(mid, color.fadeToBlackBy(fadeVal)); @@ -7684,7 +7708,8 @@ uint16_t mode_noisemove(void) { // Noisemove. By: Andrew Tuli // add support for no audio um_data = simulateSound(SEGMENT.soundSim); } - uint8_t *fftResult = (uint8_t*)um_data->u_data[2]; + uint8_t fftResult[NUM_GEQ_CHANNELS] = {0}; + if (um_data->u_data != nullptr) memcpy(fftResult, um_data->u_data[2], sizeof(fftResult)); // WLEDMM buffer curent values if (SEGENV.call == 0) { SEGENV.setUpLeds(); // WLEDMM use lossless getPixelColor() @@ -7694,11 +7719,11 @@ uint16_t mode_noisemove(void) { // Noisemove. By: Andrew Tuli int fadeoutDelay = (256 - SEGMENT.speed) / 96; if ((fadeoutDelay <= 1 ) || ((SEGENV.call % fadeoutDelay) == 0)) SEGMENT.fadeToBlackBy(4+ SEGMENT.speed/4); - uint8_t numBins = map(SEGMENT.intensity,0,255,0,16); // Map slider to fftResult bins. + uint8_t numBins = map2(SEGMENT.intensity,0,255,0,16); // Map slider to fftResult bins. for (int i=0; i 1)) ? map(band, 0, NUM_BANDS - 1, 0, 15):band; // always use full range. comment out this line to get the previous behaviour. + uint8_t frBand = ((NUM_BANDS < 16) && (NUM_BANDS > 1)) ? map2(band, 0, NUM_BANDS - 1, 0, 15):band; // always use full range. comment out this line to get the previous behaviour. // frBand = constrain(frBand, 0, 15); //WLEDMM can never be out of bounds (I think...) uint16_t colorIndex = frBand * 17; //WLEDMM 0.255 uint16_t bandHeight = fftResult[frBand]; // WLEDMM we use the original ffResult, to preserve accuracy @@ -7872,7 +7899,7 @@ uint16_t mode_2DGEQ(void) { // By Will Tatam. Code reduction by Ewoud Wijma. // get height of next (right side) bar uint8_t nextband = (remaining < 1)? band +1: band; nextband = constrain(nextband, 0, 15); // just to be sure - frBand = ((NUM_BANDS < 16) && (NUM_BANDS > 1)) ? map(nextband, 0, NUM_BANDS - 1, 0, 15):nextband; // always use full range. comment out this line to get the previous behaviour. + frBand = ((NUM_BANDS < 16) && (NUM_BANDS > 1)) ? map2(nextband, 0, NUM_BANDS - 1, 0, 15):nextband; // always use full range. comment out this line to get the previous behaviour. uint16_t nextBandHeight = fftResult[frBand]; // smooth Band height bandHeight = (7*bandHeight + 3*lastBandHeight + 3*nextBandHeight) / 12; // yeees, its 12 not 13 (10% amplification) @@ -7920,7 +7947,7 @@ uint16_t mode_2DFunkyPlank(void) { // Written by ??? Adapted by Wil const uint16_t cols = SEGMENT.virtualWidth(); const uint16_t rows = SEGMENT.virtualHeight(); - int NUMB_BANDS = map(SEGMENT.custom1, 0, 255, 1, 16); + int NUMB_BANDS = map2(SEGMENT.custom1, 0, 255, 1, 16); int barWidth = (cols / NUMB_BANDS); int bandInc = 1; if (barWidth == 0) { @@ -7934,7 +7961,8 @@ uint16_t mode_2DFunkyPlank(void) { // Written by ??? Adapted by Wil // add support for no audio um_data = simulateSound(SEGMENT.soundSim); } - uint8_t *fftResult = (uint8_t*)um_data->u_data[2]; + uint8_t fftResult[NUM_GEQ_CHANNELS] = {0}; + if (um_data->u_data != nullptr) memcpy(fftResult, um_data->u_data[2], sizeof(fftResult)); // WLEDMM buffer curent values if (SEGENV.call == 0) { SEGMENT.setUpLeds(); @@ -7949,7 +7977,7 @@ uint16_t mode_2DFunkyPlank(void) { // Written by ??? Adapted by Wil int b = 0; for (int band = 0; band < NUMB_BANDS; band += bandInc, b++) { int hue = fftResult[band % 16]; - int v = map(fftResult[band % 16], 0, 255, 10, 255); + int v = map2(fftResult[band % 16], 0, 255, 10, 255); for (int w = 0; w < barWidth; w++) { int xpos = (barWidth * b) + w; SEGMENT.setPixelColorXY(xpos, 0, CHSV(hue, 255, v)); @@ -8021,11 +8049,12 @@ uint16_t mode_2DAkemi(void) { const float lightFactor = 0.15f; const float normalFactor = 0.4f; - um_data_t *um_data; + um_data_t *um_data = nullptr; if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { um_data = simulateSound(SEGMENT.soundSim); } - uint8_t *fftResult = (uint8_t*)um_data->u_data[2]; + uint8_t fftResult[NUM_GEQ_CHANNELS] = {0}; + if (um_data->u_data != nullptr) memcpy(fftResult, um_data->u_data[2], sizeof(fftResult)); // WLEDMM buffer curent values float base = fftResult[0]/255.0f; //draw and color Akemi @@ -8047,7 +8076,7 @@ uint16_t mode_2DAkemi(void) { default: color = BLACK; break; } - if (SEGMENT.intensity > 128 && fftResult && fftResult[0] > 128) { //dance if base is high + if (SEGMENT.intensity > 128 && um_data && fftResult[0] > 128) { //dance if base is high SEGMENT.setPixelColorXY(x, 0, BLACK); SEGMENT.setPixelColorXY(x, y+1, color); } else @@ -8055,11 +8084,11 @@ uint16_t mode_2DAkemi(void) { } //add geq left and right - if (um_data && fftResult) { + if (um_data) { for (int x=0; x < cols/8; x++) { uint16_t band = x * cols/8; band = constrain(band, 0, 15); - uint16_t barHeight = map(fftResult[band], 0, 255, 0, 17*rows/32); + uint16_t barHeight = map2(fftResult[band], 0, 255, 0, 17*rows/32); CRGB color = SEGMENT.color_from_palette((band * 35), false, PALETTE_SOLID_WRAP, 0); for (int y=0; y < barHeight; y++) { From d0927a5142d73810f11c3f60ab9fb1a15a390e68 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Mon, 15 Jul 2024 12:29:29 +0200 Subject: [PATCH 087/200] akemi and GEQ didn't like map2 --- wled00/FX.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index f599c511..ba2f1c3c 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -7889,7 +7889,7 @@ uint16_t mode_2DGEQ(void) { // By Will Tatam. Code reduction by Ewoud Wijma. remaining--; //consume remaining // Serial.printf("x %d b %d n %d w %f %f\n", x, band, NUM_BANDS, bandwidth, remaining); - uint8_t frBand = ((NUM_BANDS < 16) && (NUM_BANDS > 1)) ? map2(band, 0, NUM_BANDS - 1, 0, 15):band; // always use full range. comment out this line to get the previous behaviour. + uint8_t frBand = ((NUM_BANDS < 16) && (NUM_BANDS > 1)) ? map(band, 0, NUM_BANDS - 1, 0, 15):band; // always use full range. comment out this line to get the previous behaviour. // frBand = constrain(frBand, 0, 15); //WLEDMM can never be out of bounds (I think...) uint16_t colorIndex = frBand * 17; //WLEDMM 0.255 uint16_t bandHeight = fftResult[frBand]; // WLEDMM we use the original ffResult, to preserve accuracy @@ -7899,7 +7899,7 @@ uint16_t mode_2DGEQ(void) { // By Will Tatam. Code reduction by Ewoud Wijma. // get height of next (right side) bar uint8_t nextband = (remaining < 1)? band +1: band; nextband = constrain(nextband, 0, 15); // just to be sure - frBand = ((NUM_BANDS < 16) && (NUM_BANDS > 1)) ? map2(nextband, 0, NUM_BANDS - 1, 0, 15):nextband; // always use full range. comment out this line to get the previous behaviour. + frBand = ((NUM_BANDS < 16) && (NUM_BANDS > 1)) ? map(nextband, 0, NUM_BANDS - 1, 0, 15):nextband; // always use full range. comment out this line to get the previous behaviour. uint16_t nextBandHeight = fftResult[frBand]; // smooth Band height bandHeight = (7*bandHeight + 3*lastBandHeight + 3*nextBandHeight) / 12; // yeees, its 12 not 13 (10% amplification) @@ -8088,7 +8088,7 @@ uint16_t mode_2DAkemi(void) { for (int x=0; x < cols/8; x++) { uint16_t band = x * cols/8; band = constrain(band, 0, 15); - uint16_t barHeight = map2(fftResult[band], 0, 255, 0, 17*rows/32); + uint16_t barHeight = map(fftResult[band], 0, 255, 0, 17*rows/32); CRGB color = SEGMENT.color_from_palette((band * 35), false, PALETTE_SOLID_WRAP, 0); for (int y=0; y < barHeight; y++) { From 4f6eb8b1618a8f1f929eeaab76d291c8dd4490d4 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Mon, 15 Jul 2024 13:24:18 +0200 Subject: [PATCH 088/200] prevent array bounds violation + don't use dynamic array for heights[] - dynamic arrays are using malloc() -> heap frag. --- wled00/FX.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 599dea0d..5b367e84 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -8420,7 +8420,7 @@ uint16_t mode_GEQLASER(void) { } uint8_t *fftResult = (uint8_t*)um_data->u_data[2]; - uint8_t heights[NUM_BANDS] = { 0 }; + uint8_t heights[NUM_GEQ_CHANNELS] = { 0 }; for (int i=0; i0) ? heights[i-1] : 0; y <= heights[i]; y++) { // don't bother drawing what we'll hide anyway SEGMENT.drawLine(linex,rows-y-1,*projector,horizon,ledColorTemp,false,distance); // left side perspective } From 5401328b6e69182cdbf879c4dd81d66e8bdc6d00 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Mon, 15 Jul 2024 14:05:19 +0200 Subject: [PATCH 089/200] loop optimizations * made cols/rows integer, to avoid that "cols-x" wraps around * pre-compute some expressions that don't depend on loop counters --> wins us 1-4 fps on my 72x72 test setup --- wled00/FX.cpp | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 5b367e84..8d523ffc 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -8408,8 +8408,8 @@ uint16_t mode_GEQLASER(void) { SEGMENT.fill(BLACK); const int NUM_BANDS = map(SEGMENT.custom3, 0, 31, 1, 16); // custom3 is 0..31 - const uint16_t cols = SEGMENT.virtualWidth(); - const uint16_t rows = SEGMENT.virtualHeight(); + const int cols = SEGMENT.virtualWidth(); + const int rows = SEGMENT.virtualHeight(); uint32_t ledColorTemp; uint_fast8_t split = map(*projector,0,SEGMENT.virtualWidth(),0,(NUM_BANDS - 1)); @@ -8421,9 +8421,8 @@ uint16_t mode_GEQLASER(void) { uint8_t *fftResult = (uint8_t*)um_data->u_data[2]; uint8_t heights[NUM_GEQ_CHANNELS] = { 0 }; - for (int i=0; i 1) { ledColorTemp = color_fade(ledColor,32,true); + int pPos = linex+(cols/NUM_BANDS)-1; for (int y = (i= linex+(cols/NUM_BANDS)-1)) { // draw if above horizon AND not directly under projector (special case later) + if (heights[i] < rows-horizon && (*projector <=linex || *projector >= pPos)) { // draw if above horizon AND not directly under projector (special case later) - for (uint_fast8_t x=linex; x<=linex+(cols/NUM_BANDS)-1;x++) { + for (uint_fast8_t x=linex; x<=pPos;x++) { SEGMENT.drawLine(x,rows-heights[i]-2,*projector,horizon,ledColorTemp,false,distance); // top perspective } @@ -8465,6 +8465,7 @@ uint16_t mode_GEQLASER(void) { uint32_t ledColor = SEGMENT.color_from_palette(colorIndex, false, PALETTE_SOLID_WRAP, 0); int linex = i*(cols/NUM_BANDS); + int pPos = linex+(cols/NUM_BANDS)-1; if (heights[i] > 1) { @@ -8476,9 +8477,9 @@ uint16_t mode_GEQLASER(void) { ledColorTemp = color_fade(ledColor,128,true); - if (heights[i] < rows-horizon && (*projector <=linex || *projector >= linex+(cols/NUM_BANDS)-1)) { // draw if above horizon AND not directly under projector (special case later) + if (heights[i] < rows-horizon && (*projector <=linex || *projector >= pPos)) { // draw if above horizon AND not directly under projector (special case later) - for (uint_fast8_t x=linex; x<=linex+(cols/NUM_BANDS)-1;x++) { + for (uint_fast8_t x=linex; x<=pPos;x++) { SEGMENT.drawLine(x,rows-heights[i]-2,*projector,horizon,ledColorTemp,false,distance); // top perspective } @@ -8494,14 +8495,16 @@ uint16_t mode_GEQLASER(void) { uint32_t ledColor = SEGMENT.color_from_palette(colorIndex, false, PALETTE_SOLID_WRAP, 0); int linex = i*(cols/NUM_BANDS); + int pPos = linex+(cols/NUM_BANDS)-1; + int pPos1 = linex+(cols/NUM_BANDS); - if (*projector >=linex && *projector <= linex+(cols/NUM_BANDS)-1) { // special case when top perspective is directly under the projector + if (*projector >=linex && *projector <= pPos) { // special case when top perspective is directly under the projector if (heights[i] > 1 && heights[i] < rows-horizon) { ledColorTemp = color_fade(ledColor,128,true); - for (uint_fast8_t x=linex; x<=linex+(cols/NUM_BANDS)-1;x++) { + for (uint_fast8_t x=linex; x<=pPos;x++) { SEGMENT.drawLine(x,rows-heights[i]-2,*projector,horizon,ledColorTemp,false,distance); // top perspective } @@ -8513,7 +8516,7 @@ uint16_t mode_GEQLASER(void) { ledColorTemp = color_fade(ledColor,SEGMENT.intensity,true); - for (uint_fast8_t x=linex; x