From 3ad56ea103d794082b3475fd22591a5a7787e63d Mon Sep 17 00:00:00 2001 From: Christian Schwinne Date: Sun, 10 Mar 2024 14:34:15 +0100 Subject: [PATCH 001/287] GIF testing --- wled00/fcn_declare.h | 3 ++- wled00/image_loader.cpp | 58 +++++++++++++++++++++-------------------- 2 files changed, 32 insertions(+), 29 deletions(-) diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 59be67624..72918d1a2 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -5,7 +5,6 @@ * All globally accessible functions are declared here */ -#include "FX.h" //alexa.cpp #ifndef WLED_DISABLE_ALEXA @@ -127,6 +126,8 @@ void onHueConnect(void* arg, AsyncClient* client); void sendHuePoll(); void onHueData(void* arg, AsyncClient* client, void *data, size_t len); +#include "FX.h" // must be below colors.cpp declarations (potentially due to duplicate declarations of e.g. color_blend) + //image_loader.cpp #ifndef WLED_DISABLE_GIF bool fileSeekCallback(unsigned long position); diff --git a/wled00/image_loader.cpp b/wled00/image_loader.cpp index 500fbc2dd..4a8052400 100644 --- a/wled00/image_loader.cpp +++ b/wled00/image_loader.cpp @@ -5,7 +5,7 @@ File file; char lastFilename[34] = "/"; -GifDecoder<320,320,12,true>* decoder; +GifDecoder<32,32,12> decoder; bool gifDecodeFailed = false; long lastFrameDisplayTime = 0, currentFrameDelay = 0; @@ -38,7 +38,7 @@ bool openGif(const char *filename) { Segment* activeSeg; uint16_t gifWidth, gifHeight; -uint16_t fillPixX, fillPixY; +//uint16_t fillPixX, fillPixY; void screenClearCallback(void) { activeSeg->fill(0); @@ -66,6 +66,7 @@ void drawPixelCallback(int16_t x, int16_t y, uint8_t red, uint8_t green, uint8_t #define IMAGE_ERROR_DECODER_ALLOC 5 #define IMAGE_ERROR_GIF_DECODE 6 #define IMAGE_ERROR_FRAME_DECODE 7 +#define IMAGE_ERROR_WAITING 254 #define IMAGE_ERROR_PREV 255 // renders an image (.gif only; .bmp and .fseq to be added soon) from FS to a segment @@ -84,47 +85,48 @@ byte renderImageToSegment(Segment &seg) { if (file) file.close(); openGif(lastFilename); if (!file) { gifDecodeFailed = true; return IMAGE_ERROR_FILE_MISSING; } - if (!decoder) decoder = new GifDecoder<320,320,12,true>(); - if (!decoder) { gifDecodeFailed = true; return IMAGE_ERROR_DECODER_ALLOC; } - decoder->setScreenClearCallback(screenClearCallback); - decoder->setUpdateScreenCallback(updateScreenCallback); - decoder->setDrawPixelCallback(drawPixelCallback); - decoder->setFileSeekCallback(fileSeekCallback); - decoder->setFilePositionCallback(filePositionCallback); - decoder->setFileReadCallback(fileReadCallback); - decoder->setFileReadBlockCallback(fileReadBlockCallback); - decoder->setFileSizeCallback(fileSizeCallback); + //if (!decoder) decoder = new GifDecoder<32,32,12>(); + //if (!decoder) { gifDecodeFailed = true; return IMAGE_ERROR_DECODER_ALLOC; } + decoder.setScreenClearCallback(screenClearCallback); + decoder.setUpdateScreenCallback(updateScreenCallback); + decoder.setDrawPixelCallback(drawPixelCallback); + decoder.setFileSeekCallback(fileSeekCallback); + decoder.setFilePositionCallback(filePositionCallback); + decoder.setFileReadCallback(fileReadCallback); + decoder.setFileReadBlockCallback(fileReadBlockCallback); + decoder.setFileSizeCallback(fileSizeCallback); Serial.println("Starting decoding"); - if(decoder->startDecoding() < 0) { gifDecodeFailed = true; return IMAGE_ERROR_GIF_DECODE; } + if(decoder.startDecoding() < 0) { gifDecodeFailed = true; return IMAGE_ERROR_GIF_DECODE; } Serial.println("Decoding started"); } if (gifDecodeFailed) return IMAGE_ERROR_PREV; if (!file) { gifDecodeFailed = true; return IMAGE_ERROR_FILE_MISSING; } - if (!decoder) { gifDecodeFailed = true; return IMAGE_ERROR_DECODER_ALLOC; } + //if (!decoder) { gifDecodeFailed = true; return IMAGE_ERROR_DECODER_ALLOC; } // speed 0 = half speed, 128 = normal, 255 = full FX FPS // TODO: 0 = 4x slow, 64 = 2x slow, 128 = normal, 192 = 2x fast, 255 = 4x fast uint32_t wait = currentFrameDelay * 2 - seg.speed * currentFrameDelay / 128; - if((millis() - lastFrameDisplayTime) >= wait) { - decoder->getSize(&gifWidth, &gifHeight); - fillPixX = (seg.width()+(gifWidth-1)) / gifWidth; - fillPixY = (seg.height()+(gifHeight-1)) / gifHeight; - int result = decoder->decodeFrame(false); - if (result < 0) { gifDecodeFailed = true; return IMAGE_ERROR_FRAME_DECODE; } - long lastFrameDelay = currentFrameDelay; - currentFrameDelay = decoder->getFrameDelay_ms(); - long tooSlowBy = (millis() - lastFrameDisplayTime) - wait; // if last frame was longer than intended, compensate - currentFrameDelay -= tooSlowBy; - lastFrameDisplayTime = millis(); - } - return true; + if((millis() - lastFrameDisplayTime) < wait) return IMAGE_ERROR_WAITING; + + decoder.getSize(&gifWidth, &gifHeight); + //fillPixX = (seg.width()+(gifWidth-1)) / gifWidth; + //fillPixY = (seg.height()+(gifHeight-1)) / gifHeight; + int result = decoder.decodeFrame(false); + if (result < 0) { gifDecodeFailed = true; return IMAGE_ERROR_FRAME_DECODE; } + //long lastFrameDelay = currentFrameDelay; + currentFrameDelay = decoder.getFrameDelay_ms(); + //long tooSlowBy = (millis() - lastFrameDisplayTime) - wait; // if last frame was longer than intended, compensate + //currentFrameDelay -= tooSlowBy; // TODO this is broken + lastFrameDisplayTime = millis(); + + return IMAGE_ERROR_NONE; } void endImagePlayback() { if (file) file.close(); - delete decoder; + //delete decoder; gifDecodeFailed = false; activeSeg = nullptr; lastFilename[0] = '\0'; From b2afac891415a65c5267755a351844903aba8e9d Mon Sep 17 00:00:00 2001 From: Christian Schwinne Date: Sun, 10 Mar 2024 21:36:13 +0100 Subject: [PATCH 002/287] GIFs work again in principle --- platformio.ini | 2 +- wled00/FX.cpp | 11 +++++++---- wled00/FX_fcn.cpp | 1 + wled00/fcn_declare.h | 1 + wled00/image_loader.cpp | 13 +++++++++---- 5 files changed, 19 insertions(+), 9 deletions(-) diff --git a/platformio.ini b/platformio.ini index 66bd09957..73d82c3a7 100644 --- a/platformio.ini +++ b/platformio.ini @@ -221,7 +221,7 @@ lib_deps = https://github.com/lorol/LITTLEFS.git https://github.com/pbolduc/AsyncTCP.git @ 1.2.0 bitbank2/AnimatedGIF@^1.4.7 - pixelmatix/GifDecoder@^1.1.0 + https://github.com/Aircoookie/GifDecoder#e76f58f ${env.lib_deps} # additional build flags for audioreactive AR_build_flags = -D USERMOD_AUDIOREACTIVE diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 681f3d107..e8c59babe 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -4470,7 +4470,12 @@ static const char _data_FX_MODE_WASHING_MACHINE[] PROGMEM = "Washing Machine@!,! Draws a .gif image from filesystem on the matrix/strip */ uint16_t mode_image(void) { - renderImageToSegment(SEGMENT); + //Serial.println(renderImageToSegment(SEGMENT)); + int status = renderImageToSegment(SEGMENT); + if (status != 0 && status != 254 && status != 255) { + Serial.print("GIF renderer return: "); + Serial.println(status); + } return FRAMETIME; } static const char _data_FX_MODE_IMAGE[] PROGMEM = "Image@!,;;;12;sx=128"; @@ -7973,7 +7978,7 @@ void WS2812FX::setupEffectData() { addEffect(FX_MODE_TWO_DOTS, &mode_two_dots, _data_FX_MODE_TWO_DOTS); addEffect(FX_MODE_FAIRYTWINKLE, &mode_fairytwinkle, _data_FX_MODE_FAIRYTWINKLE); addEffect(FX_MODE_RUNNING_DUAL, &mode_running_dual, _data_FX_MODE_RUNNING_DUAL); - + addEffect(FX_MODE_IMAGE, &mode_image, _data_FX_MODE_IMAGE); addEffect(FX_MODE_TRICOLOR_CHASE, &mode_tricolor_chase, _data_FX_MODE_TRICOLOR_CHASE); addEffect(FX_MODE_TRICOLOR_WIPE, &mode_tricolor_wipe, _data_FX_MODE_TRICOLOR_WIPE); addEffect(FX_MODE_TRICOLOR_FADE, &mode_tricolor_fade, _data_FX_MODE_TRICOLOR_FADE); @@ -8035,8 +8040,6 @@ void WS2812FX::setupEffectData() { addEffect(FX_MODE_DANCING_SHADOWS, &mode_dancing_shadows, _data_FX_MODE_DANCING_SHADOWS); addEffect(FX_MODE_WASHING_MACHINE, &mode_washing_machine, _data_FX_MODE_WASHING_MACHINE); - addEffect(FX_MODE_IMAGE, &mode_image, _data_FX_MODE_IMAGE); - addEffect(FX_MODE_BLENDS, &mode_blends, _data_FX_MODE_BLENDS); addEffect(FX_MODE_TV_SIMULATOR, &mode_tv_simulator, _data_FX_MODE_TV_SIMULATOR); addEffect(FX_MODE_DYNAMIC_SMOOTH, &mode_dynamic_smooth, _data_FX_MODE_DYNAMIC_SMOOTH); diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 42e98452f..94fdb3006 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -199,6 +199,7 @@ void Segment::resetIfRequired() { if (data && _dataLen > 0) memset(data, 0, _dataLen); // prevent heap fragmentation (just erase buffer instead of deallocateData()) next_time = 0; step = 0; call = 0; aux0 = 0; aux1 = 0; reset = false; + endImagePlayback(this); } CRGBPalette16 IRAM_ATTR &Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) { diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 72918d1a2..4619e640a 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -136,6 +136,7 @@ int fileReadCallback(void); int fileReadBlockCallback(void * buffer, int numberOfBytes); int fileSizeCallback(void); byte renderImageToSegment(Segment &seg); +void endImagePlayback(Segment* seg); #endif //improv.cpp diff --git a/wled00/image_loader.cpp b/wled00/image_loader.cpp index 4a8052400..44fb7f7c8 100644 --- a/wled00/image_loader.cpp +++ b/wled00/image_loader.cpp @@ -5,7 +5,7 @@ File file; char lastFilename[34] = "/"; -GifDecoder<32,32,12> decoder; +GifDecoder<32,32,12,true> decoder; bool gifDecodeFailed = false; long lastFrameDisplayTime = 0, currentFrameDelay = 0; @@ -85,7 +85,7 @@ byte renderImageToSegment(Segment &seg) { if (file) file.close(); openGif(lastFilename); if (!file) { gifDecodeFailed = true; return IMAGE_ERROR_FILE_MISSING; } - //if (!decoder) decoder = new GifDecoder<32,32,12>(); + //if (!decoder) decoder = new GifDecoder<32,32,12,true>(); //if (!decoder) { gifDecodeFailed = true; return IMAGE_ERROR_DECODER_ALLOC; } decoder.setScreenClearCallback(screenClearCallback); decoder.setUpdateScreenCallback(updateScreenCallback); @@ -95,6 +95,7 @@ byte renderImageToSegment(Segment &seg) { decoder.setFileReadCallback(fileReadCallback); decoder.setFileReadBlockCallback(fileReadBlockCallback); decoder.setFileSizeCallback(fileSizeCallback); + decoder.alloc(); // TODO only if not already allocated Serial.println("Starting decoding"); if(decoder.startDecoding() < 0) { gifDecodeFailed = true; return IMAGE_ERROR_GIF_DECODE; } Serial.println("Decoding started"); @@ -124,12 +125,16 @@ byte renderImageToSegment(Segment &seg) { return IMAGE_ERROR_NONE; } -void endImagePlayback() { +void endImagePlayback(Segment *seg) { + Serial.println("Image playback end called"); + if (!activeSeg || activeSeg != seg) return; if (file) file.close(); //delete decoder; + decoder.dealloc(); gifDecodeFailed = false; activeSeg = nullptr; - lastFilename[0] = '\0'; + lastFilename[1] = '\0'; + Serial.println("Image playback ended"); } #endif \ No newline at end of file From 3e60d3d96e9ebbea6232fca0b0e10fe714f9b008 Mon Sep 17 00:00:00 2001 From: Christian Schwinne Date: Sun, 17 Mar 2024 22:24:55 +0100 Subject: [PATCH 003/287] Working GIF support --- wled00/FX.cpp | 11 +++++------ wled00/image_loader.cpp | 28 +++++++++++++++------------- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index e8c59babe..48866224e 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -4470,12 +4470,11 @@ static const char _data_FX_MODE_WASHING_MACHINE[] PROGMEM = "Washing Machine@!,! Draws a .gif image from filesystem on the matrix/strip */ uint16_t mode_image(void) { - //Serial.println(renderImageToSegment(SEGMENT)); - int status = renderImageToSegment(SEGMENT); - if (status != 0 && status != 254 && status != 255) { - Serial.print("GIF renderer return: "); - Serial.println(status); - } + renderImageToSegment(SEGMENT); + // if (status != 0 && status != 254 && status != 255) { + // Serial.print("GIF renderer return: "); + // Serial.println(status); + // } return FRAMETIME; } static const char _data_FX_MODE_IMAGE[] PROGMEM = "Image@!,;;;12;sx=128"; diff --git a/wled00/image_loader.cpp b/wled00/image_loader.cpp index 44fb7f7c8..9ad4662e6 100644 --- a/wled00/image_loader.cpp +++ b/wled00/image_loader.cpp @@ -3,11 +3,15 @@ #include "GifDecoder.h" #include "wled.h" +/* + * Functions to render images from filesystem to segments, used by the "Image" effect + */ + File file; char lastFilename[34] = "/"; -GifDecoder<32,32,12,true> decoder; +GifDecoder<320,320,12,true> decoder; bool gifDecodeFailed = false; -long lastFrameDisplayTime = 0, currentFrameDelay = 0; +unsigned long lastFrameDisplayTime = 0, currentFrameDelay = 0; bool fileSeekCallback(unsigned long position) { return file.seek(position); @@ -38,7 +42,6 @@ bool openGif(const char *filename) { Segment* activeSeg; uint16_t gifWidth, gifHeight; -//uint16_t fillPixX, fillPixY; void screenClearCallback(void) { activeSeg->fill(0); @@ -72,6 +75,8 @@ void drawPixelCallback(int16_t x, int16_t y, uint8_t red, uint8_t green, uint8_t // renders an image (.gif only; .bmp and .fseq to be added soon) from FS to a segment byte renderImageToSegment(Segment &seg) { if (!seg.name) return IMAGE_ERROR_NO_NAME; + // disable during effect transition, causes flickering, multiple allocations and depending on image, part of old FX remaining + if (seg.mode != seg.currentMode()) return IMAGE_ERROR_WAITING; if (activeSeg && activeSeg != &seg) return IMAGE_ERROR_SEG_LIMIT; // only one segment at a time activeSeg = &seg; @@ -85,8 +90,6 @@ byte renderImageToSegment(Segment &seg) { if (file) file.close(); openGif(lastFilename); if (!file) { gifDecodeFailed = true; return IMAGE_ERROR_FILE_MISSING; } - //if (!decoder) decoder = new GifDecoder<32,32,12,true>(); - //if (!decoder) { gifDecodeFailed = true; return IMAGE_ERROR_DECODER_ALLOC; } decoder.setScreenClearCallback(screenClearCallback); decoder.setUpdateScreenCallback(updateScreenCallback); decoder.setDrawPixelCallback(drawPixelCallback); @@ -95,7 +98,7 @@ byte renderImageToSegment(Segment &seg) { decoder.setFileReadCallback(fileReadCallback); decoder.setFileReadBlockCallback(fileReadBlockCallback); decoder.setFileSizeCallback(fileSizeCallback); - decoder.alloc(); // TODO only if not already allocated + decoder.alloc(); Serial.println("Starting decoding"); if(decoder.startDecoding() < 0) { gifDecodeFailed = true; return IMAGE_ERROR_GIF_DECODE; } Serial.println("Decoding started"); @@ -109,17 +112,17 @@ byte renderImageToSegment(Segment &seg) { // TODO: 0 = 4x slow, 64 = 2x slow, 128 = normal, 192 = 2x fast, 255 = 4x fast uint32_t wait = currentFrameDelay * 2 - seg.speed * currentFrameDelay / 128; - if((millis() - lastFrameDisplayTime) < wait) return IMAGE_ERROR_WAITING; + // TODO consider handling this on FX level with a different frametime, but that would cause slow gifs to speed up during transitions + if (millis() - lastFrameDisplayTime < wait) return IMAGE_ERROR_WAITING; decoder.getSize(&gifWidth, &gifHeight); - //fillPixX = (seg.width()+(gifWidth-1)) / gifWidth; - //fillPixY = (seg.height()+(gifHeight-1)) / gifHeight; + int result = decoder.decodeFrame(false); if (result < 0) { gifDecodeFailed = true; return IMAGE_ERROR_FRAME_DECODE; } - //long lastFrameDelay = currentFrameDelay; + currentFrameDelay = decoder.getFrameDelay_ms(); - //long tooSlowBy = (millis() - lastFrameDisplayTime) - wait; // if last frame was longer than intended, compensate - //currentFrameDelay -= tooSlowBy; // TODO this is broken + unsigned long tooSlowBy = (millis() - lastFrameDisplayTime) - wait; // if last frame was longer than intended, compensate + currentFrameDelay = tooSlowBy > currentFrameDelay ? 0 : currentFrameDelay - tooSlowBy; lastFrameDisplayTime = millis(); return IMAGE_ERROR_NONE; @@ -129,7 +132,6 @@ void endImagePlayback(Segment *seg) { Serial.println("Image playback end called"); if (!activeSeg || activeSeg != seg) return; if (file) file.close(); - //delete decoder; decoder.dealloc(); gifDecodeFailed = false; activeSeg = nullptr; From 247de600afd3d06918c7c9873e63a756e970ad2b Mon Sep 17 00:00:00 2001 From: Christian Schwinne Date: Sun, 17 Mar 2024 22:57:15 +0100 Subject: [PATCH 004/287] Fix missing GIF enable macros --- platformio.ini | 2 +- wled00/FX.cpp | 6 +++++- wled00/FX_fcn.cpp | 2 ++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/platformio.ini b/platformio.ini index 73d82c3a7..c08c632a4 100644 --- a/platformio.ini +++ b/platformio.ini @@ -221,7 +221,7 @@ lib_deps = https://github.com/lorol/LITTLEFS.git https://github.com/pbolduc/AsyncTCP.git @ 1.2.0 bitbank2/AnimatedGIF@^1.4.7 - https://github.com/Aircoookie/GifDecoder#e76f58f + https://github.com/Aircoookie/GifDecoder#bc3af18 ${env.lib_deps} # additional build flags for audioreactive AR_build_flags = -D USERMOD_AUDIOREACTIVE diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 48866224e..5543b54ba 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -4470,12 +4470,16 @@ static const char _data_FX_MODE_WASHING_MACHINE[] PROGMEM = "Washing Machine@!,! Draws a .gif image from filesystem on the matrix/strip */ uint16_t mode_image(void) { + #ifdef WLED_DISABLE_GIF + return mode_static(); + #else renderImageToSegment(SEGMENT); + return FRAMETIME; + #endif // if (status != 0 && status != 254 && status != 255) { // Serial.print("GIF renderer return: "); // Serial.println(status); // } - return FRAMETIME; } static const char _data_FX_MODE_IMAGE[] PROGMEM = "Image@!,;;;12;sx=128"; diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 94fdb3006..7a2c00574 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -199,7 +199,9 @@ void Segment::resetIfRequired() { if (data && _dataLen > 0) memset(data, 0, _dataLen); // prevent heap fragmentation (just erase buffer instead of deallocateData()) next_time = 0; step = 0; call = 0; aux0 = 0; aux1 = 0; reset = false; + #ifndef WLED_DISABLE_GIF endImagePlayback(this); + #endif } CRGBPalette16 IRAM_ATTR &Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) { From 0c8d9d5614a2e4f0013604cfcac8552cf1646279 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Wed, 3 Apr 2024 18:38:06 +0200 Subject: [PATCH 005/287] Mode blending styles - alternative to #3669 --- wled00/FX.h | 28 ++++++++++ wled00/FX_2Dfcn.cpp | 33 +++++++++++- wled00/FX_fcn.cpp | 123 +++++++++++++++++++++++++++++++++++++----- wled00/data/index.htm | 20 ++++++- wled00/data/index.js | 4 ++ wled00/fcn_declare.h | 1 + wled00/json.cpp | 8 +++ wled00/util.cpp | 7 +++ wled00/wled.h | 3 +- 9 files changed, 212 insertions(+), 15 deletions(-) diff --git a/wled00/FX.h b/wled00/FX.h index 1089a0b8b..44c5e1549 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -316,6 +316,25 @@ #define MODE_COUNT 187 + +#define BLEND_STYLE_FADE 0 +#define BLEND_STYLE_FAIRY_DUST 1 +#define BLEND_STYLE_SWIPE_RIGHT 2 +#define BLEND_STYLE_SWIPE_LEFT 3 +#define BLEND_STYLE_PINCH_OUT 4 +#define BLEND_STYLE_INSIDE_OUT 5 +#define BLEND_STYLE_SWIPE_UP 6 +#define BLEND_STYLE_SWIPE_DOWN 7 +#define BLEND_STYLE_OPEN_H 8 +#define BLEND_STYLE_OPEN_V 9 +#define BLEND_STYLE_PUSH_TL 10 +#define BLEND_STYLE_PUSH_TR 11 +#define BLEND_STYLE_PUSH_BR 12 +#define BLEND_STYLE_PUSH_BL 13 + +#define BLEND_STYLE_COUNT 14 + + typedef enum mapping1D2D { M12_Pixels = 0, M12_pBar = 1, @@ -419,6 +438,9 @@ typedef struct Segment { static uint16_t _lastPaletteBlend; // blend palette according to set Transition Delay in millis()%0xFFFF #ifndef WLED_DISABLE_MODE_BLEND static bool _modeBlend; // mode/effect blending semaphore + // clipping + static uint16_t _clipStart, _clipStop; + static uint8_t _clipStartY, _clipStopY; #endif // transition data, valid only if transitional==true, holds values during transition (72 bytes) @@ -578,6 +600,10 @@ typedef struct Segment { void setPixelColor(float i, uint32_t c, bool aa = true); 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); } + #ifndef WLED_DISABLE_MODE_BLEND + inline void setClippingRect(int startX, int stopX, int startY = 0, int stopY = 1) { _clipStart = startX; _clipStop = stopX; _clipStartY = startY; _clipStopY = stopY; }; + #endif + bool isPixelClipped(int i); uint32_t getPixelColor(int i); // 1D support functions (some implement 2D as well) void blur(uint8_t); @@ -606,6 +632,7 @@ typedef struct Segment { void setPixelColorXY(float x, float y, uint32_t c, bool aa = 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); } inline void setPixelColorXY(float x, float y, CRGB c, bool aa = true) { setPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), aa); } + bool isPixelXYClipped(int x, int y); uint32_t getPixelColorXY(uint16_t x, uint16_t y); // 2D support functions inline void blendPixelColorXY(uint16_t x, uint16_t y, uint32_t color, uint8_t blend) { setPixelColorXY(x, y, color_blend(getPixelColorXY(x,y), color, blend)); } @@ -640,6 +667,7 @@ typedef struct Segment { 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); } inline void setPixelColorXY(float x, float y, CRGB c, bool aa = true) { setPixelColor(x, RGBW32(c.r,c.g,c.b,0), aa); } + inline bool isPixelXYClipped(int x, int y) { return isPixelClipped(x); } inline uint32_t getPixelColorXY(uint16_t x, uint16_t y) { return getPixelColor(x); } inline void blendPixelColorXY(uint16_t x, uint16_t y, uint32_t c, uint8_t blend) { blendPixelColor(x, c, blend); } inline void blendPixelColorXY(uint16_t x, uint16_t y, CRGB c, uint8_t blend) { blendPixelColor(x, RGBW32(c.r,c.g,c.b,0), blend); } diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index 7aecd2271..7e1419293 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -167,10 +167,41 @@ uint16_t IRAM_ATTR Segment::XY(uint16_t x, uint16_t y) return isActive() ? (x%width) + (y%height) * width : 0; } +// pixel is clipped if it falls outside clipping range (_modeBlend==true) or is inside clipping range (_modeBlend==false) +// if clipping start > stop the clipping range is inverted +// _modeBlend==true -> old effect during transition +// _modeBlend==false -> new effect during transition +bool IRAM_ATTR Segment::isPixelXYClipped(int x, int y) { +#ifndef WLED_DISABLE_MODE_BLEND + if (_clipStart != _clipStop && blendingStyle > BLEND_STYLE_FADE) { + const bool invertX = _clipStart > _clipStop; + const bool invertY = _clipStartY > _clipStopY; + const unsigned startX = invertX ? _clipStop : _clipStart; + const unsigned stopX = invertX ? _clipStart : _clipStop; + const unsigned startY = invertY ? _clipStopY : _clipStartY; + const unsigned stopY = invertY ? _clipStartY : _clipStopY; + if (blendingStyle == BLEND_STYLE_FAIRY_DUST) { + const unsigned width = stopX - startX; // assumes full segment width (faster than virtualWidth()) + const unsigned len = width * (stopY - startY); // assumes full segment height (faster than virtualHeight()) + if (len < 2) return false; + const unsigned shuffled = hashInt(x + y * width) % len; + const unsigned pos = (shuffled * 0xFFFFU) / len; + return progress() <= pos; + } + bool xInside = (x >= startX && x < stopX); if (invertX) xInside = !xInside; + bool yInside = (y >= startY && y < stopY); if (invertY) yInside = !yInside; + const bool clip = (invertX && invertY) ? !_modeBlend : _modeBlend; + if (xInside && yInside) return clip; // covers window & corners (inverted) + return !clip; + } +#endif + return false; +} + void IRAM_ATTR Segment::setPixelColorXY(int x, int y, uint32_t col) { if (!isActive()) return; // not active - if (x >= virtualWidth() || y >= virtualHeight() || x<0 || y<0) return; // if pixel would fall out of virtual segment just exit + if (x >= virtualWidth() || y >= virtualHeight() || x < 0 || y < 0 || isPixelXYClipped(x,y)) return; // if pixel would fall out of virtual segment just exit uint8_t _bri_t = currentBri(); if (_bri_t < 255) { diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index f97268f9b..989809d02 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -84,6 +84,10 @@ uint16_t Segment::_lastPaletteBlend = 0; //in millis (lowest 16 bits only) #ifndef WLED_DISABLE_MODE_BLEND bool Segment::_modeBlend = false; +uint16_t Segment::_clipStart = 0; +uint16_t Segment::_clipStop = 0; +uint8_t Segment::_clipStartY = 0; +uint8_t Segment::_clipStopY = 1; #endif // copy constructor @@ -413,12 +417,17 @@ void Segment::restoreSegenv(tmpsegd_t &tmpSeg) { uint8_t IRAM_ATTR Segment::currentBri(bool useCct) { uint32_t prog = progress(); + uint32_t curBri = useCct ? cct : (on ? opacity : 0); if (prog < 0xFFFFU) { - uint32_t curBri = (useCct ? cct : (on ? opacity : 0)) * prog; - curBri += (useCct ? _t->_cctT : _t->_briT) * (0xFFFFU - prog); + uint8_t tmpBri = useCct ? _t->_cctT : (_t->_segT._optionsT & 0x0004 ? _t->_briT : 0); +#ifndef WLED_DISABLE_MODE_BLEND + if (blendingStyle > BLEND_STYLE_FADE) return _modeBlend ? tmpBri : curBri; // not fade/blend transition, each effect uses its brightness +#endif + curBri *= prog; + curBri += tmpBri * (0xFFFFU - prog); return curBri / 0xFFFFU; } - return (useCct ? cct : (on ? opacity : 0)); + return curBri; } uint8_t IRAM_ATTR Segment::currentMode() { @@ -431,16 +440,23 @@ uint8_t IRAM_ATTR Segment::currentMode() { uint32_t IRAM_ATTR Segment::currentColor(uint8_t slot) { if (slot >= NUM_COLORS) slot = 0; + uint32_t prog = progress(); + if (prog == 0xFFFFU) return colors[slot]; #ifndef WLED_DISABLE_MODE_BLEND - return isInTransition() ? color_blend(_t->_segT._colorT[slot], colors[slot], progress(), true) : colors[slot]; + if (blendingStyle > BLEND_STYLE_FADE) return _modeBlend ? _t->_segT._colorT[slot] : colors[slot]; // not fade/blend transition, each effect uses its color + return color_blend(_t->_segT._colorT[slot], colors[slot], prog, true); #else - return isInTransition() ? color_blend(_t->_colorT[slot], colors[slot], progress(), true) : colors[slot]; + return color_blend(_t->_colorT[slot], colors[slot], prog, true); #endif } CRGBPalette16 IRAM_ATTR &Segment::currentPalette(CRGBPalette16 &targetPalette, uint8_t pal) { loadPalette(targetPalette, pal); uint16_t prog = progress(); +#ifndef WLED_DISABLE_MODE_BLEND + if (prog < 0xFFFFU && blendingStyle > BLEND_STYLE_FADE && _modeBlend) targetPalette = _t->_palT; // not fade/blend transition, each effect uses its palette + else +#endif if (strip.paletteFade && prog < 0xFFFFU) { // blend palettes // there are about 255 blend passes of 48 "blends" to completely blend two palettes (in _dur time) @@ -456,9 +472,9 @@ CRGBPalette16 IRAM_ATTR &Segment::currentPalette(CRGBPalette16 &targetPalette, u void Segment::handleRandomPalette() { // is it time to generate a new palette? if ((millis()/1000U) - _lastPaletteChange > randomPaletteChangeTime) { - _newRandomPalette = useHarmonicRandomPalette ? generateHarmonicRandomPalette(_randomPalette) : generateRandomPalette(); - _lastPaletteChange = millis()/1000U; - _lastPaletteBlend = (uint16_t)(millis() & 0xFFFF)-512; // starts blending immediately + _newRandomPalette = useHarmonicRandomPalette ? generateHarmonicRandomPalette(_randomPalette) : generateRandomPalette(); + _lastPaletteChange = millis()/1000U; + _lastPaletteBlend = (uint16_t)(millis() & 0xFFFF)-512; // starts blending immediately } // if palette transitions is enabled, blend it according to Transition Time (if longer than minimum given by service calls) @@ -662,6 +678,32 @@ uint16_t IRAM_ATTR Segment::virtualLength() const { return vLength; } +// pixel is clipped if it falls outside clipping range (_modeBlend==true) or is inside clipping range (_modeBlend==false) +// if clipping start > stop the clipping range is inverted +// _modeBlend==true -> old effect during transition +// _modeBlend==false -> new effect during transition +bool IRAM_ATTR Segment::isPixelClipped(int i) { +#ifndef WLED_DISABLE_MODE_BLEND + if (_clipStart != _clipStop && blendingStyle > BLEND_STYLE_FADE) { + bool invert = _clipStart > _clipStop; + unsigned start = invert ? _clipStop : _clipStart; + unsigned stop = invert ? _clipStart : _clipStop; + if (blendingStyle == BLEND_STYLE_FAIRY_DUST) { + unsigned len = stop - start; + if (len < 2) return false; + unsigned shuffled = hashInt(i) % len; + unsigned pos = (shuffled * 0xFFFFU) / len; + return progress() <= pos; + } + const bool iInside = (i >= start && i < stop); + if (!invert && iInside) return _modeBlend; + if ( invert && !iInside) return _modeBlend; + return !_modeBlend; + } +#endif + return false; +} + void IRAM_ATTR Segment::setPixelColor(int i, uint32_t col) { if (!isActive()) return; // not active @@ -732,6 +774,8 @@ void IRAM_ATTR Segment::setPixelColor(int i, uint32_t col) } #endif + if (isPixelClipped(i)) return; // handle clipping on 1D + uint16_t len = length(); uint8_t _bri_t = currentBri(); if (_bri_t < 255) { @@ -763,14 +807,16 @@ void IRAM_ATTR Segment::setPixelColor(int i, uint32_t col) indexMir += offset; // offset/phase if (indexMir >= stop) indexMir -= len; // wrap #ifndef WLED_DISABLE_MODE_BLEND - if (_modeBlend) tmpCol = color_blend(strip.getPixelColor(indexMir), col, 0xFFFFU - progress(), true); + // _modeBlend==true -> old effect + if (_modeBlend && blendingStyle == BLEND_STYLE_FADE) tmpCol = color_blend(strip.getPixelColor(indexMir), col, 0xFFFFU - progress(), true); #endif strip.setPixelColor(indexMir, tmpCol); } indexSet += offset; // offset/phase if (indexSet >= stop) indexSet -= len; // wrap #ifndef WLED_DISABLE_MODE_BLEND - if (_modeBlend) tmpCol = color_blend(strip.getPixelColor(indexSet), col, 0xFFFFU - progress(), true); + // _modeBlend==true -> old effect + if (_modeBlend && blendingStyle == BLEND_STYLE_FADE) tmpCol = color_blend(strip.getPixelColor(indexSet), col, 0xFFFFU - progress(), true); #endif strip.setPixelColor(indexSet, tmpCol); } @@ -1062,7 +1108,7 @@ uint32_t Segment::color_from_palette(uint16_t i, bool mapping, bool wrap, uint8_ // paletteBlend: 0 - wrap when moving, 1 - always wrap, 2 - never wrap, 3 - none (undefined) if (!wrap && strip.paletteBlend != 3) paletteIndex = scale8(paletteIndex, 240); //cut off blend at palette "end" CRGBPalette16 curPal; - curPal = currentPalette(curPal, palette); + currentPalette(curPal, palette); CRGB fastled_col = ColorFromPalette(curPal, paletteIndex, pbri, (strip.paletteBlend == 3)? NOBLEND:LINEARBLEND); // NOTE: paletteBlend should be global return RGBW32(fastled_col.r, fastled_col.g, fastled_col.b, W(color)); @@ -1185,8 +1231,59 @@ void WS2812FX::service() { // overwritten by later effect. To enable seamless blending for every effect, additional LED buffer // would need to be allocated for each effect and then blended together for each pixel. [[maybe_unused]] uint8_t tmpMode = seg.currentMode(); // this will return old mode while in transition - delay = (*_mode[seg.mode])(); // run new/current mode #ifndef WLED_DISABLE_MODE_BLEND + seg.setClippingRect(0, 0); // disable clipping + if (modeBlending && seg.mode != tmpMode) { + // set clipping rectangle + // new mode is run inside clipping area and old mode outside clipping area + unsigned p = seg.progress(); + unsigned w = seg.is2D() ? seg.virtualWidth() : _virtualSegmentLength; + unsigned h = seg.virtualHeight(); + unsigned dw = p * w / 0xFFFFU + 1; + unsigned dh = p * h / 0xFFFFU + 1; + switch (blendingStyle) { + case BLEND_STYLE_FAIRY_DUST: // fairy dust (must set entire segment, see isPixelXYClipped()) + seg.setClippingRect(0, w, 0, h); + break; + case BLEND_STYLE_SWIPE_RIGHT: // left-to-right + seg.setClippingRect(0, dw, 0, h); + break; + case BLEND_STYLE_SWIPE_LEFT: // right-to-left + seg.setClippingRect(w - dw, w, 0, h); + break; + case BLEND_STYLE_PINCH_OUT: // corners + seg.setClippingRect((w + dw)/2, (w - dw)/2, (h + dh)/2, (h - dh)/2); // inverted!! + break; + case BLEND_STYLE_INSIDE_OUT: // outward + seg.setClippingRect((w - dw)/2, (w + dw)/2, (h - dh)/2, (h + dh)/2); + break; + case BLEND_STYLE_SWIPE_DOWN: // top-to-bottom (2D) + seg.setClippingRect(0, w, 0, dh); + break; + case BLEND_STYLE_SWIPE_UP: // bottom-to-top (2D) + seg.setClippingRect(0, w, h - dh, h); + break; + case BLEND_STYLE_OPEN_H: // horizontal-outward (2D) same look as INSIDE_OUT on 1D + seg.setClippingRect((w - dw)/2, (w + dw)/2, 0, h); + break; + case BLEND_STYLE_OPEN_V: // vertical-outward (2D) + seg.setClippingRect(0, w, (h - dh)/2, (h + dh)/2); + break; + case BLEND_STYLE_PUSH_TL: // TL-to-BR (2D) + seg.setClippingRect(0, dw, 0, dh); + break; + case BLEND_STYLE_PUSH_TR: // TR-to-BL (2D) + seg.setClippingRect(w - dw, w, 0, dh); + break; + case BLEND_STYLE_PUSH_BR: // BR-to-TL (2D) + seg.setClippingRect(w - dw, w, h - dh, h); + break; + case BLEND_STYLE_PUSH_BL: // BL-to-TR (2D) + seg.setClippingRect(0, dw, h - dh, h); + break; + } + } + delay = (*_mode[seg.mode])(); // run new/current mode if (modeBlending && seg.mode != tmpMode) { Segment::tmpsegd_t _tmpSegData; Segment::modeBlend(true); // set semaphore @@ -1197,6 +1294,8 @@ void WS2812FX::service() { delay = MIN(delay,d2); // use shortest delay Segment::modeBlend(false); // unset semaphore } +#else + delay = (*_mode[seg.mode])(); // run effect mode #endif seg.call++; if (seg.isInTransition() && delay > FRAMETIME) delay = FRAMETIME; // force faster updates during transition diff --git a/wled00/data/index.htm b/wled00/data/index.htm index 4a532abb7..dc1431767 100644 --- a/wled00/data/index.htm +++ b/wled00/data/index.htm @@ -265,7 +265,25 @@
-

Transition:  s

+

Transition:  s

+

Blend: + +

diff --git a/wled00/data/index.js b/wled00/data/index.js index 4ad2044ad..1a50b6a3b 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -1426,6 +1426,9 @@ function readState(s,command=false) tr = s.transition; gId('tt').value = tr/10; + gId('bs').value = s.bs || 0; + if (tr===0) gId('bsp').classList.add('hide') + else gId('bsp').classList.remove('hide') populateSegments(s); var selc=0; @@ -1682,6 +1685,7 @@ function requestJson(command=null) var tn = parseInt(t.value*10); if (tn != tr) command.transition = tn; } + //command.bs = parseInt(gId('bs').value); req = JSON.stringify(command); if (req.length > 1340) useWs = false; // do not send very long requests over websocket if (req.length > 500 && lastinfo && lastinfo.arch == "esp8266") useWs = false; // esp8266 can only handle 500 bytes diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index f1b013e99..9a843dbf4 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -383,6 +383,7 @@ uint16_t crc16(const unsigned char* data_p, size_t length); um_data_t* simulateSound(uint8_t simulationId); void enumerateLedmaps(); uint8_t get_random_wheel_index(uint8_t pos); +uint32_t hashInt(uint32_t s); // RAII guard class for the JSON Buffer lock // Modeled after std::lock_guard diff --git a/wled00/json.cpp b/wled00/json.cpp index fd1527a21..bde6c36c7 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -355,6 +355,11 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId) } } +#ifndef WLED_DISABLE_MODE_BLEND + blendingStyle = root[F("bs")] | blendingStyle; + blendingStyle = constrain(blendingStyle, 0, BLEND_STYLE_COUNT-1); +#endif + // temporary transition (applies only once) tr = root[F("tt")] | -1; if (tr >= 0) { @@ -581,6 +586,9 @@ void serializeState(JsonObject root, bool forPreset, bool includeBri, bool segme root["on"] = (bri > 0); root["bri"] = briLast; root[F("transition")] = transitionDelay/100; //in 100ms +#ifndef WLED_DISABLE_MODE_BLEND + root[F("bs")] = blendingStyle; +#endif } if (!forPreset) { diff --git a/wled00/util.cpp b/wled00/util.cpp index ad7e4b670..eabd3e383 100644 --- a/wled00/util.cpp +++ b/wled00/util.cpp @@ -599,3 +599,10 @@ uint8_t get_random_wheel_index(uint8_t pos) { } return r; } + +uint32_t hashInt(uint32_t s) { + // borrowed from https://stackoverflow.com/questions/664014/what-integer-hash-function-are-good-that-accepts-an-integer-hash-key + s = ((s >> 16) ^ s) * 0x45d9f3b; + s = ((s >> 16) ^ s) * 0x45d9f3b; + return (s >> 16) ^ s; +} diff --git a/wled00/wled.h b/wled00/wled.h index 35b99260a..a58e1ceab 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -8,7 +8,7 @@ */ // version code in format yymmddb (b = daily build) -#define VERSION 2403280 +#define VERSION 2404030 //uncomment this if you have a "my_config.h" file you'd like to use //#define WLED_USE_MY_CONFIG @@ -547,6 +547,7 @@ WLED_GLOBAL byte lastRandomIndex _INIT(0); // used to save last random co // transitions WLED_GLOBAL bool fadeTransition _INIT(true); // enable crossfading brightness/color WLED_GLOBAL bool modeBlending _INIT(true); // enable effect blending +WLED_GLOBAL uint8_t blendingStyle _INIT(0); // effect blending/transitionig style WLED_GLOBAL bool transitionActive _INIT(false); WLED_GLOBAL uint16_t transitionDelay _INIT(750); // global transition duration WLED_GLOBAL uint16_t transitionDelayDefault _INIT(750); // default transition time (stored in cfg.json) From f5199d2b73bb3b516b7d98b4fee8b2af2b688c07 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Wed, 3 Apr 2024 20:55:59 +0200 Subject: [PATCH 006/287] Fix compile. --- wled00/FX_fcn.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 989809d02..bd5758e31 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -419,9 +419,11 @@ uint8_t IRAM_ATTR Segment::currentBri(bool useCct) { uint32_t prog = progress(); uint32_t curBri = useCct ? cct : (on ? opacity : 0); if (prog < 0xFFFFU) { - uint8_t tmpBri = useCct ? _t->_cctT : (_t->_segT._optionsT & 0x0004 ? _t->_briT : 0); #ifndef WLED_DISABLE_MODE_BLEND + uint8_t tmpBri = useCct ? _t->_cctT : (_t->_segT._optionsT & 0x0004 ? _t->_briT : 0); if (blendingStyle > BLEND_STYLE_FADE) return _modeBlend ? tmpBri : curBri; // not fade/blend transition, each effect uses its brightness +#else + uint8_t tmpBri = useCct ? _t->_cctT : _t->_briT; #endif curBri *= prog; curBri += tmpBri * (0xFFFFU - prog); From a3a8fa1cef1f54c2d1028a0c3e5a5fcc6ef8235e Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Mon, 8 Apr 2024 16:24:27 +0200 Subject: [PATCH 007/287] Remove conditional fade/blend - transitions always enabled (use delay 0 to disable) - optimisation in on/off fade - fix for palette/color blend when blending style is not fade - various tweaks and optimisations --- CHANGELOG.md | 3 + .../stairway-wipe-usermod-v2.h | 2 +- .../stairway_wipe_basic/wled06_usermod.ino | 111 ------------------ wled00/FX.cpp | 61 +++++----- wled00/FX.h | 7 +- wled00/FX_2Dfcn.cpp | 10 +- wled00/FX_fcn.cpp | 104 ++++++++-------- wled00/bus_manager.cpp | 6 +- wled00/bus_manager.h | 3 +- wled00/cfg.cpp | 14 +-- wled00/data/settings_leds.htm | 7 +- wled00/fcn_declare.h | 1 - wled00/json.cpp | 6 +- wled00/led.cpp | 54 +++------ wled00/playlist.cpp | 2 +- wled00/set.cpp | 9 +- wled00/udp.cpp | 6 +- wled00/wled.cpp | 6 +- wled00/wled.h | 2 - wled00/wled_eeprom.cpp | 2 +- wled00/xml.cpp | 5 +- 21 files changed, 135 insertions(+), 286 deletions(-) delete mode 100644 usermods/stairway_wipe_basic/wled06_usermod.ino diff --git a/CHANGELOG.md b/CHANGELOG.md index 46f6df2de..0d86c8b2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ ## WLED changelog +#### Build 2404050 +- Blending styles (with help from @tkadauke) + #### Build 2403280 - Individual color channel control for JSON API (fixes #3860) - "col":[int|string|object|array, int|string|object|array, int|string|object|array] diff --git a/usermods/stairway_wipe_basic/stairway-wipe-usermod-v2.h b/usermods/stairway_wipe_basic/stairway-wipe-usermod-v2.h index f712316b8..707479df1 100644 --- a/usermods/stairway_wipe_basic/stairway-wipe-usermod-v2.h +++ b/usermods/stairway_wipe_basic/stairway-wipe-usermod-v2.h @@ -96,7 +96,7 @@ void setup() { jsonTransitionOnce = true; strip.setTransition(0); //no transition effectCurrent = FX_MODE_COLOR_WIPE; - resetTimebase(); //make sure wipe starts from beginning + strip.resetTimebase(); //make sure wipe starts from beginning //set wipe direction Segment& seg = strip.getSegment(0); diff --git a/usermods/stairway_wipe_basic/wled06_usermod.ino b/usermods/stairway_wipe_basic/wled06_usermod.ino deleted file mode 100644 index c1264ebfb..000000000 --- a/usermods/stairway_wipe_basic/wled06_usermod.ino +++ /dev/null @@ -1,111 +0,0 @@ -/* - * This file allows you to add own functionality to WLED more easily - * See: https://github.com/Aircoookie/WLED/wiki/Add-own-functionality - * EEPROM bytes 2750+ are reserved for your custom use case. (if you extend #define EEPSIZE in wled_eeprom.h) - * bytes 2400+ are currently ununsed, but might be used for future wled features - */ - -//Use userVar0 and userVar1 (API calls &U0=,&U1=, uint16_t) - -byte wipeState = 0; //0: inactive 1: wiping 2: solid -unsigned long timeStaticStart = 0; -uint16_t previousUserVar0 = 0; - -//comment this out if you want the turn off effect to be just fading out instead of reverse wipe -#define STAIRCASE_WIPE_OFF - -//gets called once at boot. Do all initialization that doesn't depend on network here -void userSetup() -{ - //setup PIR sensor here, if needed -} - -//gets called every time WiFi is (re-)connected. Initialize own network interfaces here -void userConnected() -{ - -} - -//loop. You can use "if (WLED_CONNECTED)" to check for successful connection -void userLoop() -{ - //userVar0 (U0 in HTTP API): - //has to be set to 1 if movement is detected on the PIR that is the same side of the staircase as the ESP8266 - //has to be set to 2 if movement is detected on the PIR that is the opposite side - //can be set to 0 if no movement is detected. Otherwise LEDs will turn off after a configurable timeout (userVar1 seconds) - - if (userVar0 > 0) - { - if ((previousUserVar0 == 1 && userVar0 == 2) || (previousUserVar0 == 2 && userVar0 == 1)) wipeState = 3; //turn off if other PIR triggered - previousUserVar0 = userVar0; - - if (wipeState == 0) { - startWipe(); - wipeState = 1; - } else if (wipeState == 1) { //wiping - uint32_t cycleTime = 360 + (255 - effectSpeed)*75; //this is how long one wipe takes (minus 25 ms to make sure we switch in time) - if (millis() + strip.timebase > (cycleTime - 25)) { //wipe complete - effectCurrent = FX_MODE_STATIC; - timeStaticStart = millis(); - colorUpdated(CALL_MODE_NOTIFICATION); - wipeState = 2; - } - } else if (wipeState == 2) { //static - if (userVar1 > 0) //if U1 is not set, the light will stay on until second PIR or external command is triggered - { - if (millis() - timeStaticStart > userVar1*1000) wipeState = 3; - } - } else if (wipeState == 3) { //switch to wipe off - #ifdef STAIRCASE_WIPE_OFF - effectCurrent = FX_MODE_COLOR_WIPE; - strip.timebase = 360 + (255 - effectSpeed)*75 - millis(); //make sure wipe starts fully lit - colorUpdated(CALL_MODE_NOTIFICATION); - wipeState = 4; - #else - turnOff(); - #endif - } else { //wiping off - if (millis() + strip.timebase > (725 + (255 - effectSpeed)*150)) turnOff(); //wipe complete - } - } else { - wipeState = 0; //reset for next time - if (previousUserVar0) { - #ifdef STAIRCASE_WIPE_OFF - userVar0 = previousUserVar0; - wipeState = 3; - #else - turnOff(); - #endif - } - previousUserVar0 = 0; - } -} - -void startWipe() -{ - bri = briLast; //turn on - transitionDelayTemp = 0; //no transition - effectCurrent = FX_MODE_COLOR_WIPE; - resetTimebase(); //make sure wipe starts from beginning - - //set wipe direction - Segment& seg = strip.getSegment(0); - bool doReverse = (userVar0 == 2); - seg.setOption(1, doReverse); - - colorUpdated(CALL_MODE_NOTIFICATION); -} - -void turnOff() -{ - #ifdef STAIRCASE_WIPE_OFF - transitionDelayTemp = 0; //turn off immediately after wipe completed - #else - transitionDelayTemp = 4000; //fade out slowly - #endif - bri = 0; - stateUpdated(CALL_MODE_NOTIFICATION); - wipeState = 0; - userVar0 = 0; - previousUserVar0 = 0; -} diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 14341f5b9..e75e20cd2 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -1211,8 +1211,9 @@ static const char _data_FX_MODE_COMET[] PROGMEM = "Lighthouse@!,Fade rate;!,!;!" */ uint16_t mode_fireworks() { if (SEGLEN == 1) return mode_static(); - const uint16_t width = SEGMENT.is2D() ? SEGMENT.virtualWidth() : SEGMENT.virtualLength(); - const uint16_t height = SEGMENT.virtualHeight(); + const unsigned width = SEGMENT.is2D() ? SEGMENT.virtualWidth() : SEGMENT.virtualLength(); + const unsigned height = SEGMENT.virtualHeight(); + const unsigned dimension = width * height; if (SEGENV.call == 0) { SEGENV.aux0 = UINT16_MAX; @@ -1220,19 +1221,19 @@ uint16_t mode_fireworks() { } SEGMENT.fade_out(128); - bool valid1 = (SEGENV.aux0 < width*height); - bool valid2 = (SEGENV.aux1 < width*height); - uint8_t x = SEGENV.aux0%width, y = SEGENV.aux0/width; // 2D coordinates stored in upper and lower byte + bool valid1 = (SEGENV.aux0 < dimension); + bool valid2 = (SEGENV.aux1 < dimension); + unsigned x = SEGENV.aux0%width, y = SEGENV.aux0/width; // 2D coordinates stored in upper and lower byte uint32_t sv1 = 0, sv2 = 0; if (valid1) sv1 = SEGMENT.is2D() ? SEGMENT.getPixelColorXY(x, y) : SEGMENT.getPixelColor(SEGENV.aux0); // get spark color if (valid2) sv2 = SEGMENT.is2D() ? SEGMENT.getPixelColorXY(x, y) : SEGMENT.getPixelColor(SEGENV.aux1); - if (!SEGENV.step) SEGMENT.blur(16); + if (!SEGENV.step) SEGMENT.blur(dimension > 100 ? 16 : 8); if (valid1) { if (SEGMENT.is2D()) SEGMENT.setPixelColorXY(x, y, sv1); else SEGMENT.setPixelColor(SEGENV.aux0, sv1); } // restore spark color after blur if (valid2) { if (SEGMENT.is2D()) SEGMENT.setPixelColorXY(x, y, sv2); else SEGMENT.setPixelColor(SEGENV.aux1, sv2); } // restore old spark color after blur - for (int i=0; i> 1)) == 0) { - uint16_t index = random16(width*height); + unsigned index = random16(dimension); x = index % width; y = index / width; uint32_t col = SEGMENT.color_from_palette(random8(), false, false, 0); @@ -2066,41 +2067,41 @@ uint16_t mode_fire_2012() { struct virtualStrip { static void runStrip(uint16_t stripNr, byte* heat, uint32_t it) { - const uint8_t ignition = max(3,SEGLEN/10); // ignition area: 10% of segment length or minimum 3 pixels + const unsigned ignition = max(3,SEGLEN/10); // ignition area: 10% of segment length or minimum 3 pixels // Step 1. Cool down every cell a little - for (int i = 0; i < SEGLEN; i++) { - uint8_t cool = (it != SEGENV.step) ? random8((((20 + SEGMENT.speed/3) * 16) / SEGLEN)+2) : random8(4); - uint8_t minTemp = (i 1; k--) { + for (unsigned k = SEGLEN -1; k > 1; k--) { heat[k] = (heat[k - 1] + (heat[k - 2]<<1) ) / 3; // heat[k-2] multiplied by 2 } // Step 3. Randomly ignite new 'sparks' of heat near the bottom if (random8() <= SEGMENT.intensity) { - uint8_t y = random8(ignition); - uint8_t boost = (17+SEGMENT.custom3) * (ignition - y/2) / ignition; // integer math! + unsigned y = random8(ignition); + unsigned boost = (17+SEGMENT.custom3) * (ignition - y/2) / ignition; // integer math! heat[y] = qadd8(heat[y], random8(96+2*boost,207+boost)); } } // Step 4. Map from heat cells to LED colors for (int j = 0; j < SEGLEN; j++) { - SEGMENT.setPixelColor(indexToVStrip(j, stripNr), ColorFromPalette(SEGPALETTE, MIN(heat[j],240), 255, NOBLEND)); + SEGMENT.setPixelColor(indexToVStrip(j, stripNr), ColorFromPalette(SEGPALETTE, MIN(heat[j],240), 255, LINEARBLEND_NOWRAP)); } } }; - for (int stripNr=0; stripNr 100 ? 32 : 0); if (it != SEGENV.step) SEGENV.step = it; @@ -4856,9 +4857,9 @@ static const char _data_FX_MODE_FLOWSTRIPE[] PROGMEM = "Flow Stripe@Hue speed,Ef uint16_t mode_2DBlackHole(void) { // By: Stepko https://editor.soulmatelights.com/gallery/1012 , Modified by: Andrew Tuline if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const uint16_t cols = SEGMENT.virtualWidth(); - const uint16_t rows = SEGMENT.virtualHeight(); - uint16_t x, y; + const unsigned cols = SEGMENT.virtualWidth(); + const unsigned rows = SEGMENT.virtualHeight(); + unsigned x, y; SEGMENT.fadeToBlackBy(16 + (SEGMENT.speed>>3)); // create fading trails unsigned long t = strip.now/128; // timebase @@ -4877,7 +4878,7 @@ uint16_t mode_2DBlackHole(void) { // By: Stepko https://editor.soulma // central white dot SEGMENT.setPixelColorXY(cols/2, rows/2, WHITE); // blur everything a bit - SEGMENT.blur(16); + SEGMENT.blur(sqrt16(cols*rows) > 100 ? 16 : 0); return FRAMETIME; } // mode_2DBlackHole() @@ -6436,8 +6437,8 @@ static const char _data_FX_MODE_2DSWIRL[] PROGMEM = "Swirl@!,Sensitivity,Blur;,B uint16_t mode_2DWaverly(void) { if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const uint16_t cols = SEGMENT.virtualWidth(); - const uint16_t rows = SEGMENT.virtualHeight(); + const unsigned cols = SEGMENT.virtualWidth(); + const unsigned rows = SEGMENT.virtualHeight(); um_data_t *um_data; if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { @@ -6449,21 +6450,21 @@ uint16_t mode_2DWaverly(void) { SEGMENT.fadeToBlackBy(SEGMENT.speed); long t = strip.now / 2; - for (int i = 0; i < cols; i++) { - uint16_t thisVal = (1 + SEGMENT.intensity/64) * inoise8(i * 45 , t , t)/2; + for (unsigned i = 0; i < cols; i++) { + unsigned thisVal = (1 + SEGMENT.intensity/64) * inoise8(i * 45 , t , t)/2; // use audio if available if (um_data) { thisVal /= 32; // reduce intensity of inoise8() thisVal *= volumeSmth; } - uint16_t thisMax = map(thisVal, 0, 512, 0, rows); + unsigned thisMax = map(thisVal, 0, 512, 0, rows); - for (int j = 0; j < thisMax; j++) { + for (unsigned j = 0; j < thisMax; j++) { SEGMENT.addPixelColorXY(i, j, ColorFromPalette(SEGPALETTE, map(j, 0, thisMax, 250, 0), 255, LINEARBLEND)); SEGMENT.addPixelColorXY((cols - 1) - i, (rows - 1) - j, ColorFromPalette(SEGPALETTE, map(j, 0, thisMax, 250, 0), 255, LINEARBLEND)); } } - SEGMENT.blur(16); + SEGMENT.blur(sqrt16(cols*rows) > 100 ? 16 : 0); return FRAMETIME; } // mode_2DWaverly() diff --git a/wled00/FX.h b/wled00/FX.h index 44c5e1549..fd0bb297b 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -601,7 +601,7 @@ typedef struct Segment { 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); } #ifndef WLED_DISABLE_MODE_BLEND - inline void setClippingRect(int startX, int stopX, int startY = 0, int stopY = 1) { _clipStart = startX; _clipStop = stopX; _clipStartY = startY; _clipStopY = stopY; }; + static inline void setClippingRect(int startX, int stopX, int startY = 0, int stopY = 1) { _clipStart = startX; _clipStop = stopX; _clipStartY = startY; _clipStopY = stopY; }; #endif bool isPixelClipped(int i); uint32_t getPixelColor(int i); @@ -708,9 +708,7 @@ class WS2812FX { // 96 bytes public: WS2812FX() : - paletteFade(0), paletteBlend(0), - cctBlending(0), now(millis()), timebase(0), isMatrix(false), @@ -792,6 +790,7 @@ class WS2812FX { // 96 bytes addEffect(uint8_t id, mode_ptr mode_fn, const char *mode_name), // add effect to the list; defined in FX.cpp setupEffectData(void); // add default effects to the list; defined in FX.cpp + inline void resetTimebase() { timebase = 0U - millis(); } inline void restartRuntime() { for (Segment &seg : _segments) seg.markForReset(); } inline void setTransitionMode(bool t) { for (Segment &seg : _segments) seg.startTransition(t ? _transitionDur : 0); } inline void setColor(uint8_t slot, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0) { setColor(slot, RGBW32(r,g,b,w)); } @@ -806,7 +805,6 @@ class WS2812FX { // 96 bytes inline void resume(void) { _suspend = false; } // will resume strip.service() execution bool - paletteFade, checkSegmentAlignment(void), hasRGBWBus(void), hasCCTBus(void), @@ -822,7 +820,6 @@ class WS2812FX { // 96 bytes uint8_t paletteBlend, - cctBlending, getActiveSegmentsNum(void), getFirstSelectedSegId(void), getLastActiveSegmentId(void), diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index 7e1419293..a37e5d11f 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -176,10 +176,10 @@ bool IRAM_ATTR Segment::isPixelXYClipped(int x, int y) { if (_clipStart != _clipStop && blendingStyle > BLEND_STYLE_FADE) { const bool invertX = _clipStart > _clipStop; const bool invertY = _clipStartY > _clipStopY; - const unsigned startX = invertX ? _clipStop : _clipStart; - const unsigned stopX = invertX ? _clipStart : _clipStop; - const unsigned startY = invertY ? _clipStopY : _clipStartY; - const unsigned stopY = invertY ? _clipStartY : _clipStopY; + const int startX = invertX ? _clipStop : _clipStart; + const int stopX = invertX ? _clipStart : _clipStop; + const int startY = invertY ? _clipStopY : _clipStartY; + const int stopY = invertY ? _clipStartY : _clipStopY; if (blendingStyle == BLEND_STYLE_FAIRY_DUST) { const unsigned width = stopX - startX; // assumes full segment width (faster than virtualWidth()) const unsigned len = width * (stopY - startY); // assumes full segment height (faster than virtualHeight()) @@ -295,7 +295,7 @@ void Segment::setPixelColorXY(float x, float y, uint32_t col, bool aa) // returns RGBW values of pixel uint32_t IRAM_ATTR Segment::getPixelColorXY(uint16_t x, uint16_t y) { if (!isActive()) return 0; // not active - if (x >= virtualWidth() || y >= virtualHeight() || x<0 || y<0) return 0; // if pixel would fall out of virtual segment just exit + if (x >= virtualWidth() || y >= virtualHeight() || x<0 || y<0 || isPixelXYClipped(x,y)) return 0; // if pixel would fall out of virtual segment just exit if (reverse ) x = virtualWidth() - x - 1; if (reverse_y) y = virtualHeight() - y - 1; if (transpose) { uint16_t t = x; x = y; y = t; } // swap X & Y if segment transposed diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 00196e38e..4ef110e07 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -293,21 +293,17 @@ void Segment::startTransition(uint16_t dur) { _t->_briT = on ? opacity : 0; _t->_cctT = cct; #ifndef WLED_DISABLE_MODE_BLEND - if (modeBlending) { - swapSegenv(_t->_segT); - _t->_modeT = mode; - _t->_segT._dataLenT = 0; - _t->_segT._dataT = nullptr; - if (_dataLen > 0 && data) { - _t->_segT._dataT = (byte *)malloc(_dataLen); - if (_t->_segT._dataT) { - //DEBUG_PRINTF_P(PSTR("-- Allocated duplicate data (%d) for %p: %p\n"), _dataLen, this, _t->_segT._dataT); - memcpy(_t->_segT._dataT, data, _dataLen); - _t->_segT._dataLenT = _dataLen; - } + swapSegenv(_t->_segT); + _t->_modeT = mode; + _t->_segT._dataLenT = 0; + _t->_segT._dataT = nullptr; + if (_dataLen > 0 && data) { + _t->_segT._dataT = (byte *)malloc(_dataLen); + if (_t->_segT._dataT) { + //DEBUG_PRINTF_P(PSTR("-- Allocated duplicate data (%d) for %p: %p\n"), _dataLen, this, _t->_segT._dataT); + memcpy(_t->_segT._dataT, data, _dataLen); + _t->_segT._dataLenT = _dataLen; } - } else { - for (size_t i=0; i_segT._colorT[i] = colors[i]; } #else for (size_t i=0; i_colorT[i] = colors[i]; @@ -435,7 +431,7 @@ uint8_t IRAM_ATTR Segment::currentBri(bool useCct) { uint8_t IRAM_ATTR Segment::currentMode() { #ifndef WLED_DISABLE_MODE_BLEND uint16_t prog = progress(); - if (modeBlending && prog < 0xFFFFU) return _t->_modeT; + if (prog < 0xFFFFU) return _t->_modeT; #endif return mode; } @@ -445,7 +441,7 @@ uint32_t IRAM_ATTR Segment::currentColor(uint8_t slot) { uint32_t prog = progress(); if (prog == 0xFFFFU) return colors[slot]; #ifndef WLED_DISABLE_MODE_BLEND - if (blendingStyle > BLEND_STYLE_FADE) return _modeBlend ? _t->_segT._colorT[slot] : colors[slot]; // not fade/blend transition, each effect uses its color + if (blendingStyle > BLEND_STYLE_FADE && mode != _t->_modeT) return _modeBlend ? _t->_segT._colorT[slot] : colors[slot]; // not fade/blend transition, each effect uses its color return color_blend(_t->_segT._colorT[slot], colors[slot], prog, true); #else return color_blend(_t->_colorT[slot], colors[slot], prog, true); @@ -456,10 +452,10 @@ CRGBPalette16 IRAM_ATTR &Segment::currentPalette(CRGBPalette16 &targetPalette, u loadPalette(targetPalette, pal); uint16_t prog = progress(); #ifndef WLED_DISABLE_MODE_BLEND - if (prog < 0xFFFFU && blendingStyle > BLEND_STYLE_FADE && _modeBlend) targetPalette = _t->_palT; // not fade/blend transition, each effect uses its palette + if (prog < 0xFFFFU && blendingStyle > BLEND_STYLE_FADE && _modeBlend && mode != _t->_modeT) targetPalette = _t->_palT; // not fade/blend transition, each effect uses its palette else #endif - if (strip.paletteFade && prog < 0xFFFFU) { + if (prog < 0xFFFFU) { // blend palettes // there are about 255 blend passes of 48 "blends" to completely blend two palettes (in _dur time) // minimum blend time is 100ms maximum is 65535ms @@ -473,19 +469,16 @@ CRGBPalette16 IRAM_ATTR &Segment::currentPalette(CRGBPalette16 &targetPalette, u // relies on WS2812FX::service() to call it for each frame void Segment::handleRandomPalette() { // is it time to generate a new palette? - if ((uint16_t)(millis() / 1000U) - _lastPaletteChange > randomPaletteChangeTime){ - _newRandomPalette = useHarmonicRandomPalette ? generateHarmonicRandomPalette(_randomPalette) : generateRandomPalette(); - _lastPaletteChange = (uint16_t)(millis() / 1000U); - _lastPaletteBlend = (uint16_t)millis() - 512; // starts blending immediately + if ((uint16_t)(millis()/1000U) - _lastPaletteChange > randomPaletteChangeTime) { + _newRandomPalette = useHarmonicRandomPalette ? generateHarmonicRandomPalette(_randomPalette) : generateRandomPalette(); + _lastPaletteChange = (uint16_t)(millis()/1000U); + _lastPaletteBlend = (uint16_t)(millis())-512; // starts blending immediately } - // if palette transitions is enabled, blend it according to Transition Time (if longer than minimum given by service calls) - if (strip.paletteFade) { - // assumes that 128 updates are sufficient to blend a palette, so shift by 7 (can be more, can be less) - // in reality there need to be 255 blends to fully blend two entirely different palettes - if ((uint16_t)((uint16_t)millis() - _lastPaletteBlend) < strip.getTransition() >> 7) return; // not yet time to fade, delay the update - _lastPaletteBlend = (uint16_t)millis(); - } + // assumes that 128 updates are sufficient to blend a palette, so shift by 7 (can be more, can be less) + // in reality there need to be 255 blends to fully blend two entirely different palettes + if ((uint16_t)millis() - _lastPaletteBlend < strip.getTransition() >> 7) return; // not yet time to fade, delay the update + _lastPaletteBlend = (uint16_t)millis(); nblendPaletteTowardPalette(_randomPalette, _newRandomPalette, 48); } @@ -549,7 +542,7 @@ bool Segment::setColor(uint8_t slot, uint32_t c) { //returns true if changed if (slot == 0 && c == BLACK) return false; // on/off segment cannot have primary color black if (slot == 1 && c != BLACK) return false; // on/off segment cannot have secondary color non black } - if (fadeTransition) startTransition(strip.getTransition()); // start transition prior to change + startTransition(strip.getTransition()); // start transition prior to change colors[slot] = c; stateChanged = true; // send UDP/WS broadcast return true; @@ -562,21 +555,21 @@ void Segment::setCCT(uint16_t k) { k = (k - 1900) >> 5; } if (cct == k) return; - if (fadeTransition) startTransition(strip.getTransition()); // start transition prior to change + startTransition(strip.getTransition()); // start transition prior to change cct = k; stateChanged = true; // send UDP/WS broadcast } void Segment::setOpacity(uint8_t o) { if (opacity == o) return; - if (fadeTransition) startTransition(strip.getTransition()); // start transition prior to change + startTransition(strip.getTransition()); // start transition prior to change opacity = o; stateChanged = true; // send UDP/WS broadcast } void Segment::setOption(uint8_t n, bool val) { bool prevOn = on; - if (fadeTransition && n == SEG_OPTION_ON && val != prevOn) startTransition(strip.getTransition()); // start transition prior to change + if (n == SEG_OPTION_ON && val != prevOn) startTransition(strip.getTransition()); // start transition prior to change if (val) options |= 0x01 << n; else options &= ~(0x01 << n); if (!(n == SEG_OPTION_SELECTED || n == SEG_OPTION_RESET)) stateChanged = true; // send UDP/WS broadcast @@ -589,7 +582,7 @@ void Segment::setMode(uint8_t fx, bool loadDefaults) { // if we have a valid mode & is not reserved if (fx != mode) { #ifndef WLED_DISABLE_MODE_BLEND - if (modeBlending) startTransition(strip.getTransition()); // set effect transitions + startTransition(strip.getTransition()); // set effect transitions #endif mode = fx; // load default values from effect string @@ -620,7 +613,7 @@ void Segment::setPalette(uint8_t pal) { if (pal < 245 && pal > GRADIENT_PALETTE_COUNT+13) pal = 0; // built in palettes if (pal > 245 && (strip.customPalettes.size() == 0 || 255U-pal > strip.customPalettes.size()-1)) pal = 0; // custom palettes if (pal != palette) { - if (strip.paletteFade) startTransition(strip.getTransition()); + startTransition(strip.getTransition()); palette = pal; stateChanged = true; // send UDP/WS broadcast } @@ -688,8 +681,8 @@ bool IRAM_ATTR Segment::isPixelClipped(int i) { #ifndef WLED_DISABLE_MODE_BLEND if (_clipStart != _clipStop && blendingStyle > BLEND_STYLE_FADE) { bool invert = _clipStart > _clipStop; - unsigned start = invert ? _clipStop : _clipStart; - unsigned stop = invert ? _clipStart : _clipStop; + int start = invert ? _clipStop : _clipStart; + int stop = invert ? _clipStart : _clipStop; if (blendingStyle == BLEND_STYLE_FAIRY_DUST) { unsigned len = stop - start; if (len < 2) return false; @@ -888,6 +881,8 @@ uint32_t IRAM_ATTR Segment::getPixelColor(int i) } #endif + if (isPixelClipped(i)) return 0; // handle clipping on 1D + if (reverse) i = virtualLength() - i - 1; i *= groupLength(); i += start; @@ -1234,8 +1229,8 @@ void WS2812FX::service() { // would need to be allocated for each effect and then blended together for each pixel. [[maybe_unused]] uint8_t tmpMode = seg.currentMode(); // this will return old mode while in transition #ifndef WLED_DISABLE_MODE_BLEND - seg.setClippingRect(0, 0); // disable clipping - if (modeBlending && seg.mode != tmpMode) { + Segment::setClippingRect(0, 0); // disable clipping (just in case) + if (seg.mode != tmpMode) { // could try seg.isInTransition() to allow color and palette to follow blending styles // set clipping rectangle // new mode is run inside clipping area and old mode outside clipping area unsigned p = seg.progress(); @@ -1245,48 +1240,48 @@ void WS2812FX::service() { unsigned dh = p * h / 0xFFFFU + 1; switch (blendingStyle) { case BLEND_STYLE_FAIRY_DUST: // fairy dust (must set entire segment, see isPixelXYClipped()) - seg.setClippingRect(0, w, 0, h); + Segment::setClippingRect(0, w, 0, h); break; case BLEND_STYLE_SWIPE_RIGHT: // left-to-right - seg.setClippingRect(0, dw, 0, h); + Segment::setClippingRect(0, dw, 0, h); break; case BLEND_STYLE_SWIPE_LEFT: // right-to-left - seg.setClippingRect(w - dw, w, 0, h); + Segment::setClippingRect(w - dw, w, 0, h); break; case BLEND_STYLE_PINCH_OUT: // corners - seg.setClippingRect((w + dw)/2, (w - dw)/2, (h + dh)/2, (h - dh)/2); // inverted!! + Segment::setClippingRect((w + dw)/2, (w - dw)/2, (h + dh)/2, (h - dh)/2); // inverted!! break; case BLEND_STYLE_INSIDE_OUT: // outward - seg.setClippingRect((w - dw)/2, (w + dw)/2, (h - dh)/2, (h + dh)/2); + Segment::setClippingRect((w - dw)/2, (w + dw)/2, (h - dh)/2, (h + dh)/2); break; case BLEND_STYLE_SWIPE_DOWN: // top-to-bottom (2D) - seg.setClippingRect(0, w, 0, dh); + Segment::setClippingRect(0, w, 0, dh); break; case BLEND_STYLE_SWIPE_UP: // bottom-to-top (2D) - seg.setClippingRect(0, w, h - dh, h); + Segment::setClippingRect(0, w, h - dh, h); break; case BLEND_STYLE_OPEN_H: // horizontal-outward (2D) same look as INSIDE_OUT on 1D - seg.setClippingRect((w - dw)/2, (w + dw)/2, 0, h); + Segment::setClippingRect((w - dw)/2, (w + dw)/2, 0, h); break; case BLEND_STYLE_OPEN_V: // vertical-outward (2D) - seg.setClippingRect(0, w, (h - dh)/2, (h + dh)/2); + Segment::setClippingRect(0, w, (h - dh)/2, (h + dh)/2); break; case BLEND_STYLE_PUSH_TL: // TL-to-BR (2D) - seg.setClippingRect(0, dw, 0, dh); + Segment::setClippingRect(0, dw, 0, dh); break; case BLEND_STYLE_PUSH_TR: // TR-to-BL (2D) - seg.setClippingRect(w - dw, w, 0, dh); + Segment::setClippingRect(w - dw, w, 0, dh); break; case BLEND_STYLE_PUSH_BR: // BR-to-TL (2D) - seg.setClippingRect(w - dw, w, h - dh, h); + Segment::setClippingRect(w - dw, w, h - dh, h); break; case BLEND_STYLE_PUSH_BL: // BL-to-TR (2D) - seg.setClippingRect(0, dw, h - dh, h); + Segment::setClippingRect(0, dw, h - dh, h); break; } } delay = (*_mode[seg.mode])(); // run new/current mode - if (modeBlending && seg.mode != tmpMode) { + if (seg.mode != tmpMode) { // could try seg.isInTransition() to allow color and palette to follow blending styles Segment::tmpsegd_t _tmpSegData; Segment::modeBlend(true); // set semaphore seg.swapSegenv(_tmpSegData); // temporarily store new mode state (and swap it with transitional state) @@ -1301,13 +1296,14 @@ void WS2812FX::service() { #endif seg.call++; if (seg.isInTransition() && delay > FRAMETIME) delay = FRAMETIME; // force faster updates during transition - BusManager::setSegmentCCT(oldCCT); // restore old CCT for ABL adjustments + BusManager::setSegmentCCT(oldCCT); // restore old CCT for ABL adjustments } seg.next_time = nowUp + delay; } _segment_index++; } + Segment::setClippingRect(0, 0); // disable clipping for overlays _virtualSegmentLength = 0; _isServicing = false; _triggered = false; diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index 82e81a387..02a76f69d 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -391,14 +391,14 @@ BusPwm::BusPwm(BusConfig &bc) uint8_t numPins = NUM_PWM_PINS(bc.type); _frequency = bc.frequency ? bc.frequency : WLED_PWM_FREQ; - #ifdef ESP8266 +#ifdef ESP8266 // duty cycle resolution (_depth) can be extracted from this formula: 1MHz > _frequency * 2^_depth if (_frequency > 1760) _depth = 8; else if (_frequency > 880) _depth = 9; else _depth = 10; // WLED_PWM_FREQ <= 880Hz analogWriteRange((1<<_depth)-1); analogWriteFreq(_frequency); - #else +#else _ledcStart = pinManager.allocateLedc(numPins); if (_ledcStart == 255) { //no more free LEDC channels deallocatePins(); return; @@ -408,7 +408,7 @@ BusPwm::BusPwm(BusConfig &bc) else if (_frequency > 39062) _depth = 10; else if (_frequency > 19531) _depth = 11; else _depth = 12; // WLED_PWM_FREQ <= 19531Hz - #endif +#endif for (unsigned i = 0; i < numPins; i++) { uint8_t currentPin = bc.pins[i]; diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h index c128f8c09..cdd1e82ff 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -173,10 +173,11 @@ class Bus { type == TYPE_FW1906 || type == TYPE_WS2805 ) return true; return false; } - static int16_t getCCT() { return _cct; } + static inline int16_t getCCT() { return _cct; } static void setCCT(int16_t cct) { _cct = cct; } + static inline uint8_t getCCTBlend() { return _cctBlend; } static void setCCTBlend(uint8_t b) { if (b > 100) b = 100; _cctBlend = (b * 127) / 100; diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index 530777ab5..01c407f95 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -111,8 +111,8 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { CJSON(correctWB, hw_led["cct"]); CJSON(cctFromRgb, hw_led[F("cr")]); CJSON(cctICused, hw_led[F("ic")]); - CJSON(strip.cctBlending, hw_led[F("cb")]); - Bus::setCCTBlend(strip.cctBlending); + uint8_t cctBlending = hw_led[F("cb")] | Bus::getCCTBlend(); + Bus::setCCTBlend(cctBlending); strip.setTargetFps(hw_led["fps"]); //NOP if 0, default 42 FPS CJSON(useGlobalLedBuffer, hw_led[F("ld")]); @@ -408,12 +408,9 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { } JsonObject light_tr = light["tr"]; - CJSON(fadeTransition, light_tr["mode"]); - CJSON(modeBlending, light_tr["fx"]); int tdd = light_tr["dur"] | -1; if (tdd >= 0) transitionDelay = transitionDelayDefault = tdd * 100; - strip.setTransition(fadeTransition ? transitionDelayDefault : 0); - CJSON(strip.paletteFade, light_tr["pal"]); + strip.setTransition(transitionDelayDefault); CJSON(randomPaletteChangeTime, light_tr[F("rpc")]); CJSON(useHarmonicRandomPalette, light_tr[F("hrp")]); @@ -777,7 +774,7 @@ void serializeConfig() { hw_led["cct"] = correctWB; hw_led[F("cr")] = cctFromRgb; hw_led[F("ic")] = cctICused; - hw_led[F("cb")] = strip.cctBlending; + hw_led[F("cb")] = Bus::getCCTBlend(); hw_led["fps"] = strip.getTargetFps(); hw_led[F("rgbwm")] = Bus::getGlobalAWMode(); // global auto white mode override hw_led[F("ld")] = useGlobalLedBuffer; @@ -894,10 +891,7 @@ void serializeConfig() { light_gc["val"] = gammaCorrectVal; JsonObject light_tr = light.createNestedObject("tr"); - light_tr["mode"] = fadeTransition; - light_tr["fx"] = modeBlending; light_tr["dur"] = transitionDelayDefault / 100; - light_tr["pal"] = strip.paletteFade; light_tr[F("rpc")] = randomPaletteChangeTime; light_tr[F("hrp")] = useHarmonicRandomPalette; diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index dddedd471..2a5267825 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -834,12 +834,7 @@ Swap:

Brightness factor: %

Transitions

- Enable transitions:
- - Effect blending:
- Transition Time: ms
- Palette transitions:
-
+ Transition Time: ms
Random Cycle Palette Time: s
Use harmonic Random Cycle Palette:

Timed light

diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 010ad3a53..85893dfae 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -181,7 +181,6 @@ bool serveLiveLeds(AsyncWebServerRequest* request, uint32_t wsClient = 0); void setValuesFromSegment(uint8_t s); void setValuesFromMainSeg(); void setValuesFromFirstSelectedSeg(); -void resetTimebase(); void toggleOnOff(); void applyBri(); void applyFinalBri(); diff --git a/wled00/json.cpp b/wled00/json.cpp index 269d8a7f6..2e00ec09c 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -351,7 +351,7 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId) tr = root[F("transition")] | -1; if (tr >= 0) { transitionDelay = tr * 100; - if (fadeTransition) strip.setTransition(transitionDelay); + strip.setTransition(transitionDelay); } } @@ -364,7 +364,7 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId) tr = root[F("tt")] | -1; if (tr >= 0) { jsonTransitionOnce = true; - if (fadeTransition) strip.setTransition(tr * 100); + strip.setTransition(tr * 100); } tr = root[F("tb")] | -1; @@ -779,7 +779,7 @@ void serializeInfo(JsonObject root) root[F("freeheap")] = ESP.getFreeHeap(); #if defined(ARDUINO_ARCH_ESP32) - if (psramSafe && psramFound()) root[F("psram")] = ESP.getFreePsram(); + if (psramFound()) root[F("psram")] = ESP.getFreePsram(); #endif root[F("uptime")] = millis()/1000 + rolloverMillis*4294967; diff --git a/wled00/led.cpp b/wled00/led.cpp index 23c8d03c5..acfa3ac36 100644 --- a/wled00/led.cpp +++ b/wled00/led.cpp @@ -47,12 +47,6 @@ void applyValuesToSelectedSegs() } -void resetTimebase() -{ - strip.timebase = 0 - millis(); -} - - void toggleOnOff() { if (bri == 0) @@ -76,7 +70,7 @@ byte scaledBri(byte in) } -//applies global brightness +//applies global temporary brightness (briT) to strip void applyBri() { if (!realtimeMode || !arlsForceMaxBri) { @@ -90,6 +84,7 @@ void applyFinalBri() { briOld = bri; briT = bri; applyBri(); + strip.trigger(); // force one last update } @@ -122,7 +117,7 @@ void stateUpdated(byte callMode) { nightlightStartTime = millis(); } if (briT == 0) { - if (callMode != CALL_MODE_NOTIFICATION) resetTimebase(); //effect start from beginning + if (callMode != CALL_MODE_NOTIFICATION) strip.resetTimebase(); //effect start from beginning } if (bri > 0) briLast = bri; @@ -133,31 +128,24 @@ void stateUpdated(byte callMode) { // notify usermods of state change usermods.onStateChange(callMode); - if (fadeTransition) { - if (strip.getTransition() == 0) { - jsonTransitionOnce = false; - transitionActive = false; - applyFinalBri(); - strip.trigger(); - return; - } - - if (transitionActive) { - briOld = briT; - tperLast = 0; - } else - strip.setTransitionMode(true); // force all segments to transition mode - transitionActive = true; - transitionStartTime = millis(); - } else { + if (strip.getTransition() == 0) { + jsonTransitionOnce = false; + transitionActive = false; applyFinalBri(); - strip.trigger(); + return; } + + if (transitionActive) { + briOld = briT; + tperLast = 0; + } else + strip.setTransitionMode(true); // force all segments to transition mode + transitionActive = true; + transitionStartTime = millis(); } -void updateInterfaces(uint8_t callMode) -{ +void updateInterfaces(uint8_t callMode) { if (!interfaceUpdateCallMode || millis() - lastInterfaceUpdate < INTERFACE_UPDATE_COOLDOWN) return; sendDataWs(); @@ -178,8 +166,7 @@ void updateInterfaces(uint8_t callMode) } -void handleTransitions() -{ +void handleTransitions() { //handle still pending interface update updateInterfaces(interfaceUpdateCallMode); @@ -198,7 +185,6 @@ void handleTransitions() if (tper - tperLast < 0.004f) return; tperLast = tper; briT = briOld + ((bri - briOld) * tper); - applyBri(); } } @@ -211,8 +197,7 @@ void colorUpdated(byte callMode) { } -void handleNightlight() -{ +void handleNightlight() { unsigned long now = millis(); if (now < 100 && lastNlUpdate > 0) lastNlUpdate = 0; // take care of millis() rollover if (now - lastNlUpdate < 100) return; // allow only 10 NL updates per second @@ -292,7 +277,6 @@ void handleNightlight() } //utility for FastLED to use our custom timer -uint32_t get_millisecond_timer() -{ +uint32_t get_millisecond_timer() { return strip.now; } diff --git a/wled00/playlist.cpp b/wled00/playlist.cpp index 67c4f6049..225102da6 100644 --- a/wled00/playlist.cpp +++ b/wled00/playlist.cpp @@ -146,7 +146,7 @@ void handlePlaylist() { } jsonTransitionOnce = true; - strip.setTransition(fadeTransition ? playlistEntries[playlistIndex].tr * 100 : 0); + strip.setTransition(playlistEntries[playlistIndex].tr * 100); playlistEntryDur = playlistEntries[playlistIndex].dur; applyPresetFromPlaylist(playlistEntries[playlistIndex].preset); } diff --git a/wled00/set.cpp b/wled00/set.cpp index a2e884c81..c5b9f262c 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -128,8 +128,8 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) correctWB = request->hasArg(F("CCT")); cctFromRgb = request->hasArg(F("CR")); cctICused = request->hasArg(F("IC")); - strip.cctBlending = request->arg(F("CB")).toInt(); - Bus::setCCTBlend(strip.cctBlending); + uint8_t cctBlending = request->arg(F("CB")).toInt(); + Bus::setCCTBlend(cctBlending); Bus::setGlobalAWMode(request->arg(F("AW")).toInt()); strip.setTargetFps(request->arg(F("FR")).toInt()); useGlobalLedBuffer = request->hasArg(F("LD")); @@ -313,11 +313,8 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) gammaCorrectCol = false; } - fadeTransition = request->hasArg(F("TF")); - modeBlending = request->hasArg(F("EB")); t = request->arg(F("TD")).toInt(); if (t >= 0) transitionDelayDefault = t; - strip.paletteFade = request->hasArg(F("PF")); t = request->arg(F("TP")).toInt(); randomPaletteChangeTime = MIN(255,MAX(1,t)); useHarmonicRandomPalette = request->hasArg(F("TH")); @@ -1124,7 +1121,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply) pos = req.indexOf(F("TT=")); if (pos > 0) transitionDelay = getNumVal(&req, pos); - if (fadeTransition) strip.setTransition(transitionDelay); + strip.setTransition(transitionDelay); //set time (unix timestamp) pos = req.indexOf(F("ST=")); diff --git a/wled00/udp.cpp b/wled00/udp.cpp index 100ace166..d2f49144a 100644 --- a/wled00/udp.cpp +++ b/wled00/udp.cpp @@ -225,10 +225,8 @@ void parseNotifyPacket(uint8_t *udpIn) { // set transition time before making any segment changes if (version > 3) { - if (fadeTransition) { - jsonTransitionOnce = true; - strip.setTransition(((udpIn[17] << 0) & 0xFF) + ((udpIn[18] << 8) & 0xFF00)); - } + jsonTransitionOnce = true; + strip.setTransition(((udpIn[17] << 0) & 0xFF) + ((udpIn[18] << 8) & 0xFF00)); } //apply colors from notification to main segment, only if not syncing full segments diff --git a/wled00/wled.cpp b/wled00/wled.cpp index eb7860851..8f64a10e9 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -538,10 +538,10 @@ void WLED::beginStrip() } else { // fix for #3196 if (bootPreset > 0) { - bool oldTransition = fadeTransition; // workaround if transitions are enabled - fadeTransition = false; // ignore transitions temporarily + uint16_t oldTransition = strip.getTransition(); // workaround if transitions are enabled + strip.setTransition(0); // ignore transitions temporarily strip.setColor(0, BLACK); // set all segments black - fadeTransition = oldTransition; // restore transitions + strip.setTransition(oldTransition); // restore transitions col[0] = col[1] = col[2] = col[3] = 0; // needed for colorUpdated() } briLast = briS; bri = 0; diff --git a/wled00/wled.h b/wled00/wled.h index 5a9b8dcf5..8e21343f3 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -545,8 +545,6 @@ WLED_GLOBAL bool wasConnected _INIT(false); WLED_GLOBAL byte lastRandomIndex _INIT(0); // used to save last random color so the new one is not the same // transitions -WLED_GLOBAL bool fadeTransition _INIT(true); // enable crossfading brightness/color -WLED_GLOBAL bool modeBlending _INIT(true); // enable effect blending WLED_GLOBAL uint8_t blendingStyle _INIT(0); // effect blending/transitionig style WLED_GLOBAL bool transitionActive _INIT(false); WLED_GLOBAL uint16_t transitionDelay _INIT(750); // global transition duration diff --git a/wled00/wled_eeprom.cpp b/wled00/wled_eeprom.cpp index 4f2c14d47..a43c23e8d 100755 --- a/wled00/wled_eeprom.cpp +++ b/wled00/wled_eeprom.cpp @@ -220,7 +220,7 @@ void loadSettingsFromEEPROM() if (lastEEPROMversion > 7) { - strip.paletteFade = EEPROM.read(374); + //strip.paletteFade = EEPROM.read(374); strip.paletteBlend = EEPROM.read(382); for (int i = 0; i < 8; ++i) diff --git a/wled00/xml.cpp b/wled00/xml.cpp index 3915d9b0e..e9c1fac46 100644 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -357,7 +357,7 @@ void getSettingsJS(byte subPage, char* dest) sappend('c',SET_F("CCT"),correctWB); sappend('c',SET_F("IC"),cctICused); sappend('c',SET_F("CR"),cctFromRgb); - sappend('v',SET_F("CB"),strip.cctBlending); + sappend('v',SET_F("CB"),Bus::getCCTBlend()); sappend('v',SET_F("FR"),strip.getTargetFps()); sappend('v',SET_F("AW"),Bus::getGlobalAWMode()); sappend('c',SET_F("LD"),useGlobalLedBuffer); @@ -445,10 +445,7 @@ void getSettingsJS(byte subPage, char* dest) sappend('c',SET_F("GB"),gammaCorrectBri); sappend('c',SET_F("GC"),gammaCorrectCol); dtostrf(gammaCorrectVal,3,1,nS); sappends('s',SET_F("GV"),nS); - sappend('c',SET_F("TF"),fadeTransition); - sappend('c',SET_F("EB"),modeBlending); sappend('v',SET_F("TD"),transitionDelayDefault); - sappend('c',SET_F("PF"),strip.paletteFade); sappend('v',SET_F("TP"),randomPaletteChangeTime); sappend('c',SET_F("TH"),useHarmonicRandomPalette); sappend('v',SET_F("BF"),briMultiplier); From ef017fd343bc0329125c52681015fc6b85284be1 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Sun, 14 Apr 2024 15:34:59 +0200 Subject: [PATCH 008/287] Revert FX.cpp --- wled00/FX.cpp | 61 +++++++++++++++++++++++++-------------------------- 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index e75e20cd2..14341f5b9 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -1211,9 +1211,8 @@ static const char _data_FX_MODE_COMET[] PROGMEM = "Lighthouse@!,Fade rate;!,!;!" */ uint16_t mode_fireworks() { if (SEGLEN == 1) return mode_static(); - const unsigned width = SEGMENT.is2D() ? SEGMENT.virtualWidth() : SEGMENT.virtualLength(); - const unsigned height = SEGMENT.virtualHeight(); - const unsigned dimension = width * height; + const uint16_t width = SEGMENT.is2D() ? SEGMENT.virtualWidth() : SEGMENT.virtualLength(); + const uint16_t height = SEGMENT.virtualHeight(); if (SEGENV.call == 0) { SEGENV.aux0 = UINT16_MAX; @@ -1221,19 +1220,19 @@ uint16_t mode_fireworks() { } SEGMENT.fade_out(128); - bool valid1 = (SEGENV.aux0 < dimension); - bool valid2 = (SEGENV.aux1 < dimension); - unsigned x = SEGENV.aux0%width, y = SEGENV.aux0/width; // 2D coordinates stored in upper and lower byte + bool valid1 = (SEGENV.aux0 < width*height); + bool valid2 = (SEGENV.aux1 < width*height); + uint8_t x = SEGENV.aux0%width, y = SEGENV.aux0/width; // 2D coordinates stored in upper and lower byte uint32_t sv1 = 0, sv2 = 0; if (valid1) sv1 = SEGMENT.is2D() ? SEGMENT.getPixelColorXY(x, y) : SEGMENT.getPixelColor(SEGENV.aux0); // get spark color if (valid2) sv2 = SEGMENT.is2D() ? SEGMENT.getPixelColorXY(x, y) : SEGMENT.getPixelColor(SEGENV.aux1); - if (!SEGENV.step) SEGMENT.blur(dimension > 100 ? 16 : 8); + if (!SEGENV.step) SEGMENT.blur(16); if (valid1) { if (SEGMENT.is2D()) SEGMENT.setPixelColorXY(x, y, sv1); else SEGMENT.setPixelColor(SEGENV.aux0, sv1); } // restore spark color after blur if (valid2) { if (SEGMENT.is2D()) SEGMENT.setPixelColorXY(x, y, sv2); else SEGMENT.setPixelColor(SEGENV.aux1, sv2); } // restore old spark color after blur - for (unsigned i=0; i> 1)) == 0) { - unsigned index = random16(dimension); + uint16_t index = random16(width*height); x = index % width; y = index / width; uint32_t col = SEGMENT.color_from_palette(random8(), false, false, 0); @@ -2067,41 +2066,41 @@ uint16_t mode_fire_2012() { struct virtualStrip { static void runStrip(uint16_t stripNr, byte* heat, uint32_t it) { - const unsigned ignition = max(3,SEGLEN/10); // ignition area: 10% of segment length or minimum 3 pixels + const uint8_t ignition = max(3,SEGLEN/10); // ignition area: 10% of segment length or minimum 3 pixels // Step 1. Cool down every cell a little - for (unsigned i = 0; i < SEGLEN; i++) { - unsigned cool = (it != SEGENV.step) ? random8((((20 + SEGMENT.speed/3) * 16) / SEGLEN)+2) : random8(4); - unsigned minTemp = (i 1; k--) { + for (int k = SEGLEN -1; k > 1; k--) { heat[k] = (heat[k - 1] + (heat[k - 2]<<1) ) / 3; // heat[k-2] multiplied by 2 } // Step 3. Randomly ignite new 'sparks' of heat near the bottom if (random8() <= SEGMENT.intensity) { - unsigned y = random8(ignition); - unsigned boost = (17+SEGMENT.custom3) * (ignition - y/2) / ignition; // integer math! + uint8_t y = random8(ignition); + uint8_t boost = (17+SEGMENT.custom3) * (ignition - y/2) / ignition; // integer math! heat[y] = qadd8(heat[y], random8(96+2*boost,207+boost)); } } // Step 4. Map from heat cells to LED colors for (int j = 0; j < SEGLEN; j++) { - SEGMENT.setPixelColor(indexToVStrip(j, stripNr), ColorFromPalette(SEGPALETTE, MIN(heat[j],240), 255, LINEARBLEND_NOWRAP)); + SEGMENT.setPixelColor(indexToVStrip(j, stripNr), ColorFromPalette(SEGPALETTE, MIN(heat[j],240), 255, NOBLEND)); } } }; - for (unsigned stripNr=0; stripNr 100 ? 32 : 0); + if (SEGMENT.is2D()) SEGMENT.blur(32); if (it != SEGENV.step) SEGENV.step = it; @@ -4857,9 +4856,9 @@ static const char _data_FX_MODE_FLOWSTRIPE[] PROGMEM = "Flow Stripe@Hue speed,Ef uint16_t mode_2DBlackHole(void) { // By: Stepko https://editor.soulmatelights.com/gallery/1012 , Modified by: Andrew Tuline if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const unsigned cols = SEGMENT.virtualWidth(); - const unsigned rows = SEGMENT.virtualHeight(); - unsigned x, y; + const uint16_t cols = SEGMENT.virtualWidth(); + const uint16_t rows = SEGMENT.virtualHeight(); + uint16_t x, y; SEGMENT.fadeToBlackBy(16 + (SEGMENT.speed>>3)); // create fading trails unsigned long t = strip.now/128; // timebase @@ -4878,7 +4877,7 @@ uint16_t mode_2DBlackHole(void) { // By: Stepko https://editor.soulma // central white dot SEGMENT.setPixelColorXY(cols/2, rows/2, WHITE); // blur everything a bit - SEGMENT.blur(sqrt16(cols*rows) > 100 ? 16 : 0); + SEGMENT.blur(16); return FRAMETIME; } // mode_2DBlackHole() @@ -6437,8 +6436,8 @@ static const char _data_FX_MODE_2DSWIRL[] PROGMEM = "Swirl@!,Sensitivity,Blur;,B uint16_t mode_2DWaverly(void) { if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const unsigned cols = SEGMENT.virtualWidth(); - const unsigned rows = SEGMENT.virtualHeight(); + const uint16_t cols = SEGMENT.virtualWidth(); + const uint16_t rows = SEGMENT.virtualHeight(); um_data_t *um_data; if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { @@ -6450,21 +6449,21 @@ uint16_t mode_2DWaverly(void) { SEGMENT.fadeToBlackBy(SEGMENT.speed); long t = strip.now / 2; - for (unsigned i = 0; i < cols; i++) { - unsigned thisVal = (1 + SEGMENT.intensity/64) * inoise8(i * 45 , t , t)/2; + for (int i = 0; i < cols; i++) { + uint16_t thisVal = (1 + SEGMENT.intensity/64) * inoise8(i * 45 , t , t)/2; // use audio if available if (um_data) { thisVal /= 32; // reduce intensity of inoise8() thisVal *= volumeSmth; } - unsigned thisMax = map(thisVal, 0, 512, 0, rows); + uint16_t thisMax = map(thisVal, 0, 512, 0, rows); - for (unsigned j = 0; j < thisMax; j++) { + for (int j = 0; j < thisMax; j++) { SEGMENT.addPixelColorXY(i, j, ColorFromPalette(SEGPALETTE, map(j, 0, thisMax, 250, 0), 255, LINEARBLEND)); SEGMENT.addPixelColorXY((cols - 1) - i, (rows - 1) - j, ColorFromPalette(SEGPALETTE, map(j, 0, thisMax, 250, 0), 255, LINEARBLEND)); } } - SEGMENT.blur(sqrt16(cols*rows) > 100 ? 16 : 0); + SEGMENT.blur(16); return FRAMETIME; } // mode_2DWaverly() From da484b07f5bda6d0a955e389658971e4b18f18f1 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Sun, 2 Jun 2024 21:30:44 +0200 Subject: [PATCH 009/287] Use transition style for palette and color change - as requested by @willmmiles & @tkadauke --- wled00/FX.h | 2 ++ wled00/FX_fcn.cpp | 63 +++++++++++++++++++++++++++++++---------------- 2 files changed, 44 insertions(+), 21 deletions(-) diff --git a/wled00/FX.h b/wled00/FX.h index cb9cafb23..acca0b20d 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -457,6 +457,7 @@ typedef struct Segment { #else uint32_t _colorT[NUM_COLORS]; #endif + uint8_t _palTid; // previous palette uint8_t _briT; // temporary brightness uint8_t _cctT; // temporary CCT CRGBPalette16 _palT; // temporary palette @@ -594,6 +595,7 @@ typedef struct Segment { uint16_t progress(void); // transition progression between 0-65535 uint8_t currentBri(bool useCct = false); // current segment brightness/CCT (blended while in transition) uint8_t currentMode(void); // currently active effect/mode (while in transition) + uint8_t currentPalette(void); // currently active palette (while in transition) uint32_t currentColor(uint8_t slot); // currently active segment color (blended while in transition) CRGBPalette16 &loadPalette(CRGBPalette16 &tgt, uint8_t pal); void setCurrentPalette(void); diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 29302bfb2..df06e56ad 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -291,6 +291,7 @@ void Segment::startTransition(uint16_t dur) { //DEBUG_PRINTF_P(PSTR("-- Started transition: %p (%p)\n"), this, _t); loadPalette(_t->_palT, palette); + _t->_palTid = palette; _t->_briT = on ? opacity : 0; _t->_cctT = cct; #ifndef WLED_DISABLE_MODE_BLEND @@ -442,27 +443,43 @@ uint32_t IRAM_ATTR Segment::currentColor(uint8_t slot) { uint32_t prog = progress(); if (prog == 0xFFFFU) return colors[slot]; #ifndef WLED_DISABLE_MODE_BLEND - if (blendingStyle > BLEND_STYLE_FADE && mode != _t->_modeT) return _modeBlend ? _t->_segT._colorT[slot] : colors[slot]; // not fade/blend transition, each effect uses its color + if (blendingStyle > BLEND_STYLE_FADE) return _modeBlend ? _t->_segT._colorT[slot] : colors[slot]; // not fade/blend transition, each effect uses its color return color_blend(_t->_segT._colorT[slot], colors[slot], prog, true); #else return color_blend(_t->_colorT[slot], colors[slot], prog, true); #endif } +uint8_t IRAM_ATTR Segment::currentPalette() { + unsigned prog = progress(); + if (prog < 0xFFFFU) { +#ifndef WLED_DISABLE_MODE_BLEND + if (blendingStyle > BLEND_STYLE_FADE && _modeBlend) return _t->_palTid; +#else + return _t->_palTid; +#endif + } + return palette; +} + void Segment::setCurrentPalette() { loadPalette(_currentPalette, palette); unsigned prog = progress(); -#ifndef WLED_DISABLE_MODE_BLEND - if (prog < 0xFFFFU && blendingStyle > BLEND_STYLE_FADE && _modeBlend && mode != _t->_modeT) _currentPalette = _t->_palT; // not fade/blend transition, each effect uses its palette - else -#endif if (prog < 0xFFFFU) { - // blend palettes - // there are about 255 blend passes of 48 "blends" to completely blend two palettes (in _dur time) - // minimum blend time is 100ms maximum is 65535ms - unsigned noOfBlends = ((255U * prog) / 0xFFFFU) - _t->_prevPaletteBlends; - for (unsigned i = 0; i < noOfBlends; i++, _t->_prevPaletteBlends++) nblendPaletteTowardPalette(_t->_palT, _currentPalette, 48); - _currentPalette = _t->_palT; // copy transitioning/temporary palette +#ifndef WLED_DISABLE_MODE_BLEND + if (blendingStyle > BLEND_STYLE_FADE) { + //if (_modeBlend) loadPalette(_currentPalette, _t->_palTid); // not fade/blend transition, each effect uses its palette + if (_modeBlend) _currentPalette = _t->_palT; // not fade/blend transition, each effect uses its palette + } else +#endif + { + // blend palettes + // there are about 255 blend passes of 48 "blends" to completely blend two palettes (in _dur time) + // minimum blend time is 100ms maximum is 65535ms + unsigned noOfBlends = ((255U * prog) / 0xFFFFU) - _t->_prevPaletteBlends; + for (unsigned i = 0; i < noOfBlends; i++, _t->_prevPaletteBlends++) nblendPaletteTowardPalette(_t->_palT, _currentPalette, 48); + _currentPalette = _t->_palT; // copy transitioning/temporary palette + } } } @@ -719,7 +736,7 @@ uint16_t IRAM_ATTR Segment::virtualLength() const { bool IRAM_ATTR Segment::isPixelClipped(int i) { #ifndef WLED_DISABLE_MODE_BLEND if (_clipStart != _clipStop && blendingStyle > BLEND_STYLE_FADE) { - bool invert = _clipStart > _clipStop; + bool invert = _clipStart > _clipStop; // ineverted start & stop int start = invert ? _clipStop : _clipStart; int stop = invert ? _clipStart : _clipStop; if (blendingStyle == BLEND_STYLE_FAIRY_DUST) { @@ -727,12 +744,13 @@ bool IRAM_ATTR Segment::isPixelClipped(int i) { if (len < 2) return false; unsigned shuffled = hashInt(i) % len; unsigned pos = (shuffled * 0xFFFFU) / len; - return progress() <= pos; + return (progress() <= pos) ^ _modeBlend; } const bool iInside = (i >= start && i < stop); - if (!invert && iInside) return _modeBlend; - if ( invert && !iInside) return _modeBlend; - return !_modeBlend; + //if (!invert && iInside) return _modeBlend; + //if ( invert && !iInside) return _modeBlend; + //return !_modeBlend; + return !iInside ^ invert ^ _modeBlend; // thanks @willmmiles (https://github.com/Aircoookie/WLED/pull/3877#discussion_r1554633876) } #endif return false; @@ -1220,7 +1238,7 @@ uint32_t Segment::color_from_palette(uint16_t i, bool mapping, bool wrap, uint8_ uint32_t color = gamma32(currentColor(mcol)); // default palette or no RGB support on segment - if ((palette == 0 && mcol < NUM_COLORS) || !_isRGB) return (pbri == 255) ? color : color_fade(color, pbri, true); + if ((currentPalette() == 0 && mcol < NUM_COLORS) || !_isRGB) return (pbri == 255) ? color : color_fade(color, pbri, true); unsigned paletteIndex = i; if (mapping && virtualLength() > 1) paletteIndex = (i*255)/(virtualLength() -1); @@ -1313,6 +1331,7 @@ void WS2812FX::service() { now = nowUp + timebase; if (nowUp - _lastShow < MIN_SHOW_DELAY || _suspend) return; bool doShow = false; + int pal = -1; // optimise palette loading _isServicing = true; _segment_index = 0; @@ -1339,7 +1358,8 @@ void WS2812FX::service() { _colors_t[0] = gamma32(seg.currentColor(0)); _colors_t[1] = gamma32(seg.currentColor(1)); _colors_t[2] = gamma32(seg.currentColor(2)); - seg.setCurrentPalette(); // load actual palette + if (seg.currentPalette() != pal) seg.setCurrentPalette(); // load actual palette + pal = seg.currentPalette(); // when correctWB is true we need to correct/adjust RGB value according to desired CCT value, but it will also affect actual WW/CW ratio // when cctFromRgb is true we implicitly calculate WW and CW from RGB values if (cctFromRgb) BusManager::setSegmentCCT(-1); @@ -1350,10 +1370,10 @@ void WS2812FX::service() { // The blending will largely depend on the effect behaviour since actual output (LEDs) may be // overwritten by later effect. To enable seamless blending for every effect, additional LED buffer // would need to be allocated for each effect and then blended together for each pixel. - [[maybe_unused]] uint8_t tmpMode = seg.currentMode(); // this will return old mode while in transition #ifndef WLED_DISABLE_MODE_BLEND + uint8_t tmpMode = seg.currentMode(); // this will return old mode while in transition Segment::setClippingRect(0, 0); // disable clipping (just in case) - if (seg.mode != tmpMode) { // could try seg.isInTransition() to allow color and palette to follow blending styles + if (seg.isInTransition()) { // set clipping rectangle // new mode is run inside clipping area and old mode outside clipping area unsigned p = seg.progress(); @@ -1404,11 +1424,12 @@ void WS2812FX::service() { } } delay = (*_mode[seg.mode])(); // run new/current mode - if (seg.mode != tmpMode) { // could try seg.isInTransition() to allow color and palette to follow blending styles + if (seg.isInTransition()) { Segment::tmpsegd_t _tmpSegData; Segment::modeBlend(true); // set semaphore seg.swapSegenv(_tmpSegData); // temporarily store new mode state (and swap it with transitional state) _virtualSegmentLength = seg.virtualLength(); // update SEGLEN (mapping may have changed) + seg.setCurrentPalette(); // load actual palette unsigned d2 = (*_mode[tmpMode])(); // run old mode seg.restoreSegenv(_tmpSegData); // restore mode state (will also update transitional state) delay = MIN(delay,d2); // use shortest delay From b9849da66e9d7c052e314a1d1eda0531a4939ffd Mon Sep 17 00:00:00 2001 From: Brandon502 <105077712+Brandon502@users.noreply.github.com> Date: Sat, 8 Jun 2024 13:16:56 -0400 Subject: [PATCH 010/287] Added Cube Mapping tool --- tools/AutoCubeMap.xlsx | Bin 0 -> 80009 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tools/AutoCubeMap.xlsx diff --git a/tools/AutoCubeMap.xlsx b/tools/AutoCubeMap.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..b3f5cee2ad32ae887d6cc62d6667a05c61a8ac35 GIT binary patch literal 80009 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 literal 0 HcmV?d00001 From bee75a450827ad571bc59241386b425a153981d1 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Sun, 23 Jun 2024 14:14:26 +0200 Subject: [PATCH 011/287] Hide 2D blending styles on non-2D set-up --- wled00/data/index.htm | 16 ++++++++-------- wled00/data/index.js | 2 ++ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/wled00/data/index.htm b/wled00/data/index.htm index e91bc10ba..d7ef3707a 100644 --- a/wled00/data/index.htm +++ b/wled00/data/index.htm @@ -275,14 +275,14 @@ - - - - - - - - + + + + + + + +

diff --git a/wled00/data/index.js b/wled00/data/index.js index 4cb707965..1f8a91616 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -677,8 +677,10 @@ function parseInfo(i) { isM = mw>0 && mh>0; if (!isM) { gId("filter2D").classList.add('hide'); + gId('bs').querySelectorAll('option[data-type="2D"]').forEach((o,i)=>{o.style.display='none';}); } else { gId("filter2D").classList.remove('hide'); + gId('bs').querySelectorAll('option[data-type="2D"]').forEach((o,i)=>{o.style.display='';}); } // if (i.noaudio) { // gId("filterVol").classList.add("hide"); From 0275bd1d45660ae1630792c835e65fd5a34c40ca Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Fri, 5 Jul 2024 21:23:59 +0200 Subject: [PATCH 012/287] On/Off blending respected --- wled00/FX_fcn.cpp | 37 ++++++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 3ebf0c448..af9b3218c 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -433,9 +433,17 @@ uint8_t IRAM_ATTR Segment::currentBri(bool useCct) { uint8_t IRAM_ATTR Segment::currentMode() { #ifndef WLED_DISABLE_MODE_BLEND unsigned prog = progress(); - if (prog < 0xFFFFU) return _t->_modeT; -#endif + if (prog == 0xFFFFU) return mode; + if (blendingStyle > BLEND_STYLE_FADE) { + // workaround for on/off transition to respect blending style + uint8_t modeT = (bri != briT) && bri ? FX_MODE_STATIC : _t->_modeT; // On/Off transition active (bri!=briT) and final bri>0 : old mode is STATIC + uint8_t modeS = (bri != briT) && !bri ? FX_MODE_STATIC : mode; // On/Off transition active (bri!=briT) and final bri==0 : new mode is STATIC + return _modeBlend ? modeT : modeS; + } + return _modeBlend ? _t->_modeT : mode; +#else return mode; +#endif } uint32_t IRAM_ATTR Segment::currentColor(uint8_t slot) { @@ -443,7 +451,12 @@ uint32_t IRAM_ATTR Segment::currentColor(uint8_t slot) { uint32_t prog = progress(); if (prog == 0xFFFFU) return colors[slot]; #ifndef WLED_DISABLE_MODE_BLEND - if (blendingStyle > BLEND_STYLE_FADE) return _modeBlend ? _t->_segT._colorT[slot] : colors[slot]; // not fade/blend transition, each effect uses its color + if (blendingStyle > BLEND_STYLE_FADE) { + // workaround for on/off transition to respect blending style + uint32_t colT = (bri != briT) && bri ? BLACK : _t->_segT._colorT[slot]; // On/Off transition active (bri!=briT) and final bri>0 : old color is BLACK + uint32_t colS = (bri != briT) && !bri ? BLACK : colors[slot]; // On/Off transition active (bri!=briT) and final bri==0 : new color is BLACK + return _modeBlend ? colT : colS; + } return color_blend(_t->_segT._colorT[slot], colors[slot], prog, true); #else return color_blend(_t->_colorT[slot], colors[slot], prog, true); @@ -559,6 +572,7 @@ bool Segment::setColor(uint8_t slot, uint32_t c) { //returns true if changed if (slot == 0 && c == BLACK) return false; // on/off segment cannot have primary color black if (slot == 1 && c != BLACK) return false; // on/off segment cannot have secondary color non black } + //DEBUG_PRINTF_P(PSTR("- Starting color transition: %d [0x%X]\n"), slot, c); startTransition(strip.getTransition()); // start transition prior to change colors[slot] = c; stateChanged = true; // send UDP/WS broadcast @@ -572,6 +586,7 @@ void Segment::setCCT(uint16_t k) { k = (k - 1900) >> 5; } if (cct == k) return; + //DEBUG_PRINTF_P(PSTR("- Starting CCT transition: %d\n"), k); startTransition(strip.getTransition()); // start transition prior to change cct = k; stateChanged = true; // send UDP/WS broadcast @@ -579,6 +594,7 @@ void Segment::setCCT(uint16_t k) { void Segment::setOpacity(uint8_t o) { if (opacity == o) return; + //DEBUG_PRINTF_P(PSTR("- Starting opacity transition: %d\n"), o); startTransition(strip.getTransition()); // start transition prior to change opacity = o; stateChanged = true; // send UDP/WS broadcast @@ -599,6 +615,7 @@ void Segment::setMode(uint8_t fx, bool loadDefaults) { // if we have a valid mode & is not reserved if (fx != mode) { #ifndef WLED_DISABLE_MODE_BLEND + //DEBUG_PRINTF_P(PSTR("- Starting effect transition: %d\n"), fx); startTransition(strip.getTransition()); // set effect transitions #endif mode = fx; @@ -630,6 +647,7 @@ void Segment::setPalette(uint8_t pal) { if (pal < 245 && pal > GRADIENT_PALETTE_COUNT+13) pal = 0; // built in palettes if (pal > 245 && (strip.customPalettes.size() == 0 || 255U-pal > strip.customPalettes.size()-1)) pal = 0; // custom palettes if (pal != palette) { + //DEBUG_PRINTF_P(PSTR("- Starting palette transition: %d\n"), pal); startTransition(strip.getTransition()); palette = pal; stateChanged = true; // send UDP/WS broadcast @@ -1373,7 +1391,6 @@ void WS2812FX::service() { // overwritten by later effect. To enable seamless blending for every effect, additional LED buffer // would need to be allocated for each effect and then blended together for each pixel. #ifndef WLED_DISABLE_MODE_BLEND - uint8_t tmpMode = seg.currentMode(); // this will return old mode while in transition Segment::setClippingRect(0, 0); // disable clipping (just in case) if (seg.isInTransition()) { // set clipping rectangle @@ -1425,14 +1442,20 @@ void WS2812FX::service() { break; } } - delay = (*_mode[seg.mode])(); // run new/current mode + delay = (*_mode[seg.currentMode()])(); // run new/current mode if (seg.isInTransition()) { Segment::tmpsegd_t _tmpSegData; Segment::modeBlend(true); // set semaphore seg.swapSegenv(_tmpSegData); // temporarily store new mode state (and swap it with transitional state) _virtualSegmentLength = seg.virtualLength(); // update SEGLEN (mapping may have changed) - seg.setCurrentPalette(); // load actual palette - unsigned d2 = (*_mode[tmpMode])(); // run old mode + _colors_t[0] = gamma32(seg.currentColor(0)); + _colors_t[1] = gamma32(seg.currentColor(1)); + _colors_t[2] = gamma32(seg.currentColor(2)); + if (seg.currentPalette() != pal) { + seg.setCurrentPalette(); // load actual palette + pal = seg.currentPalette(); + } + unsigned d2 = (*_mode[seg.currentMode()])(); // run old mode seg.restoreSegenv(_tmpSegData); // restore mode state (will also update transitional state) delay = MIN(delay,d2); // use shortest delay Segment::modeBlend(false); // unset semaphore From c03422ee3703dd604c909a58997442c0f2c532e4 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Tue, 30 Jul 2024 17:26:50 +0200 Subject: [PATCH 013/287] Push variants --- wled00/FX.h | 26 ++++++++++-------- wled00/FX_2Dfcn.cpp | 61 ++++++++++++++++++++++++++++++++++++++----- wled00/FX_fcn.cpp | 30 ++++++++++++++++++--- wled00/data/index.htm | 28 +++++++++++--------- 4 files changed, 112 insertions(+), 33 deletions(-) diff --git a/wled00/FX.h b/wled00/FX.h index 136f8e18b..3dc69e987 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -325,18 +325,22 @@ #define BLEND_STYLE_FAIRY_DUST 1 #define BLEND_STYLE_SWIPE_RIGHT 2 #define BLEND_STYLE_SWIPE_LEFT 3 -#define BLEND_STYLE_PINCH_OUT 4 -#define BLEND_STYLE_INSIDE_OUT 5 -#define BLEND_STYLE_SWIPE_UP 6 -#define BLEND_STYLE_SWIPE_DOWN 7 -#define BLEND_STYLE_OPEN_H 8 -#define BLEND_STYLE_OPEN_V 9 -#define BLEND_STYLE_PUSH_TL 10 -#define BLEND_STYLE_PUSH_TR 11 -#define BLEND_STYLE_PUSH_BR 12 -#define BLEND_STYLE_PUSH_BL 13 +#define BLEND_STYLE_PUSH_RIGHT 4 +#define BLEND_STYLE_PUSH_LEFT 5 +#define BLEND_STYLE_PINCH_OUT 6 +#define BLEND_STYLE_INSIDE_OUT 7 +#define BLEND_STYLE_SWIPE_UP 8 +#define BLEND_STYLE_SWIPE_DOWN 9 +#define BLEND_STYLE_OPEN_H 10 +#define BLEND_STYLE_OPEN_V 11 +#define BLEND_STYLE_PUSH_UP 12 +#define BLEND_STYLE_PUSH_DOWN 13 +#define BLEND_STYLE_PUSH_TL 14 +#define BLEND_STYLE_PUSH_TR 15 +#define BLEND_STYLE_PUSH_BR 16 +#define BLEND_STYLE_PUSH_BL 17 -#define BLEND_STYLE_COUNT 14 +#define BLEND_STYLE_COUNT 18 typedef enum mapping1D2D { diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index a62aa330e..846c78675 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -202,15 +202,39 @@ bool IRAM_ATTR Segment::isPixelXYClipped(int x, int y) { void IRAM_ATTR Segment::setPixelColorXY(int x, int y, uint32_t col) { if (!isActive()) return; // not active - if (x >= virtualWidth() || y >= virtualHeight() || x < 0 || y < 0 || isPixelXYClipped(x,y)) return; // if pixel would fall out of virtual segment just exit + + int vW = virtualWidth(); + int vH = virtualHeight(); + +#ifndef WLED_DISABLE_MODE_BLEND + if (!_modeBlend && + (blendingStyle == BLEND_STYLE_PUSH_RIGHT || + blendingStyle == BLEND_STYLE_PUSH_LEFT || + blendingStyle == BLEND_STYLE_PUSH_UP || + blendingStyle == BLEND_STYLE_PUSH_DOWN || + blendingStyle == BLEND_STYLE_PUSH_TL || + blendingStyle == BLEND_STYLE_PUSH_TR || + blendingStyle == BLEND_STYLE_PUSH_BR || + blendingStyle == BLEND_STYLE_PUSH_BL)) { + unsigned prog = 0xFFFF - progress(); + unsigned dX = (blendingStyle == BLEND_STYLE_PUSH_UP || blendingStyle == BLEND_STYLE_PUSH_DOWN) ? 0 : prog * vW / 0xFFFF; + unsigned dY = (blendingStyle == BLEND_STYLE_PUSH_LEFT || blendingStyle == BLEND_STYLE_PUSH_RIGHT) ? 0 : prog * vH / 0xFFFF; + if (blendingStyle == BLEND_STYLE_PUSH_LEFT || blendingStyle == BLEND_STYLE_PUSH_TL || blendingStyle == BLEND_STYLE_PUSH_BL) x -= dX; + else x += dX; + if (blendingStyle == BLEND_STYLE_PUSH_DOWN || blendingStyle == BLEND_STYLE_PUSH_TL || blendingStyle == BLEND_STYLE_PUSH_TR) y -= dY; + else y += dY; + } +#endif + + if (x >= vW || y >= vH || x < 0 || y < 0 || isPixelXYClipped(x,y)) return; // if pixel would fall out of virtual segment just exit uint8_t _bri_t = currentBri(); if (_bri_t < 255) { col = color_fade(col, _bri_t); } - if (reverse ) x = virtualWidth() - x - 1; - if (reverse_y) y = virtualHeight() - y - 1; + if (reverse ) x = vW - x - 1; + if (reverse_y) y = vH - y - 1; if (transpose) { unsigned t = x; x = y; y = t; } // swap X & Y if segment transposed x *= groupLength(); // expand to physical pixels @@ -294,9 +318,34 @@ void Segment::setPixelColorXY(float x, float y, uint32_t col, bool aa) // returns RGBW values of pixel uint32_t IRAM_ATTR Segment::getPixelColorXY(int x, int y) { if (!isActive()) return 0; // not active - if (x >= virtualWidth() || y >= virtualHeight() || x<0 || y<0 || isPixelXYClipped(x,y)) return 0; // if pixel would fall out of virtual segment just exit - if (reverse ) x = virtualWidth() - x - 1; - if (reverse_y) y = virtualHeight() - y - 1; + + int vW = virtualWidth(); + int vH = virtualHeight(); + +#ifndef WLED_DISABLE_MODE_BLEND + if (!_modeBlend && + (blendingStyle == BLEND_STYLE_PUSH_RIGHT || + blendingStyle == BLEND_STYLE_PUSH_LEFT || + blendingStyle == BLEND_STYLE_PUSH_UP || + blendingStyle == BLEND_STYLE_PUSH_DOWN || + blendingStyle == BLEND_STYLE_PUSH_TL || + blendingStyle == BLEND_STYLE_PUSH_TR || + blendingStyle == BLEND_STYLE_PUSH_BR || + blendingStyle == BLEND_STYLE_PUSH_BL)) { + unsigned prog = 0xFFFF - progress(); + unsigned dX = (blendingStyle == BLEND_STYLE_PUSH_UP || blendingStyle == BLEND_STYLE_PUSH_DOWN) ? 0 : prog * vW / 0xFFFF; + unsigned dY = (blendingStyle == BLEND_STYLE_PUSH_LEFT || blendingStyle == BLEND_STYLE_PUSH_RIGHT) ? 0 : prog * vH / 0xFFFF; + if (blendingStyle == BLEND_STYLE_PUSH_LEFT || blendingStyle == BLEND_STYLE_PUSH_TL || blendingStyle == BLEND_STYLE_PUSH_BL) x -= dX; + else x += dX; + if (blendingStyle == BLEND_STYLE_PUSH_DOWN || blendingStyle == BLEND_STYLE_PUSH_TL || blendingStyle == BLEND_STYLE_PUSH_TR) y -= dY; + else y += dY; + } +#endif + + if (x >= vW || y >= vH || x<0 || y<0 || isPixelXYClipped(x,y)) return 0; // if pixel would fall out of virtual segment just exit + + if (reverse ) x = vW - x - 1; + if (reverse_y) y = vH - y - 1; if (transpose) { unsigned t = x; x = y; y = t; } // swap X & Y if segment transposed x *= groupLength(); // expand to physical pixels y *= groupLength(); // expand to physical pixels diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 4af961764..583496de0 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -782,7 +782,8 @@ void IRAM_ATTR Segment::setPixelColor(int i, uint32_t col) #endif i &= 0xFFFF; - if (i >= virtualLength() || i<0) return; // if pixel would fall out of segment just exit + int vL = virtualLength(); + if (i >= vL || i < 0) return; // if pixel would fall out of segment just exit #ifndef WLED_DISABLE_2D if (is2D()) { @@ -890,7 +891,16 @@ void IRAM_ATTR Segment::setPixelColor(int i, uint32_t col) } #endif - if (isPixelClipped(i)) return; // handle clipping on 1D +#ifndef WLED_DISABLE_MODE_BLEND + if (!_modeBlend && (blendingStyle == BLEND_STYLE_PUSH_RIGHT || blendingStyle == BLEND_STYLE_PUSH_LEFT)) { + unsigned prog = 0xFFFF - progress(); + unsigned dI = prog * vL / 0xFFFF; + if (blendingStyle == BLEND_STYLE_PUSH_RIGHT) i -= dI; + else i += dI; + } +#endif + + if (i >= vL || i < 0 || isPixelClipped(i)) return; // handle clipping on 1D unsigned len = length(); uint8_t _bri_t = currentBri(); @@ -978,6 +988,9 @@ uint32_t IRAM_ATTR Segment::getPixelColor(int i) #endif i &= 0xFFFF; + int vL = virtualLength(); + if (i >= vL || i < 0) return 0; + #ifndef WLED_DISABLE_2D if (is2D()) { unsigned vH = virtualHeight(); // segment height in logical pixels @@ -1029,9 +1042,18 @@ uint32_t IRAM_ATTR Segment::getPixelColor(int i) } #endif - if (isPixelClipped(i)) return 0; // handle clipping on 1D +#ifndef WLED_DISABLE_MODE_BLEND + if (!_modeBlend && (blendingStyle == BLEND_STYLE_PUSH_RIGHT || blendingStyle == BLEND_STYLE_PUSH_LEFT)) { + unsigned prog = 0xFFFF - progress(); + unsigned dI = prog * vL / 0xFFFF; + if (blendingStyle == BLEND_STYLE_PUSH_RIGHT) i -= dI; + else i += dI; + } +#endif - if (reverse) i = virtualLength() - i - 1; + if (i >= vL || i < 0 || isPixelClipped(i)) return 0; // handle clipping on 1D + + if (reverse) i = vL - i - 1; i *= groupLength(); i += start; /* offset/phase */ diff --git a/wled00/data/index.htm b/wled00/data/index.htm index d7ef3707a..df867d3a5 100644 --- a/wled00/data/index.htm +++ b/wled00/data/index.htm @@ -271,18 +271,22 @@

From 365c1987ed8f5f8070081c2619d1b784ff354a7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Kristan?= Date: Thu, 1 Aug 2024 10:24:40 +0200 Subject: [PATCH 014/287] Missing clipping fix - small speed optimisations --- wled00/FX.h | 6 +++--- wled00/FX_2Dfcn.cpp | 22 ++++++++++++---------- wled00/FX_fcn.cpp | 35 ++++++++++++++++++++--------------- 3 files changed, 35 insertions(+), 28 deletions(-) diff --git a/wled00/FX.h b/wled00/FX.h index 3dc69e987..3abc81166 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -351,7 +351,7 @@ typedef enum mapping1D2D { M12_sPinwheel = 4 } mapping1D2D_t; -// segment, 80 bytes +// segment, 68 bytes typedef struct Segment { public: uint16_t start; // start index / start X coordinate 2D (left) @@ -639,7 +639,7 @@ typedef struct Segment { uint16_t virtualHeight(void) const; // segment height in virtual pixels (accounts for groupping and spacing) uint16_t nrOfVStrips(void) const; // returns number of virtual vertical strips in 2D matrix (used to expand 1D effects into 2D) #ifndef WLED_DISABLE_2D - uint16_t XY(uint16_t x, uint16_t y); // support function to get relative index within segment + uint16_t XY(int x, int y); // support function to get relative index within segment 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)); } @@ -678,7 +678,7 @@ typedef struct Segment { inline void blur2d(fract8 blur_amount) { blur(blur_amount); } inline void fill_solid(CRGB c) { fill(RGBW32(c.r,c.g,c.b,0)); } #else - inline uint16_t XY(uint16_t x, uint16_t y) { return x; } + inline uint16_t XY(int x, int y) { return x; } inline void setPixelColorXY(int x, int y, uint32_t c) { setPixelColor(x, c); } 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)); } diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index 846c78675..493cb8963 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -161,8 +161,7 @@ void WS2812FX::setUpMatrix() { #ifndef WLED_DISABLE_2D // XY(x,y) - gets pixel index within current segment (often used to reference leds[] array element) -uint16_t IRAM_ATTR Segment::XY(uint16_t x, uint16_t y) -{ +uint16_t IRAM_ATTR Segment::XY(int x, int y) { unsigned width = virtualWidth(); // segment width in logical pixels (can be 0 if segment is inactive) unsigned height = virtualHeight(); // segment height in logical pixels (is always >= 1) return isActive() ? (x%width) + (y%height) * width : 0; @@ -207,7 +206,7 @@ void IRAM_ATTR Segment::setPixelColorXY(int x, int y, uint32_t col) int vH = virtualHeight(); #ifndef WLED_DISABLE_MODE_BLEND - if (!_modeBlend && + if (isInTransition() && !_modeBlend && (blendingStyle == BLEND_STYLE_PUSH_RIGHT || blendingStyle == BLEND_STYLE_PUSH_LEFT || blendingStyle == BLEND_STYLE_PUSH_UP || @@ -239,13 +238,16 @@ void IRAM_ATTR Segment::setPixelColorXY(int x, int y, uint32_t col) x *= groupLength(); // expand to physical pixels y *= groupLength(); // expand to physical pixels - if (x >= width() || y >= height()) return; // if pixel would fall out of segment just exit + + int W = width(); + int H = height(); + if (x >= W || y >= H) return; // if pixel would fall out of segment just exit uint32_t tmpCol = col; for (int j = 0; j < grouping; j++) { // groupping vertically for (int g = 0; g < grouping; g++) { // groupping horizontally unsigned xX = (x+g), yY = (y+j); - if (xX >= width() || yY >= height()) continue; // we have reached one dimension's end + if (xX >= W || yY >= H) continue; // we have reached one dimension's end #ifndef WLED_DISABLE_MODE_BLEND // if blending modes, blend with underlying pixel @@ -255,15 +257,15 @@ void IRAM_ATTR Segment::setPixelColorXY(int x, int y, uint32_t col) strip.setPixelColorXY(start + xX, startY + yY, tmpCol); if (mirror) { //set the corresponding horizontally mirrored pixel - if (transpose) strip.setPixelColorXY(start + xX, startY + height() - yY - 1, tmpCol); - else strip.setPixelColorXY(start + width() - xX - 1, startY + yY, tmpCol); + if (transpose) strip.setPixelColorXY(start + xX, startY + H - yY - 1, tmpCol); + else strip.setPixelColorXY(start + W - xX - 1, startY + yY, tmpCol); } if (mirror_y) { //set the corresponding vertically mirrored pixel - if (transpose) strip.setPixelColorXY(start + width() - xX - 1, startY + yY, tmpCol); - else strip.setPixelColorXY(start + xX, startY + height() - yY - 1, tmpCol); + if (transpose) strip.setPixelColorXY(start + W - xX - 1, startY + yY, tmpCol); + else strip.setPixelColorXY(start + xX, startY + H - yY - 1, tmpCol); } if (mirror_y && mirror) { //set the corresponding vertically AND horizontally mirrored pixel - strip.setPixelColorXY(width() - xX - 1, height() - yY - 1, tmpCol); + strip.setPixelColorXY(W - xX - 1, H - yY - 1, tmpCol); } } } diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 583496de0..dbbd24b7d 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -754,7 +754,7 @@ uint16_t IRAM_ATTR Segment::virtualLength() const { bool IRAM_ATTR Segment::isPixelClipped(int i) { #ifndef WLED_DISABLE_MODE_BLEND if (_clipStart != _clipStop && blendingStyle > BLEND_STYLE_FADE) { - bool invert = _clipStart > _clipStop; // ineverted start & stop + bool invert = _clipStart > _clipStop; // ineverted start & stop int start = invert ? _clipStop : _clipStart; int stop = invert ? _clipStart : _clipStop; if (blendingStyle == BLEND_STYLE_FAIRY_DUST) { @@ -892,7 +892,8 @@ void IRAM_ATTR Segment::setPixelColor(int i, uint32_t col) #endif #ifndef WLED_DISABLE_MODE_BLEND - if (!_modeBlend && (blendingStyle == BLEND_STYLE_PUSH_RIGHT || blendingStyle == BLEND_STYLE_PUSH_LEFT)) { + // if we blend using "push" style we need to "shift" new mode to left or right + if (isInTransition() && !_modeBlend && (blendingStyle == BLEND_STYLE_PUSH_RIGHT || blendingStyle == BLEND_STYLE_PUSH_LEFT)) { unsigned prog = 0xFFFF - progress(); unsigned dI = prog * vL / 0xFFFF; if (blendingStyle == BLEND_STYLE_PUSH_RIGHT) i -= dI; @@ -1043,7 +1044,7 @@ uint32_t IRAM_ATTR Segment::getPixelColor(int i) #endif #ifndef WLED_DISABLE_MODE_BLEND - if (!_modeBlend && (blendingStyle == BLEND_STYLE_PUSH_RIGHT || blendingStyle == BLEND_STYLE_PUSH_LEFT)) { + if (isInTransition() && !_modeBlend && (blendingStyle == BLEND_STYLE_PUSH_RIGHT || blendingStyle == BLEND_STYLE_PUSH_LEFT)) { unsigned prog = 0xFFFF - progress(); unsigned dI = prog * vL / 0xFFFF; if (blendingStyle == BLEND_STYLE_PUSH_RIGHT) i -= dI; @@ -1425,43 +1426,47 @@ void WS2812FX::service() { unsigned dw = p * w / 0xFFFFU + 1; unsigned dh = p * h / 0xFFFFU + 1; switch (blendingStyle) { - case BLEND_STYLE_FAIRY_DUST: // fairy dust (must set entire segment, see isPixelXYClipped()) + case BLEND_STYLE_FAIRY_DUST: // fairy dust (must set entire segment, see isPixelXYClipped()) Segment::setClippingRect(0, w, 0, h); break; case BLEND_STYLE_SWIPE_RIGHT: // left-to-right + case BLEND_STYLE_PUSH_RIGHT: // left-to-right Segment::setClippingRect(0, dw, 0, h); break; - case BLEND_STYLE_SWIPE_LEFT: // right-to-left + case BLEND_STYLE_SWIPE_LEFT: // right-to-left + case BLEND_STYLE_PUSH_LEFT: // right-to-left Segment::setClippingRect(w - dw, w, 0, h); break; - case BLEND_STYLE_PINCH_OUT: // corners + case BLEND_STYLE_PINCH_OUT: // corners Segment::setClippingRect((w + dw)/2, (w - dw)/2, (h + dh)/2, (h - dh)/2); // inverted!! break; - case BLEND_STYLE_INSIDE_OUT: // outward + case BLEND_STYLE_INSIDE_OUT: // outward Segment::setClippingRect((w - dw)/2, (w + dw)/2, (h - dh)/2, (h + dh)/2); break; - case BLEND_STYLE_SWIPE_DOWN: // top-to-bottom (2D) + case BLEND_STYLE_SWIPE_DOWN: // top-to-bottom (2D) + case BLEND_STYLE_PUSH_DOWN: // top-to-bottom (2D) Segment::setClippingRect(0, w, 0, dh); break; - case BLEND_STYLE_SWIPE_UP: // bottom-to-top (2D) + case BLEND_STYLE_SWIPE_UP: // bottom-to-top (2D) + case BLEND_STYLE_PUSH_UP: // bottom-to-top (2D) Segment::setClippingRect(0, w, h - dh, h); break; - case BLEND_STYLE_OPEN_H: // horizontal-outward (2D) same look as INSIDE_OUT on 1D + case BLEND_STYLE_OPEN_H: // horizontal-outward (2D) same look as INSIDE_OUT on 1D Segment::setClippingRect((w - dw)/2, (w + dw)/2, 0, h); break; - case BLEND_STYLE_OPEN_V: // vertical-outward (2D) + case BLEND_STYLE_OPEN_V: // vertical-outward (2D) Segment::setClippingRect(0, w, (h - dh)/2, (h + dh)/2); break; - case BLEND_STYLE_PUSH_TL: // TL-to-BR (2D) + case BLEND_STYLE_PUSH_TL: // TL-to-BR (2D) Segment::setClippingRect(0, dw, 0, dh); break; - case BLEND_STYLE_PUSH_TR: // TR-to-BL (2D) + case BLEND_STYLE_PUSH_TR: // TR-to-BL (2D) Segment::setClippingRect(w - dw, w, 0, dh); break; - case BLEND_STYLE_PUSH_BR: // BR-to-TL (2D) + case BLEND_STYLE_PUSH_BR: // BR-to-TL (2D) Segment::setClippingRect(w - dw, w, h - dh, h); break; - case BLEND_STYLE_PUSH_BL: // BL-to-TR (2D) + case BLEND_STYLE_PUSH_BL: // BL-to-TR (2D) Segment::setClippingRect(0, dw, h - dh, h); break; } From 77723b615f5c482a63053c7a40705b073f072681 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Thu, 8 Aug 2024 21:10:27 +0200 Subject: [PATCH 015/287] Fix compiler warning --- wled00/FX_2Dfcn.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index 36d2038b9..7aec73cad 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -246,7 +246,7 @@ void IRAM_ATTR Segment::setPixelColorXY(int x, int y, uint32_t col) uint32_t tmpCol = col; for (int j = 0; j < grouping; j++) { // groupping vertically for (int g = 0; g < grouping; g++) { // groupping horizontally - unsigned xX = (x+g), yY = (y+j); + int xX = (x+g), yY = (y+j); if (xX >= W || yY >= H) continue; // we have reached one dimension's end #ifndef WLED_DISABLE_MODE_BLEND From ebd8a10cefdfa83f289961de8d3d6ef5fa6e6da6 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Tue, 3 Sep 2024 17:20:16 +0200 Subject: [PATCH 016/287] Prevent styles on 1px segments --- wled00/FX_fcn.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index a5e007e89..f825cecd3 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1428,6 +1428,8 @@ void WS2812FX::service() { unsigned h = seg.virtualHeight(); unsigned dw = p * w / 0xFFFFU + 1; unsigned dh = p * h / 0xFFFFU + 1; + unsigned orgBS = blendingStyle; + if (w*h == 1) blendingStyle = BLEND_STYLE_FADE; // disable belending for single pixel segments (use fade instead) switch (blendingStyle) { case BLEND_STYLE_FAIRY_DUST: // fairy dust (must set entire segment, see isPixelXYClipped()) Segment::setClippingRect(0, w, 0, h); @@ -1473,9 +1475,8 @@ void WS2812FX::service() { Segment::setClippingRect(0, dw, h - dh, h); break; } - } - delay = (*_mode[seg.currentMode()])(); // run new/current mode - if (seg.isInTransition()) { + delay = (*_mode[seg.currentMode()])(); // run new/current mode + // now run old/previous mode Segment::tmpsegd_t _tmpSegData; Segment::modeBlend(true); // set semaphore seg.swapSegenv(_tmpSegData); // temporarily store new mode state (and swap it with transitional state) @@ -1491,10 +1492,10 @@ void WS2812FX::service() { seg.restoreSegenv(_tmpSegData); // restore mode state (will also update transitional state) delay = MIN(delay,d2); // use shortest delay Segment::modeBlend(false); // unset semaphore - } -#else - delay = (*_mode[seg.mode])(); // run effect mode + blendingStyle = orgBS; // restore blending style if it was modified for single pixel segment + } else #endif + delay = (*_mode[seg.mode])(); // run effect mode (not in transition) seg.call++; if (seg.isInTransition() && delay > FRAMETIME) delay = FRAMETIME; // force faster updates during transition BusManager::setSegmentCCT(oldCCT); // restore old CCT for ABL adjustments From ef80abd88509856a0426735743ebc39ceda2952e Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Wed, 4 Sep 2024 11:38:03 +0200 Subject: [PATCH 017/287] 8266 compatibility builds for older chips (another attempt t o solve #3690 and #3685) some users have reported that releases after 0.14.0 are not working reliably. So we add a few "compat" for 8266 that try to reproduce the buildenv of 0.14.0 as much as possible. * platform and platform_packages from 0.14.0 * not using PIO_FRAMEWORK_ARDUINO_MMU_CACHE16_IRAM48 * due to smaller IRAM, we had to move some functions back from IRAM to normal flash (may cause slowdown) --- platformio.ini | 44 +++++++++++++++++++++++++++++++++++++++++++- wled00/FX_2Dfcn.cpp | 4 ++-- wled00/FX_fcn.cpp | 3 ++- wled00/const.h | 8 ++++++++ 4 files changed, 55 insertions(+), 4 deletions(-) diff --git a/platformio.ini b/platformio.ini index cbe13c0dd..d19f47764 100644 --- a/platformio.ini +++ b/platformio.ini @@ -11,7 +11,7 @@ # CI binaries ; default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, esp32dev, esp32_eth # ESP32 variant builds are temporarily excluded from CI due to toolchain issues on the GitHub Actions Linux environment -default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, nodemcuv2_160, esp8266_2m_160, esp01_1m_full_160, esp32dev, esp32_eth, esp32dev_audioreactive, lolin_s2_mini, esp32c3dev, esp32s3dev_8MB, esp32s3dev_8MB_PSRAM_opi +default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, nodemcuv2_compat, esp8266_2m_compat, esp01_1m_full_compat, nodemcuv2_160, esp8266_2m_160, esp01_1m_full_160, esp32dev, esp32_eth, esp32dev_audioreactive, lolin_s2_mini, esp32c3dev, esp32s3dev_8MB, esp32s3dev_8MB_PSRAM_opi # Release binaries ; default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, esp32dev, esp32_eth, lolin_s2_mini, esp32c3dev, esp32s3dev_8MB @@ -226,6 +226,27 @@ lib_deps = ESPAsyncUDP ${env.lib_deps} +;; compatibilty flags - same as 0.14.0 which seems to work better on some 8266 boards. Not using PIO_FRAMEWORK_ARDUINO_MMU_CACHE16_IRAM48 +build_flags_compat = + -DESP8266 + -DFP_IN_IROM + ;;-Wno-deprecated-declarations + -Wno-misleading-indentation + ;;-Wno-attributes ;; silence warnings about unknown attribute 'maybe_unused' in NeoPixelBus + -DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK22x_190703 + -DPIO_FRAMEWORK_ARDUINO_LWIP_HIGHER_BANDWIDTH + -DVTABLES_IN_FLASH + -DMIMETYPE_MINIMAL + -DWLED_SAVE_IRAM ;; needed to prevent linker error + +;; this platform version was used for WLED 0.14.0 +platform_compat = espressif8266@4.2.0 +platform_packages_compat = + platformio/toolchain-xtensa @ ~2.100300.220621 #2.40802.200502 + platformio/tool-esptool #@ ~1.413.0 + platformio/tool-esptoolpy #@ ~1.30000.0 + + [esp32] #platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.2.3/platform-espressif32-2.0.2.3.zip platform = espressif32@3.5.0 @@ -336,6 +357,13 @@ build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP8266 #-DWLED lib_deps = ${esp8266.lib_deps} monitor_filters = esp8266_exception_decoder +[env:nodemcuv2_compat] +extends = env:nodemcuv2 +;; using platform version and build options from WLED 0.14.0 +platform = ${esp8266.platform_compat} +platform_packages = ${esp8266.platform_packages_compat} +build_flags = ${common.build_flags} ${esp8266.build_flags_compat} -D WLED_RELEASE_NAME=ESP8266_compat #-DWLED_DISABLE_2D + [env:nodemcuv2_160] extends = env:nodemcuv2 board_build.f_cpu = 160000000L @@ -350,6 +378,13 @@ build_unflags = ${common.build_unflags} build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP02 lib_deps = ${esp8266.lib_deps} +[env:esp8266_2m_compat] +extends = env:esp8266_2m +;; using platform version and build options from WLED 0.14.0 +platform = ${esp8266.platform_compat} +platform_packages = ${esp8266.platform_packages_compat} +build_flags = ${common.build_flags} ${esp8266.build_flags_compat} -D WLED_RELEASE_NAME=ESP02_compat #-DWLED_DISABLE_2D + [env:esp8266_2m_160] extends = env:esp8266_2m board_build.f_cpu = 160000000L @@ -365,6 +400,13 @@ build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP01 -D WLED_D ; -D WLED_USE_UNREAL_MATH ;; may cause wrong sunset/sunrise times, but saves 7064 bytes FLASH and 975 bytes RAM lib_deps = ${esp8266.lib_deps} +[env:esp01_1m_full_compat] +extends = env:esp01_1m_full +;; using platform version and build options from WLED 0.14.0 +platform = ${esp8266.platform_compat} +platform_packages = ${esp8266.platform_packages_compat} +build_flags = ${common.build_flags} ${esp8266.build_flags_compat} -D WLED_RELEASE_NAME=ESP01_compat -D WLED_DISABLE_OTA #-DWLED_DISABLE_2D + [env:esp01_1m_full_160] extends = env:esp01_1m_full board_build.f_cpu = 160000000L diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index 5dc9e9ff2..918ce61e2 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -163,14 +163,14 @@ void WS2812FX::setUpMatrix() { #ifndef WLED_DISABLE_2D // XY(x,y) - gets pixel index within current segment (often used to reference leds[] array element) -uint16_t IRAM_ATTR Segment::XY(uint16_t x, uint16_t y) +uint16_t IRAM_ATTR_YN Segment::XY(uint16_t x, uint16_t y) { uint16_t width = virtualWidth(); // segment width in logical pixels (can be 0 if segment is inactive) uint16_t height = virtualHeight(); // segment height in logical pixels (is always >= 1) return isActive() ? (x%width) + (y%height) * width : 0; } -void IRAM_ATTR Segment::setPixelColorXY(int x, int y, uint32_t col) +void IRAM_ATTR_YN Segment::setPixelColorXY(int x, int y, uint32_t col) { if (!isActive()) return; // not active if (x >= virtualWidth() || y >= virtualHeight() || x<0 || y<0) return; // if pixel would fall out of virtual segment just exit diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 10847ef57..58e2fdfa5 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -145,6 +145,7 @@ Segment& Segment::operator= (Segment &&orig) noexcept { } bool Segment::allocateData(size_t len) { + if (len == 0) return false; // nothing to do if (data && _dataLen >= len) { // already allocated enough (reduce fragmentation) if (call == 0) memset(data, 0, len); // erase buffer if called during effect initialisation return true; @@ -659,7 +660,7 @@ uint16_t Segment::virtualLength() const { return vLength; } -void IRAM_ATTR Segment::setPixelColor(int i, uint32_t col) +void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col) { if (!isActive()) return; // not active #ifndef WLED_DISABLE_2D diff --git a/wled00/const.h b/wled00/const.h index 388b64c82..97bfcff1b 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -540,4 +540,12 @@ #define HW_PIN_MISOSPI MISO #endif +// IRAM_ATTR for 8266 with 32Kb IRAM causes error: section `.text1' will not fit in region `iram1_0_seg' +// this hack removes the IRAM flag for some 1D/2D functions - somewhat slower, but it solves problems with some older 8266 chips +#ifdef WLED_SAVE_IRAM + #define IRAM_ATTR_YN +#else + #define IRAM_ATTR_YN IRAM_ATTR +#endif + #endif From dc90a4ed42702e608b1f1482a541a4adbd1ae1ac Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Wed, 4 Sep 2024 21:17:21 +0200 Subject: [PATCH 018/287] Mirroring bugfix. --- wled00/FX_2Dfcn.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index 918ce61e2..1461a1a9e 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -214,7 +214,7 @@ void IRAM_ATTR_YN Segment::setPixelColorXY(int x, int y, uint32_t col) else strip.setPixelColorXY(start + xX, startY + height() - yY - 1, tmpCol); } if (mirror_y && mirror) { //set the corresponding vertically AND horizontally mirrored pixel - strip.setPixelColorXY(width() - xX - 1, height() - yY - 1, tmpCol); + strip.setPixelColorXY(start + width() - xX - 1, startY + height() - yY - 1, tmpCol); } } } From 058e66c7fcc8b8ea568c777f7574f1295d82d4e2 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Tue, 10 Sep 2024 20:32:40 +0200 Subject: [PATCH 019/287] Update CONTRIBUTING.md - adding a hint to avoid force-pushing --- CONTRIBUTING.md | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 168131160..8ab9bb9f0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -16,6 +16,20 @@ A good description helps us to review and understand your proposed changes. For Please make all PRs against the `0_15` branch. +### Updating your code +While the PR is open - and under review by maintainers - you may be asked to modify your PR source code. +You can simply update your own branch, and push changes in response to reviewer recommendations. +Github will pick up the changes so your PR stays up-to-date. + +> [!CAUTION] +> Do not use "force-push" while your PR is open! +> It has many subtle and unexpected consequences on our github reposistory. +> For example, we regularly lost review comments when the PR author force-pushes code changes. So, pretty please, do not force-push. + + +You kan find a collection of very usefull tips and tricks here: https://github.com/Aircoookie/WLED/wiki/How-to-properly-submit-a-PR + + ### Code style When in doubt, it is easiest to replicate the code style you find in the files you want to edit :) @@ -37,6 +51,11 @@ if (a == b) { } ``` +```cpp +if (a == b) doStuff(a); +``` + +Acceptable - however the first variant is usually easier to read: ```cpp if (a == b) { @@ -44,9 +63,6 @@ if (a == b) } ``` -```cpp -if (a == b) doStuff(a); -``` There should always be a space between a keyword and its condition and between the condition and brace. Within the condition, no space should be between the paranthesis and variables. From 2cfd2e1410f2ebed85df0e56fef658ee018f6266 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Tue, 10 Sep 2024 20:36:18 +0200 Subject: [PATCH 020/287] Update CONTRIBUTING.md - typo --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8ab9bb9f0..9b1affbc3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -27,7 +27,7 @@ Github will pick up the changes so your PR stays up-to-date. > For example, we regularly lost review comments when the PR author force-pushes code changes. So, pretty please, do not force-push. -You kan find a collection of very usefull tips and tricks here: https://github.com/Aircoookie/WLED/wiki/How-to-properly-submit-a-PR +You can find a collection of very useful tips and tricks here: https://github.com/Aircoookie/WLED/wiki/How-to-properly-submit-a-PR ### Code style From c3f472fbcb0d766f8c6cce1bf8841770b4b15bac Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Wed, 11 Sep 2024 21:41:42 +0200 Subject: [PATCH 021/287] some improvements to consider no real difference in FPS but code is faster. also 160bytes smaller, meaning it is actually faster --- wled00/FX_2Dfcn.cpp | 25 +++++++++--------- wled00/colors.cpp | 62 +++++++++++++++++++++++++++++++++++++------- wled00/fcn_declare.h | 2 ++ 3 files changed, 67 insertions(+), 22 deletions(-) diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index 26ec1d608..ae76379ed 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -173,11 +173,6 @@ void IRAM_ATTR_YN Segment::setPixelColorXY(int x, int y, uint32_t col) if (!isActive()) return; // not active if (x >= virtualWidth() || y >= virtualHeight() || x<0 || y<0) return; // if pixel would fall out of virtual segment just exit - uint8_t _bri_t = currentBri(); - if (_bri_t < 255) { - col = color_fade(col, _bri_t); - } - if (reverse ) x = virtualWidth() - x - 1; if (reverse_y) y = virtualHeight() - y - 1; if (transpose) { std::swap(x,y); } // swap X & Y if segment transposed @@ -189,7 +184,11 @@ void IRAM_ATTR_YN Segment::setPixelColorXY(int x, int y, uint32_t col) int H = height(); if (x >= W || y >= H) return; // if pixel would fall out of segment just exit - uint32_t tmpCol = col; + uint8_t _bri_t = currentBri(); + if (_bri_t < 255) { + col = color_fade(col, _bri_t); + } + for (int j = 0; j < grouping; j++) { // groupping vertically for (int g = 0; g < grouping; g++) { // groupping horizontally int xX = (x+g), yY = (y+j); @@ -197,21 +196,21 @@ void IRAM_ATTR_YN Segment::setPixelColorXY(int x, int y, uint32_t col) #ifndef WLED_DISABLE_MODE_BLEND // if blending modes, blend with underlying pixel - if (_modeBlend) tmpCol = color_blend(strip.getPixelColorXY(start + xX, startY + yY), col, 0xFFFFU - progress(), true); + if (_modeBlend) col = color_blend(strip.getPixelColorXY(start + xX, startY + yY), col, 0xFFFFU - progress(), true); #endif - strip.setPixelColorXY(start + xX, startY + yY, tmpCol); + strip.setPixelColorXY(start + xX, startY + yY, col); if (mirror) { //set the corresponding horizontally mirrored pixel - if (transpose) strip.setPixelColorXY(start + xX, startY + height() - yY - 1, tmpCol); - else strip.setPixelColorXY(start + width() - xX - 1, startY + yY, tmpCol); + if (transpose) strip.setPixelColorXY(start + xX, startY + height() - yY - 1, col); + else strip.setPixelColorXY(start + width() - xX - 1, startY + yY, col); } if (mirror_y) { //set the corresponding vertically mirrored pixel - if (transpose) strip.setPixelColorXY(start + width() - xX - 1, startY + yY, tmpCol); - else strip.setPixelColorXY(start + xX, startY + height() - yY - 1, tmpCol); + if (transpose) strip.setPixelColorXY(start + width() - xX - 1, startY + yY, col); + else strip.setPixelColorXY(start + xX, startY + height() - yY - 1, col); } if (mirror_y && mirror) { //set the corresponding vertically AND horizontally mirrored pixel - strip.setPixelColorXY(start + width() - xX - 1, startY + height() - yY - 1, tmpCol); + strip.setPixelColorXY(start + width() - xX - 1, startY + height() - yY - 1, col); } } } diff --git a/wled00/colors.cpp b/wled00/colors.cpp index ac1dee00a..791aad982 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -72,25 +72,69 @@ uint32_t color_fade(uint32_t c1, uint8_t amount, bool video) { if (c1 == BLACK || amount + video == 0) return BLACK; uint32_t scaledcolor; // color order is: W R G B from MSB to LSB - uint32_t r = R(c1); - uint32_t g = G(c1); - uint32_t b = B(c1); - uint32_t w = W(c1); uint32_t scale = amount; // 32bit for faster calculation if (video) { + uint32_t r = R(c1); + uint32_t g = G(c1); + uint32_t b = B(c1); + uint32_t w = W(c1); scaledcolor = (((r * scale) >> 8) + ((r && scale) ? 1 : 0)) << 16; scaledcolor |= (((g * scale) >> 8) + ((g && scale) ? 1 : 0)) << 8; scaledcolor |= ((b * scale) >> 8) + ((b && scale) ? 1 : 0); scaledcolor |= (((w * scale) >> 8) + ((w && scale) ? 1 : 0)) << 24; - } else { - scaledcolor = ((r * scale) >> 8) << 16; - scaledcolor |= ((g * scale) >> 8) << 8; - scaledcolor |= (b * scale) >> 8; - scaledcolor |= ((w * scale) >> 8) << 24; + } else { // according to compile explorer, this is 15% faster but cannot be used for video (its not faster if the assignments are seperated) + uint32_t r = (((c1&0x00FF0000) * scale) >> 8) & 0x00FF0000; + uint32_t g = (((c1&0x0000FF00) * scale) >> 8) & 0x0000FF00; + uint32_t b = ((c1&0x000000FF) * scale) >> 8; + uint32_t w = (((c1 & 0xFF000000) >> 8) * scale) & 0xFF000000; // Scale w and keep it in position + scaledcolor = r | g | b | w; } return scaledcolor; } +// 1:1 replacement of fastled function optimized for ESP, slightly faster, more accurate and uses less flash (~ -200bytes) +CRGB ColorFromPaletteWLED(const CRGBPalette16& pal, unsigned index, uint8_t brightness, TBlendType blendType) +{ + if ( blendType == LINEARBLEND_NOWRAP) { + //index = map8(index, 0, 239); + index = (index*240) >> 8; // Blend range is affected by lo4 blend of values, remap to avoid wrapping + } + unsigned hi4 = byte(index) >> 4; + unsigned lo4 = index & 0x0F; + unsigned hi4XsizeofCRGB = hi4 * sizeof(CRGB); + // We then add that to a base array pointer. + const CRGB* entry = (CRGB*)( (uint8_t*)(&(pal[0])) + hi4XsizeofCRGB); + unsigned red1 = entry->red; + unsigned green1 = entry->green; + unsigned blue1 = entry->blue; + if(blendType != NOBLEND) { + if(hi4 == 15) entry = &(pal[0]); + else ++entry; + unsigned red2 = entry->red; + unsigned green2 = entry->green; + unsigned blue2 = entry->blue; + unsigned f2 = (lo4 << 4)+1; // +1 so we scale by 256 as a max value, then result can just be shifted by 8 + unsigned f1 = (257 - f2); // f2 is 1 minimum, so this is 256 max + red1 *= f1; + green1 *= f1; + blue1 *= f1; + red2 *= f2; + green2 *= f2; + blue2 *= f2; + red1 = (red1 + red2) >> 8; + green1 = (green1 + green2) >> 8; + blue1 = (blue1 + blue2) >> 8; + } + if( brightness != 255) { // note: zero checking could be done to return black but that is hardly ever used so it is omitted + uint32_t scale = brightness; + scale++; // adjust for rounding (bitshift) + red1 = (red1 * scale) >> 8; + green1 = (green1 * scale) >> 8; + blue1 = (blue1 * scale) >> 8; + } + return CRGB((uint8_t)red1, (uint8_t)green1, (uint8_t)blue1); +} + void setRandomColor(byte* rgb) { lastRandomIndex = get_random_wheel_index(lastRandomIndex); diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index f8399b1ad..2f9bc44d0 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -66,6 +66,7 @@ typedef struct WiFiConfig { } wifi_config; //colors.cpp +#define ColorFromPalette ColorFromPaletteWLED // override fastled version // similar to NeoPixelBus NeoGammaTableMethod but allows dynamic changes (superseded by NPB::NeoGammaDynamicTableMethod) class NeoGammaWLEDMethod { public: @@ -81,6 +82,7 @@ class NeoGammaWLEDMethod { [[gnu::hot]] uint32_t color_blend(uint32_t,uint32_t,uint16_t,bool b16=false); [[gnu::hot]] uint32_t color_add(uint32_t,uint32_t, bool fast=false); [[gnu::hot]] uint32_t color_fade(uint32_t c1, uint8_t amount, bool video=false); +CRGB ColorFromPaletteWLED(const CRGBPalette16 &pal, unsigned index, uint8_t brightness = (uint8_t)255U, TBlendType blendType = LINEARBLEND); CRGBPalette16 generateHarmonicRandomPalette(CRGBPalette16 &basepalette); CRGBPalette16 generateRandomPalette(); inline uint32_t colorFromRgbw(byte* rgbw) { return uint32_t((byte(rgbw[3]) << 24) | (byte(rgbw[0]) << 16) | (byte(rgbw[1]) << 8) | (byte(rgbw[2]))); } From 934176818f32ffa0ffe359192dbf7d769c3e740f Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Thu, 12 Sep 2024 06:43:20 +0200 Subject: [PATCH 022/287] more improvements to color_scale() now even faster. tested and working, also tested video --- wled00/colors.cpp | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/wled00/colors.cpp b/wled00/colors.cpp index 791aad982..c3ac0cb61 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -70,25 +70,22 @@ uint32_t color_add(uint32_t c1, uint32_t c2, bool fast) uint32_t color_fade(uint32_t c1, uint8_t amount, bool video) { - if (c1 == BLACK || amount + video == 0) return BLACK; + if (c1 == BLACK || amount == 0) return BLACK; + else if (amount == 255) return c1; + video = true; uint32_t scaledcolor; // color order is: W R G B from MSB to LSB uint32_t scale = amount; // 32bit for faster calculation - if (video) { - uint32_t r = R(c1); - uint32_t g = G(c1); - uint32_t b = B(c1); - uint32_t w = W(c1); - scaledcolor = (((r * scale) >> 8) + ((r && scale) ? 1 : 0)) << 16; - scaledcolor |= (((g * scale) >> 8) + ((g && scale) ? 1 : 0)) << 8; - scaledcolor |= ((b * scale) >> 8) + ((b && scale) ? 1 : 0); - scaledcolor |= (((w * scale) >> 8) + ((w && scale) ? 1 : 0)) << 24; - } else { // according to compile explorer, this is 15% faster but cannot be used for video (its not faster if the assignments are seperated) - uint32_t r = (((c1&0x00FF0000) * scale) >> 8) & 0x00FF0000; - uint32_t g = (((c1&0x0000FF00) * scale) >> 8) & 0x0000FF00; - uint32_t b = ((c1&0x000000FF) * scale) >> 8; - uint32_t w = (((c1 & 0xFF000000) >> 8) * scale) & 0xFF000000; // Scale w and keep it in position - scaledcolor = r | g | b | w; + uint32_t addRemains = 0; + if (!video) amount++; // add one for correct scaling using bitshifts + else { // video scaling: make sure colors do not dim to zero if they started non-zero + addRemains = R(c1) ? 0x00010000 : 0; + addRemains |= G(c1) ? 0x00000100 : 0; + addRemains |= B(c1) ? 0x00000001 : 0; + addRemains |= W(c1) ? 0x01000000 : 0; } + uint32_t rb = (((c1 & 0x00FF00FF) * scale) >> 8) & 0x00FF00FF; // scale red and blue + uint32_t wg = (((c1 & 0xFF00FF00) >> 8) * scale) & 0xFF00FF00; // scale white and green + scaledcolor = (rb | wg) + addRemains; return scaledcolor; } From feac45fd0aed860235aef4e03fe91c5b49f95a89 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Thu, 12 Sep 2024 07:45:49 +0200 Subject: [PATCH 023/287] improvement in color_add its not faster but cleaner (and uses less flash) --- wled00/colors.cpp | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/wled00/colors.cpp b/wled00/colors.cpp index c3ac0cb61..960cef313 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -39,21 +39,17 @@ uint32_t color_add(uint32_t c1, uint32_t c2, bool fast) { if (c1 == BLACK) return c2; if (c2 == BLACK) return c1; - if (fast) { - uint8_t r = R(c1); - uint8_t g = G(c1); - uint8_t b = B(c1); - uint8_t w = W(c1); - r = qadd8(r, R(c2)); - g = qadd8(g, G(c2)); - b = qadd8(b, B(c2)); - w = qadd8(w, W(c2)); + uint32_t r = R(c1) + R(c2); + uint32_t g = G(c1) + G(c2); + uint32_t b = B(c1) + B(c2); + uint32_t w = W(c1) + W(c2); + if (fast) { + r = r > 255 ? 255 : r; + g = g > 255 ? 255 : g; + b = b > 255 ? 255 : b; + w = w > 255 ? 255 : w; return RGBW32(r,g,b,w); } else { - uint32_t r = R(c1) + R(c2); - uint32_t g = G(c1) + G(c2); - uint32_t b = B(c1) + B(c2); - uint32_t w = W(c1) + W(c2); unsigned max = r; if (g > max) max = g; if (b > max) max = b; @@ -72,7 +68,6 @@ uint32_t color_fade(uint32_t c1, uint8_t amount, bool video) { if (c1 == BLACK || amount == 0) return BLACK; else if (amount == 255) return c1; - video = true; uint32_t scaledcolor; // color order is: W R G B from MSB to LSB uint32_t scale = amount; // 32bit for faster calculation uint32_t addRemains = 0; From 992d11be105ac00380874a11f6ba33e9acfc24e1 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Thu, 12 Sep 2024 08:28:30 +0200 Subject: [PATCH 024/287] Improvements in get/set PixelColor() -calculations for virtual strips are done on each call, which is unnecessary. moved them into the if statement. --- wled00/FX_fcn.cpp | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index d3521c90c..4364b9f43 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -705,11 +705,16 @@ void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col) { if (!isActive()) return; // not active #ifndef WLED_DISABLE_2D - int vStrip = i>>16; // hack to allow running on virtual strips (2D segment columns/rows) + int vStrip; #endif - i &= 0xFFFF; - - if (i >= virtualLength() || i<0) return; // if pixel would fall out of segment just exit + if (i >= virtualLength() || i<0) // pixel would fall out of segment, check if this is a virtual strip NOTE: this is almost always false if not virtual strip, saves the calculation on 'standard' call + { + #ifndef WLED_DISABLE_2D + vStrip = i>>16; // hack to allow running on virtual strips (2D segment columns/rows) + #endif + i &= 0xFFFF; //truncate vstrip index + if (i >= virtualLength() || i<0) return; // if pixel would still fall out of segment just exit + } #ifndef WLED_DISABLE_2D if (is2D()) { @@ -900,8 +905,7 @@ uint32_t IRAM_ATTR_YN Segment::getPixelColor(int i) const if (!isActive()) return 0; // not active #ifndef WLED_DISABLE_2D int vStrip = i>>16; -#endif - i &= 0xFFFF; +#endif #ifndef WLED_DISABLE_2D if (is2D()) { @@ -912,7 +916,7 @@ uint32_t IRAM_ATTR_YN Segment::getPixelColor(int i) const return getPixelColorXY(i % vW, i / vW); break; case M12_pBar: - if (vStrip>0) return getPixelColorXY(vStrip - 1, vH - i -1); + if (vStrip>0) { i &= 0xFFFF; return getPixelColorXY(vStrip - 1, vH - i -1); } else return getPixelColorXY(0, vH - i -1); break; case M12_pArc: From b07658b46060c73c4bd94a9a4e4289d44639c173 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Thu, 12 Sep 2024 14:09:09 +0200 Subject: [PATCH 025/287] improved Segment::setPixelColorXY a tiny bit uses less flash so it should be faster (did not notice any FPS difference though) also cleaned code in ColorFromPaletteWLED (it is not faster, same amount of code) --- wled00/FX_2Dfcn.cpp | 30 ++++++++++++++---------------- wled00/colors.cpp | 33 +++++++++++---------------------- 2 files changed, 25 insertions(+), 38 deletions(-) diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index ae76379ed..01410b811 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -173,34 +173,30 @@ void IRAM_ATTR_YN Segment::setPixelColorXY(int x, int y, uint32_t col) if (!isActive()) return; // not active if (x >= virtualWidth() || y >= virtualHeight() || x<0 || y<0) return; // if pixel would fall out of virtual segment just exit - if (reverse ) x = virtualWidth() - x - 1; - if (reverse_y) y = virtualHeight() - y - 1; - if (transpose) { std::swap(x,y); } // swap X & Y if segment transposed - - x *= groupLength(); // expand to physical pixels - y *= groupLength(); // expand to physical pixels - - int W = width(); - int H = height(); - if (x >= W || y >= H) return; // if pixel would fall out of segment just exit - uint8_t _bri_t = currentBri(); if (_bri_t < 255) { col = color_fade(col, _bri_t); } + if (reverse ) x = virtualWidth() - x - 1; + if (reverse_y) y = virtualHeight() - y - 1; + if (transpose) { std::swap(x,y); } // swap X & Y if segment transposed + x *= groupLength(); // expand to physical pixels + y *= groupLength(); // expand to physical pixels + int W = width(); + int H = height(); + + int yY = y; for (int j = 0; j < grouping; j++) { // groupping vertically + if(yY >= H) continue; + int xX = x; for (int g = 0; g < grouping; g++) { // groupping horizontally - int xX = (x+g), yY = (y+j); - if (xX >= W || yY >= H) continue; // we have reached one dimension's end - + if (xX >= W) continue; // we have reached one dimension's end #ifndef WLED_DISABLE_MODE_BLEND // if blending modes, blend with underlying pixel if (_modeBlend) col = color_blend(strip.getPixelColorXY(start + xX, startY + yY), col, 0xFFFFU - progress(), true); #endif - strip.setPixelColorXY(start + xX, startY + yY, col); - if (mirror) { //set the corresponding horizontally mirrored pixel if (transpose) strip.setPixelColorXY(start + xX, startY + height() - yY - 1, col); else strip.setPixelColorXY(start + width() - xX - 1, startY + yY, col); @@ -212,7 +208,9 @@ void IRAM_ATTR_YN Segment::setPixelColorXY(int x, int y, uint32_t col) if (mirror_y && mirror) { //set the corresponding vertically AND horizontally mirrored pixel strip.setPixelColorXY(start + width() - xX - 1, startY + height() - yY - 1, col); } + xX++; } + yY++; } } diff --git a/wled00/colors.cpp b/wled00/colors.cpp index 960cef313..b6cb3ac51 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -92,34 +92,23 @@ CRGB ColorFromPaletteWLED(const CRGBPalette16& pal, unsigned index, uint8_t brig index = (index*240) >> 8; // Blend range is affected by lo4 blend of values, remap to avoid wrapping } unsigned hi4 = byte(index) >> 4; - unsigned lo4 = index & 0x0F; - unsigned hi4XsizeofCRGB = hi4 * sizeof(CRGB); // We then add that to a base array pointer. - const CRGB* entry = (CRGB*)( (uint8_t*)(&(pal[0])) + hi4XsizeofCRGB); - unsigned red1 = entry->red; - unsigned green1 = entry->green; - unsigned blue1 = entry->blue; + const CRGB* entry = (CRGB*)( (uint8_t*)(&(pal[0])) + (hi4 * sizeof(CRGB))); + unsigned red1 = entry->r; + unsigned green1 = entry->g; + unsigned blue1 = entry->b; if(blendType != NOBLEND) { if(hi4 == 15) entry = &(pal[0]); else ++entry; - unsigned red2 = entry->red; - unsigned green2 = entry->green; - unsigned blue2 = entry->blue; - unsigned f2 = (lo4 << 4)+1; // +1 so we scale by 256 as a max value, then result can just be shifted by 8 + // unsigned red2 = entry->red; + unsigned f2 = ((index & 0x0F) << 4) + 1; // +1 so we scale by 256 as a max value, then result can just be shifted by 8 unsigned f1 = (257 - f2); // f2 is 1 minimum, so this is 256 max - red1 *= f1; - green1 *= f1; - blue1 *= f1; - red2 *= f2; - green2 *= f2; - blue2 *= f2; - red1 = (red1 + red2) >> 8; - green1 = (green1 + green2) >> 8; - blue1 = (blue1 + blue2) >> 8; + red1 = (red1 * f1 + (unsigned)entry->r * f2) >> 8; + green1 = (green1 * f1 + (unsigned)entry->g * f2) >> 8; + blue1 = (green1 * f1 + (unsigned)entry->b * f2) >> 8; } - if( brightness != 255) { // note: zero checking could be done to return black but that is hardly ever used so it is omitted - uint32_t scale = brightness; - scale++; // adjust for rounding (bitshift) + if( brightness < 255) { // note: zero checking could be done to return black but that is hardly ever used so it is omitted + uint32_t scale = brightness + 1; // adjust for rounding (bitshift) red1 = (red1 * scale) >> 8; green1 = (green1 * scale) >> 8; blue1 = (blue1 * scale) >> 8; From 09428dcade03aa4544f85f81f2d8a160d144db9c Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Thu, 12 Sep 2024 16:34:55 +0200 Subject: [PATCH 026/287] inlined getMappedPixelIndex, improved color_add, bugfix in colorFromPalette inlining getMappedPixelIndex gets rid of function entry instructions (hopefully) so it should be faster. also added the 'multi color math' trick to color_add function (it will not make much difference but code shrinks by a few bytes) --- wled00/FX_fcn.cpp | 2 +- wled00/colors.cpp | 13 ++++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 4364b9f43..cddf1ece7 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1820,7 +1820,7 @@ bool WS2812FX::deserializeMap(uint8_t n) { return (customMappingSize > 0); } -uint16_t IRAM_ATTR WS2812FX::getMappedPixelIndex(uint16_t index) const { +__attribute__ ((always_inline)) inline uint16_t IRAM_ATTR WS2812FX::getMappedPixelIndex(uint16_t index) const { // convert logical address to physical if (index < customMappingSize && (realtimeMode == REALTIME_MODE_INACTIVE || realtimeRespectLedMaps)) index = customMappingTable[index]; diff --git a/wled00/colors.cpp b/wled00/colors.cpp index b6cb3ac51..1f1069655 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -39,10 +39,17 @@ uint32_t color_add(uint32_t c1, uint32_t c2, bool fast) { if (c1 == BLACK) return c2; if (c2 == BLACK) return c1; - uint32_t r = R(c1) + R(c2); + /*uint32_t r = R(c1) + R(c2); uint32_t g = G(c1) + G(c2); uint32_t b = B(c1) + B(c2); - uint32_t w = W(c1) + W(c2); + uint32_t w = W(c1) + W(c2);*/ + uint32_t rb = (c1 & 0x00FF00FF) + (c2 & 0x00FF00FF); + uint32_t r = rb >> 16; + uint32_t b = rb & 0xFFFF; + uint32_t wg = ((c1>>8) & 0x00FF00FF) + ((c2>>8) & 0x00FF00FF); + uint32_t w = wg >> 16; + uint32_t g = wg & 0xFFFF; + if (fast) { r = r > 255 ? 255 : r; g = g > 255 ? 255 : g; @@ -105,7 +112,7 @@ CRGB ColorFromPaletteWLED(const CRGBPalette16& pal, unsigned index, uint8_t brig unsigned f1 = (257 - f2); // f2 is 1 minimum, so this is 256 max red1 = (red1 * f1 + (unsigned)entry->r * f2) >> 8; green1 = (green1 * f1 + (unsigned)entry->g * f2) >> 8; - blue1 = (green1 * f1 + (unsigned)entry->b * f2) >> 8; + blue1 = (blue1 * f1 + (unsigned)entry->b * f2) >> 8; } if( brightness < 255) { // note: zero checking could be done to return black but that is hardly ever used so it is omitted uint32_t scale = brightness + 1; // adjust for rounding (bitshift) From ec938f254cf3fdecf0757a2dee4d9364973d4941 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Thu, 12 Sep 2024 21:25:08 +0200 Subject: [PATCH 027/287] removed old code --- wled00/colors.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/wled00/colors.cpp b/wled00/colors.cpp index 1f1069655..ce1d2f2f9 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -39,10 +39,6 @@ uint32_t color_add(uint32_t c1, uint32_t c2, bool fast) { if (c1 == BLACK) return c2; if (c2 == BLACK) return c1; - /*uint32_t r = R(c1) + R(c2); - uint32_t g = G(c1) + G(c2); - uint32_t b = B(c1) + B(c2); - uint32_t w = W(c1) + W(c2);*/ uint32_t rb = (c1 & 0x00FF00FF) + (c2 & 0x00FF00FF); uint32_t r = rb >> 16; uint32_t b = rb & 0xFFFF; From d45b4ad1340bccfeb9093b1ff460f1919721a277 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Fri, 13 Sep 2024 19:01:54 +0200 Subject: [PATCH 028/287] fixes and consistency --- wled00/colors.cpp | 8 ++++---- wled00/fcn_declare.h | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/wled00/colors.cpp b/wled00/colors.cpp index ce1d2f2f9..7747216f7 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -54,9 +54,9 @@ uint32_t color_add(uint32_t c1, uint32_t c2, bool fast) return RGBW32(r,g,b,w); } else { unsigned max = r; - if (g > max) max = g; - if (b > max) max = b; - if (w > max) max = w; + max = g > max ? g : max; + max = b > max ? b : max; + max = w > max ? w : max; if (max < 256) return RGBW32(r, g, b, w); else return RGBW32(r * 255 / max, g * 255 / max, b * 255 / max, w * 255 / max); } @@ -70,7 +70,7 @@ uint32_t color_add(uint32_t c1, uint32_t c2, bool fast) uint32_t color_fade(uint32_t c1, uint8_t amount, bool video) { if (c1 == BLACK || amount == 0) return BLACK; - else if (amount == 255) return c1; + if (amount == 255) return c1; uint32_t scaledcolor; // color order is: W R G B from MSB to LSB uint32_t scale = amount; // 32bit for faster calculation uint32_t addRemains = 0; diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 2f9bc44d0..0ebcd64da 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -82,7 +82,7 @@ class NeoGammaWLEDMethod { [[gnu::hot]] uint32_t color_blend(uint32_t,uint32_t,uint16_t,bool b16=false); [[gnu::hot]] uint32_t color_add(uint32_t,uint32_t, bool fast=false); [[gnu::hot]] uint32_t color_fade(uint32_t c1, uint8_t amount, bool video=false); -CRGB ColorFromPaletteWLED(const CRGBPalette16 &pal, unsigned index, uint8_t brightness = (uint8_t)255U, TBlendType blendType = LINEARBLEND); +[[gnu::hot]] CRGB ColorFromPaletteWLED(const CRGBPalette16 &pal, unsigned index, uint8_t brightness = (uint8_t)255U, TBlendType blendType = LINEARBLEND); CRGBPalette16 generateHarmonicRandomPalette(CRGBPalette16 &basepalette); CRGBPalette16 generateRandomPalette(); inline uint32_t colorFromRgbw(byte* rgbw) { return uint32_t((byte(rgbw[3]) << 24) | (byte(rgbw[0]) << 16) | (byte(rgbw[1]) << 8) | (byte(rgbw[2]))); } From 2afff0501401c7161eae841107868884635779ae Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Sat, 14 Sep 2024 11:45:27 +0200 Subject: [PATCH 029/287] minor tweak (break instead of continue in setPixelColorXY) --- wled00/FX_2Dfcn.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index 01410b811..57ee2e5e3 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -188,7 +188,7 @@ void IRAM_ATTR_YN Segment::setPixelColorXY(int x, int y, uint32_t col) int yY = y; for (int j = 0; j < grouping; j++) { // groupping vertically - if(yY >= H) continue; + if(yY >= H) break; int xX = x; for (int g = 0; g < grouping; g++) { // groupping horizontally if (xX >= W) continue; // we have reached one dimension's end From 6a37f25c5d994b914222999fdce441c3427e1fcd Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Sat, 14 Sep 2024 14:10:46 +0200 Subject: [PATCH 030/287] memory improvement: dropped static gamma table - there already is a method to calculate the table on the fly, there is no need to store it in flash, it can just be calculated at bootup (or cfg change) --- wled00/cfg.cpp | 11 +++++------ wled00/colors.cpp | 19 ++----------------- wled00/set.cpp | 5 ++--- 3 files changed, 9 insertions(+), 26 deletions(-) diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index a6c3ab74d..978ed6eba 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -440,13 +440,12 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { else gammaCorrectBri = false; if (light_gc_col > 1.0f) gammaCorrectCol = true; else gammaCorrectCol = false; - if (gammaCorrectVal > 1.0f && gammaCorrectVal <= 3) { - if (gammaCorrectVal != 2.8f) NeoGammaWLEDMethod::calcGammaTable(gammaCorrectVal); - } else { - gammaCorrectVal = 1.0f; // no gamma correction - gammaCorrectBri = false; - gammaCorrectCol = false; + if (gammaCorrectVal <= 1.0f || gammaCorrectVal > 3) { + gammaCorrectVal = 1.0f; // no gamma correction + gammaCorrectBri = false; + gammaCorrectCol = false; } + NeoGammaWLEDMethod::calcGammaTable(gammaCorrectVal); // fill look-up table JsonObject light_tr = light["tr"]; CJSON(fadeTransition, light_tr["mode"]); diff --git a/wled00/colors.cpp b/wled00/colors.cpp index 7747216f7..4afe4c0d7 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -481,23 +481,7 @@ uint16_t approximateKelvinFromRGB(uint32_t rgb) { } //gamma 2.8 lookup table used for color correction -uint8_t NeoGammaWLEDMethod::gammaT[256] = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, - 2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5, - 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, - 10, 10, 11, 11, 11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16, - 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 24, 24, 25, - 25, 26, 27, 27, 28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 35, 36, - 37, 38, 39, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 50, - 51, 52, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 66, 67, 68, - 69, 70, 72, 73, 74, 75, 77, 78, 79, 81, 82, 83, 85, 86, 87, 89, - 90, 92, 93, 95, 96, 98, 99,101,102,104,105,107,109,110,112,114, - 115,117,119,120,122,124,126,127,129,131,133,135,137,138,140,142, - 144,146,148,150,152,154,156,158,160,162,164,167,169,171,173,175, - 177,180,182,184,186,189,191,193,196,198,200,203,205,208,210,213, - 215,218,220,223,225,228,231,233,236,239,241,244,247,249,252,255 }; +uint8_t NeoGammaWLEDMethod::gammaT[256]; // re-calculates & fills gamma table void NeoGammaWLEDMethod::calcGammaTable(float gamma) @@ -505,6 +489,7 @@ void NeoGammaWLEDMethod::calcGammaTable(float gamma) for (size_t i = 0; i < 256; i++) { gammaT[i] = (int)(powf((float)i / 255.0f, gamma) * 255.0f + 0.5f); } + Serial.println("****GAMMA***"); //!!! } uint8_t IRAM_ATTR NeoGammaWLEDMethod::Correct(uint8_t value) diff --git a/wled00/set.cpp b/wled00/set.cpp index 7814e55d9..2fe01a54c 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -319,13 +319,12 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) gammaCorrectBri = request->hasArg(F("GB")); gammaCorrectCol = request->hasArg(F("GC")); gammaCorrectVal = request->arg(F("GV")).toFloat(); - if (gammaCorrectVal > 1.0f && gammaCorrectVal <= 3) - NeoGammaWLEDMethod::calcGammaTable(gammaCorrectVal); - else { + if (gammaCorrectVal <= 1.0f || gammaCorrectVal > 3) { gammaCorrectVal = 1.0f; // no gamma correction gammaCorrectBri = false; gammaCorrectCol = false; } + NeoGammaWLEDMethod::calcGammaTable(gammaCorrectVal); // fill look-up table fadeTransition = request->hasArg(F("TF")); modeBlending = request->hasArg(F("EB")); From 0e5bd4ed7428ab7540393935ced7e8ef4e937fc2 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Sat, 14 Sep 2024 14:11:29 +0200 Subject: [PATCH 031/287] remove test printout --- wled00/colors.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/wled00/colors.cpp b/wled00/colors.cpp index 4afe4c0d7..74723471c 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -489,7 +489,6 @@ void NeoGammaWLEDMethod::calcGammaTable(float gamma) for (size_t i = 0; i < 256; i++) { gammaT[i] = (int)(powf((float)i / 255.0f, gamma) * 255.0f + 0.5f); } - Serial.println("****GAMMA***"); //!!! } uint8_t IRAM_ATTR NeoGammaWLEDMethod::Correct(uint8_t value) From f3137eb0a9b8d12759a207b910bc854dc1b36ec3 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Sat, 14 Sep 2024 14:49:36 +0200 Subject: [PATCH 032/287] updated Segment::color_from_palette - gamma correction only where needed - paletteIndex should be uint8_t (it is only used as that) note: integrating the new `ColorFromPaletteWLED()` into this would require a whole lot of code rewrite and would result in more color conversions from 32bit to CRGB. It would be really useful only if CRGB is replaced with native 32bit colors. --- wled00/FX_fcn.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index cddf1ece7..7e8bd6471 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1181,18 +1181,21 @@ uint32_t Segment::color_wheel(uint8_t pos) const { * @returns Single color from palette */ uint32_t Segment::color_from_palette(uint16_t i, bool mapping, bool wrap, uint8_t mcol, uint8_t pbri) const { - uint32_t color = gamma32(currentColor(mcol)); - + + uint32_t color = currentColor(mcol); // default palette or no RGB support on segment - if ((palette == 0 && mcol < NUM_COLORS) || !_isRGB) return (pbri == 255) ? color : color_fade(color, pbri, true); + if ((palette == 0 && mcol < NUM_COLORS) || !_isRGB) { + color = gamma32(color); + return (pbri == 255) ? color : color_fade(color, pbri, true); + } - unsigned paletteIndex = i; + uint8_t paletteIndex = i; if (mapping && virtualLength() > 1) paletteIndex = (i*255)/(virtualLength() -1); // paletteBlend: 0 - wrap when moving, 1 - always wrap, 2 - never wrap, 3 - none (undefined) if (!wrap && strip.paletteBlend != 3) paletteIndex = scale8(paletteIndex, 240); //cut off blend at palette "end" CRGB fastled_col = ColorFromPalette(_currentPalette, paletteIndex, pbri, (strip.paletteBlend == 3)? NOBLEND:LINEARBLEND); // NOTE: paletteBlend should be global - return RGBW32(fastled_col.r, fastled_col.g, fastled_col.b, W(color)); + return RGBW32(fastled_col.r, fastled_col.g, fastled_col.b, gamma8(W(color))); } From 1ff667b7eff07ebad100e48eb871c698eab11a71 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Sun, 15 Sep 2024 10:59:50 +0200 Subject: [PATCH 033/287] AWS library fix --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index d19f47764..2ef4ae01b 100644 --- a/platformio.ini +++ b/platformio.ini @@ -180,7 +180,7 @@ lib_deps = fastled/FastLED @ 3.6.0 IRremoteESP8266 @ 2.8.2 makuna/NeoPixelBus @ 2.7.5 - https://github.com/Aircoookie/ESPAsyncWebServer.git @ 2.2.1 + https://github.com/Aircoookie/ESPAsyncWebServer.git#v2.2.1 #For use of the TTGO T-Display ESP32 Module with integrated TFT display uncomment the following line #TFT_eSPI #For compatible OLED display uncomment following From 696290527abfd01fcff3d4bd5fbfdc9c53050787 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Wed, 18 Sep 2024 22:10:27 +0200 Subject: [PATCH 034/287] cleanup and improved color_add() - optimized color_add() again: now it is as fast with preserved ratio scaling than the "fast" variant was before (if no scaling is needed, it is even faster). plus it saves 250 bytes of flash - bugfix in `color_fade()` - removed a lot of whitespaces --- wled00/FX.h | 24 +++++++-------- wled00/FX_2Dfcn.cpp | 26 ++++++++-------- wled00/FX_fcn.cpp | 16 +++++----- wled00/colors.cpp | 70 +++++++++++++++++++++----------------------- wled00/fcn_declare.h | 2 +- 5 files changed, 67 insertions(+), 71 deletions(-) diff --git a/wled00/FX.h b/wled00/FX.h index 3c28274d6..bea4dbcb8 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -595,9 +595,9 @@ typedef struct Segment { void fadeToBlackBy(uint8_t fadeBy); inline void blendPixelColor(int n, uint32_t color, uint8_t blend) { setPixelColor(n, color_blend(getPixelColor(n), color, blend)); } inline void blendPixelColor(int n, CRGB c, uint8_t blend) { blendPixelColor(n, RGBW32(c.r,c.g,c.b,0), blend); } - inline void addPixelColor(int n, uint32_t color, bool fast = false) { setPixelColor(n, color_add(getPixelColor(n), color, fast)); } - 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); } - inline void addPixelColor(int n, CRGB c, bool fast = false) { addPixelColor(n, RGBW32(c.r,c.g,c.b,0), fast); } + inline void addPixelColor(int n, uint32_t color) { setPixelColor(n, color_add(getPixelColor(n), color)); } + inline void addPixelColor(int n, byte r, byte g, byte b, byte w = 0) { addPixelColor(n, RGBW32(r,g,b,w)); } + inline void addPixelColor(int n, CRGB c) { addPixelColor(n, RGBW32(c.r,c.g,c.b,0)); } inline void fadePixelColor(uint16_t n, uint8_t fade) { setPixelColor(n, color_fade(getPixelColor(n), fade, true)); } [[gnu::hot]] uint32_t color_from_palette(uint16_t, bool mapping, bool wrap, uint8_t mcol, uint8_t pbri = 255) const; [[gnu::hot]] uint32_t color_wheel(uint8_t pos) const; @@ -605,11 +605,11 @@ typedef struct Segment { // 2D Blur: shortcuts for bluring columns or rows only (50% faster than full 2D blur) inline void blurCols(fract8 blur_amount, bool smear = false) { // blur all columns const unsigned cols = virtualWidth(); - for (unsigned k = 0; k < cols; k++) blurCol(k, blur_amount, smear); + for (unsigned k = 0; k < cols; k++) blurCol(k, blur_amount, smear); } inline void blurRows(fract8 blur_amount, bool smear = false) { // blur all rows const unsigned rows = virtualHeight(); - for ( unsigned i = 0; i < rows; i++) blurRow(i, blur_amount, smear); + for ( unsigned i = 0; i < rows; i++) blurRow(i, blur_amount, smear); } // 2D matrix @@ -632,10 +632,10 @@ typedef struct Segment { // 2D support functions inline void blendPixelColorXY(uint16_t x, uint16_t y, uint32_t color, uint8_t blend) { setPixelColorXY(x, y, color_blend(getPixelColorXY(x,y), color, 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); } - inline void addPixelColorXY(int x, int y, uint32_t color, bool fast = false) { setPixelColorXY(x, y, color_add(getPixelColorXY(x,y), color, 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); } - 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 fadePixelColorXY(uint16_t x, uint16_t y, uint8_t fade) { setPixelColorXY(x, y, color_fade(getPixelColorXY(x,y), fade, true)); } + inline void addPixelColorXY(int x, int y, uint32_t color) { setPixelColorXY(x, y, color_add(getPixelColorXY(x,y), color)); } + inline void addPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) { addPixelColorXY(x, y, RGBW32(r,g,b,w)); } + inline void addPixelColorXY(int x, int y, CRGB c) { addPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0)); } + inline void fadePixelColorXY(uint16_t x, uint16_t y, uint8_t fade) { setPixelColorXY(x, y, color_fade(getPixelColorXY(x,y), fade, true)); } void box_blur(unsigned r = 1U, bool smear = false); // 2D box blur void blur2D(uint8_t blur_amount, bool smear = false); void blurRow(uint32_t row, fract8 blur_amount, bool smear = false); @@ -670,9 +670,9 @@ typedef struct Segment { inline uint32_t getPixelColorXY(int x, int y) { return getPixelColor(x); } inline void blendPixelColorXY(uint16_t x, uint16_t y, uint32_t c, uint8_t blend) { blendPixelColor(x, c, blend); } inline void blendPixelColorXY(uint16_t x, uint16_t y, CRGB c, uint8_t blend) { blendPixelColor(x, RGBW32(c.r,c.g,c.b,0), blend); } - inline void addPixelColorXY(int x, int y, uint32_t color, bool fast = false) { addPixelColor(x, color, fast); } - inline void addPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0, bool fast = false) { addPixelColor(x, RGBW32(r,g,b,w), fast); } - inline void addPixelColorXY(int x, int y, CRGB c, bool fast = false) { addPixelColor(x, RGBW32(c.r,c.g,c.b,0), fast); } + inline void addPixelColorXY(int x, int y, uint32_t color) { addPixelColor(x, color); } + inline void addPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) { addPixelColor(x, RGBW32(r,g,b,w)); } + inline void addPixelColorXY(int x, int y, CRGB c) { addPixelColor(x, RGBW32(c.r,c.g,c.b,0)); } inline void fadePixelColorXY(uint16_t x, uint16_t y, uint8_t fade) { fadePixelColor(x, fade); } inline void box_blur(unsigned i, bool vertical, fract8 blur_amount) {} inline void blur2D(uint8_t blur_amount, bool smear = false) {} diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index 57ee2e5e3..10b85a82e 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -173,7 +173,7 @@ void IRAM_ATTR_YN Segment::setPixelColorXY(int x, int y, uint32_t col) if (!isActive()) return; // not active if (x >= virtualWidth() || y >= virtualHeight() || x<0 || y<0) return; // if pixel would fall out of virtual segment just exit - uint8_t _bri_t = currentBri(); + uint8_t _bri_t = currentBri(); if (_bri_t < 255) { col = color_fade(col, _bri_t); } @@ -185,11 +185,11 @@ void IRAM_ATTR_YN Segment::setPixelColorXY(int x, int y, uint32_t col) y *= groupLength(); // expand to physical pixels int W = width(); int H = height(); - + int yY = y; for (int j = 0; j < grouping; j++) { // groupping vertically if(yY >= H) break; - int xX = x; + int xX = x; for (int g = 0; g < grouping; g++) { // groupping horizontally if (xX >= W) continue; // we have reached one dimension's end #ifndef WLED_DISABLE_MODE_BLEND @@ -293,8 +293,8 @@ void Segment::blurRow(uint32_t row, fract8 blur_amount, bool smear){ curnew = color_fade(cur, keep); if (x > 0) { if (carryover) - curnew = color_add(curnew, carryover, true); - uint32_t prev = color_add(lastnew, part, true); + curnew = color_add(curnew, carryover); + uint32_t prev = color_add(lastnew, part); if (last != prev) // optimization: only set pixel if color has changed setPixelColorXY(x - 1, row, prev); } else // first pixel @@ -326,15 +326,15 @@ void Segment::blurCol(uint32_t col, fract8 blur_amount, bool smear) { curnew = color_fade(cur, keep); if (y > 0) { if (carryover) - curnew = color_add(curnew, carryover, true); - uint32_t prev = color_add(lastnew, part, true); + curnew = color_add(curnew, carryover); + uint32_t prev = color_add(lastnew, part); if (last != prev) // optimization: only set pixel if color has changed setPixelColorXY(col, y - 1, prev); } else // first pixel setPixelColorXY(col, y, curnew); lastnew = curnew; last = cur; //save original value for comparison on next iteration - carryover = part; + carryover = part; } setPixelColorXY(col, rows - 1, curnew); } @@ -356,8 +356,8 @@ void Segment::blur2D(uint8_t blur_amount, bool smear) { uint32_t part = color_fade(cur, seep); curnew = color_fade(cur, keep); if (x > 0) { - if (carryover) curnew = color_add(curnew, carryover, true); - uint32_t prev = color_add(lastnew, part, true); + if (carryover) curnew = color_add(curnew, carryover); + uint32_t prev = color_add(lastnew, part); // optimization: only set pixel if color has changed if (last != prev) setPixelColorXY(x - 1, row, prev); } else setPixelColorXY(x, row, curnew); // first pixel @@ -375,14 +375,14 @@ void Segment::blur2D(uint8_t blur_amount, bool smear) { uint32_t part = color_fade(cur, seep); curnew = color_fade(cur, keep); if (y > 0) { - if (carryover) curnew = color_add(curnew, carryover, true); - uint32_t prev = color_add(lastnew, part, true); + if (carryover) curnew = color_add(curnew, carryover); + uint32_t prev = color_add(lastnew, part); // optimization: only set pixel if color has changed if (last != prev) setPixelColorXY(col, y - 1, prev); } else setPixelColorXY(col, y, curnew); // first pixel lastnew = curnew; last = cur; //save original value for comparison on next iteration - carryover = part; + carryover = part; } setPixelColorXY(col, rows - 1, curnew); } diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 236f7ad4a..66aeaab63 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -712,12 +712,12 @@ void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col) { if (!isActive()) return; // not active #ifndef WLED_DISABLE_2D - int vStrip; + int vStrip; #endif if (i >= virtualLength() || i<0) // pixel would fall out of segment, check if this is a virtual strip NOTE: this is almost always false if not virtual strip, saves the calculation on 'standard' call { #ifndef WLED_DISABLE_2D - vStrip = i>>16; // hack to allow running on virtual strips (2D segment columns/rows) + vStrip = i>>16; // hack to allow running on virtual strips (2D segment columns/rows) #endif i &= 0xFFFF; //truncate vstrip index if (i >= virtualLength() || i<0) return; // if pixel would still fall out of segment just exit @@ -735,7 +735,7 @@ void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col) 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); break; case M12_pArc: // expand in circular fashion from center @@ -796,7 +796,7 @@ void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col) // Odd rays start further from center if prevRay started at center. static int prevRay = INT_MIN; // previous ray number if ((i % 2 == 1) && (i - 1 == prevRay || i + 1 == prevRay)) { - int jump = min(vW/3, vH/3); // can add 2 if using medium pinwheel + int jump = min(vW/3, vH/3); // can add 2 if using medium pinwheel posx += inc_x * jump; posy += inc_y * jump; } @@ -1145,8 +1145,8 @@ void Segment::blur(uint8_t blur_amount, bool smear) { uint32_t part = color_fade(cur, seep); curnew = color_fade(cur, keep); if (i > 0) { - if (carryover) curnew = color_add(curnew, carryover, true); - uint32_t prev = color_add(lastnew, part, true); + if (carryover) curnew = color_add(curnew, carryover); + uint32_t prev = color_add(lastnew, part); // optimization: only set pixel if color has changed if (last != prev) setPixelColor(i - 1, prev); } else // first pixel @@ -1188,7 +1188,7 @@ uint32_t Segment::color_wheel(uint8_t pos) const { * @returns Single color from palette */ uint32_t Segment::color_from_palette(uint16_t i, bool mapping, bool wrap, uint8_t mcol, uint8_t pbri) const { - + uint32_t color = currentColor(mcol); // default palette or no RGB support on segment if ((palette == 0 && mcol < NUM_COLORS) || !_isRGB) { @@ -1196,7 +1196,7 @@ uint32_t Segment::color_from_palette(uint16_t i, bool mapping, bool wrap, uint8_ return (pbri == 255) ? color : color_fade(color, pbri, true); } - uint8_t paletteIndex = i; + unsigned paletteIndex = i; if (mapping && virtualLength() > 1) paletteIndex = (i*255)/(virtualLength() -1); // paletteBlend: 0 - wrap when moving, 1 - always wrap, 2 - never wrap, 3 - none (undefined) if (!wrap && strip.paletteBlend != 3) paletteIndex = scale8(paletteIndex, 240); //cut off blend at palette "end" diff --git a/wled00/colors.cpp b/wled00/colors.cpp index 104d25e60..54469ebe0 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -33,33 +33,32 @@ uint32_t color_blend(uint32_t color1, uint32_t color2, uint16_t blend, bool b16) /* * color add function that preserves ratio - * idea: https://github.com/Aircoookie/WLED/pull/2465 by https://github.com/Proto-molecule + * original idea: https://github.com/Aircoookie/WLED/pull/2465 by https://github.com/Proto-molecule + * heavily optimized for speed by @dedehai */ -uint32_t color_add(uint32_t c1, uint32_t c2, bool fast) +uint32_t color_add(uint32_t c1, uint32_t c2) { if (c1 == BLACK) return c2; if (c2 == BLACK) return c1; - uint32_t rb = (c1 & 0x00FF00FF) + (c2 & 0x00FF00FF); - uint32_t r = rb >> 16; - uint32_t b = rb & 0xFFFF; - uint32_t wg = ((c1>>8) & 0x00FF00FF) + ((c2>>8) & 0x00FF00FF); + uint32_t rb = (c1 & 0x00FF00FF) + (c2 & 0x00FF00FF); // mask and add two colors at once + uint32_t wg = ((c1>>8) & 0x00FF00FF) + ((c2>>8) & 0x00FF00FF); + uint32_t r = rb >> 16; // extract single color values + uint32_t b = rb & 0xFFFF; uint32_t w = wg >> 16; - uint32_t g = wg & 0xFFFF; + uint32_t g = wg & 0xFFFF; - if (fast) { - r = r > 255 ? 255 : r; - g = g > 255 ? 255 : g; - b = b > 255 ? 255 : b; - w = w > 255 ? 255 : w; - return RGBW32(r,g,b,w); - } else { - unsigned max = r; - max = g > max ? g : max; - max = b > max ? b : max; - max = w > max ? w : max; - if (max < 256) return RGBW32(r, g, b, w); - else return RGBW32(r * 255 / max, g * 255 / max, b * 255 / max, w * 255 / max); + unsigned max = r; // check for overflow note: not checking and just topping out at 255 (formerly 'fast') is not any faster (but even slower if not overflowing) + max = g > max ? g : max; + max = b > max ? b : max; + max = w > max ? w : max; + + if (max > 255) { + uint32_t scale = (uint32_t(255)<<8) / max; // division of two 8bit (shifted) values does not work -> use bit shifts and multiplaction instead + rb = ((rb * scale) >> 8) & 0x00FF00FF; // + wg = (wg * scale) & 0xFF00FF00; } + else wg = wg << 8; //shift white and green back to correct position + return rb | wg; } /* @@ -70,52 +69,49 @@ uint32_t color_add(uint32_t c1, uint32_t c2, bool fast) uint32_t color_fade(uint32_t c1, uint8_t amount, bool video) { if (c1 == BLACK || amount == 0) return BLACK; - if (amount == 255) return c1; + if (amount == 255) return c1; uint32_t scaledcolor; // color order is: W R G B from MSB to LSB uint32_t scale = amount; // 32bit for faster calculation uint32_t addRemains = 0; - if (!video) amount++; // add one for correct scaling using bitshifts + if (!video) scale++; // add one for correct scaling using bitshifts else { // video scaling: make sure colors do not dim to zero if they started non-zero - addRemains = R(c1) ? 0x00010000 : 0; + addRemains = R(c1) ? 0x00010000 : 0; addRemains |= G(c1) ? 0x00000100 : 0; addRemains |= B(c1) ? 0x00000001 : 0; addRemains |= W(c1) ? 0x01000000 : 0; } uint32_t rb = (((c1 & 0x00FF00FF) * scale) >> 8) & 0x00FF00FF; // scale red and blue uint32_t wg = (((c1 & 0xFF00FF00) >> 8) * scale) & 0xFF00FF00; // scale white and green - scaledcolor = (rb | wg) + addRemains; + scaledcolor = (rb | wg) + addRemains; return scaledcolor; } // 1:1 replacement of fastled function optimized for ESP, slightly faster, more accurate and uses less flash (~ -200bytes) CRGB ColorFromPaletteWLED(const CRGBPalette16& pal, unsigned index, uint8_t brightness, TBlendType blendType) { - if ( blendType == LINEARBLEND_NOWRAP) { - //index = map8(index, 0, 239); + if (blendType == LINEARBLEND_NOWRAP) { index = (index*240) >> 8; // Blend range is affected by lo4 blend of values, remap to avoid wrapping } unsigned hi4 = byte(index) >> 4; - // We then add that to a base array pointer. const CRGB* entry = (CRGB*)( (uint8_t*)(&(pal[0])) + (hi4 * sizeof(CRGB))); unsigned red1 = entry->r; unsigned green1 = entry->g; - unsigned blue1 = entry->b; + unsigned blue1 = entry->b; if(blendType != NOBLEND) { if(hi4 == 15) entry = &(pal[0]); else ++entry; - // unsigned red2 = entry->red; unsigned f2 = ((index & 0x0F) << 4) + 1; // +1 so we scale by 256 as a max value, then result can just be shifted by 8 unsigned f1 = (257 - f2); // f2 is 1 minimum, so this is 256 max - red1 = (red1 * f1 + (unsigned)entry->r * f2) >> 8; - green1 = (green1 * f1 + (unsigned)entry->g * f2) >> 8; - blue1 = (blue1 * f1 + (unsigned)entry->b * f2) >> 8; + red1 = (red1 * f1 + (unsigned)entry->r * f2) >> 8; + green1 = (green1 * f1 + (unsigned)entry->g * f2) >> 8; + blue1 = (blue1 * f1 + (unsigned)entry->b * f2) >> 8; } if( brightness < 255) { // note: zero checking could be done to return black but that is hardly ever used so it is omitted - uint32_t scale = brightness + 1; // adjust for rounding (bitshift) + uint32_t scale = brightness + 1; // adjust for rounding (bitshift) red1 = (red1 * scale) >> 8; green1 = (green1 * scale) >> 8; blue1 = (blue1 * scale) >> 8; - } + } return CRGB((uint8_t)red1, (uint8_t)green1, (uint8_t)blue1); } @@ -176,7 +172,7 @@ CRGBPalette16 generateHarmonicRandomPalette(CRGBPalette16 &basepalette) harmonics[1] = basehue + 205 + random8(10); harmonics[2] = basehue - 5 + random8(10); break; - + case 3: // square harmonics[0] = basehue + 85 + random8(10); harmonics[1] = basehue + 175 + random8(10); @@ -213,9 +209,9 @@ CRGBPalette16 generateHarmonicRandomPalette(CRGBPalette16 &basepalette) //apply saturation & gamma correction CRGB RGBpalettecolors[4]; for (int i = 0; i < 4; i++) { - if (makepastelpalette && palettecolors[i].saturation > 180) { + if (makepastelpalette && palettecolors[i].saturation > 180) { palettecolors[i].saturation -= 160; //desaturate all four colors - } + } RGBpalettecolors[i] = (CRGB)palettecolors[i]; //convert to RGB RGBpalettecolors[i] = gamma32(((uint32_t)RGBpalettecolors[i]) & 0x00FFFFFFU); //strip alpha from CRGB } diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index ac941dc97..be7ed4462 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -80,7 +80,7 @@ class NeoGammaWLEDMethod { #define gamma32(c) NeoGammaWLEDMethod::Correct32(c) #define gamma8(c) NeoGammaWLEDMethod::rawGamma8(c) [[gnu::hot]] uint32_t color_blend(uint32_t,uint32_t,uint16_t,bool b16=false); -[[gnu::hot]] uint32_t color_add(uint32_t,uint32_t, bool fast=false); +[[gnu::hot]] uint32_t color_add(uint32_t,uint32_t); [[gnu::hot]] uint32_t color_fade(uint32_t c1, uint8_t amount, bool video=false); [[gnu::hot]] CRGB ColorFromPaletteWLED(const CRGBPalette16 &pal, unsigned index, uint8_t brightness = (uint8_t)255U, TBlendType blendType = LINEARBLEND); CRGBPalette16 generateHarmonicRandomPalette(CRGBPalette16 &basepalette); From a88436c620bed17a66bf7f1563888036c3a4d10f Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Thu, 19 Sep 2024 08:49:18 +0200 Subject: [PATCH 035/287] revert removal of adding with saturation, renamed 'fast' to 'saturate' - blurring now uses desaturated adding: it is faster most of the times and blurring adds scaled colors so should rarely (ever?) saturate, I saw no visual difference in tests. - formatting --- wled00/FX.h | 18 +++++++++--------- wled00/FX_2Dfcn.cpp | 2 +- wled00/FX_fcn.cpp | 2 +- wled00/colors.cpp | 31 ++++++++++++++++++++----------- wled00/fcn_declare.h | 4 ++-- 5 files changed, 33 insertions(+), 24 deletions(-) diff --git a/wled00/FX.h b/wled00/FX.h index bea4dbcb8..50bcd6624 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -595,9 +595,9 @@ typedef struct Segment { void fadeToBlackBy(uint8_t fadeBy); inline void blendPixelColor(int n, uint32_t color, uint8_t blend) { setPixelColor(n, color_blend(getPixelColor(n), color, blend)); } inline void blendPixelColor(int n, CRGB c, uint8_t blend) { blendPixelColor(n, RGBW32(c.r,c.g,c.b,0), blend); } - inline void addPixelColor(int n, uint32_t color) { setPixelColor(n, color_add(getPixelColor(n), color)); } - inline void addPixelColor(int n, byte r, byte g, byte b, byte w = 0) { addPixelColor(n, RGBW32(r,g,b,w)); } - inline void addPixelColor(int n, CRGB c) { addPixelColor(n, RGBW32(c.r,c.g,c.b,0)); } + inline void addPixelColor(int n, uint32_t color, bool saturate = false) { setPixelColor(n, color_add(getPixelColor(n), color, saturate)); } + inline void addPixelColor(int n, byte r, byte g, byte b, byte w = 0, bool saturate = false) { addPixelColor(n, RGBW32(r,g,b,w), saturate); } + inline void addPixelColor(int n, CRGB c, bool saturate = false) { addPixelColor(n, RGBW32(c.r,c.g,c.b,0), saturate); } inline void fadePixelColor(uint16_t n, uint8_t fade) { setPixelColor(n, color_fade(getPixelColor(n), fade, true)); } [[gnu::hot]] uint32_t color_from_palette(uint16_t, bool mapping, bool wrap, uint8_t mcol, uint8_t pbri = 255) const; [[gnu::hot]] uint32_t color_wheel(uint8_t pos) const; @@ -632,9 +632,9 @@ typedef struct Segment { // 2D support functions inline void blendPixelColorXY(uint16_t x, uint16_t y, uint32_t color, uint8_t blend) { setPixelColorXY(x, y, color_blend(getPixelColorXY(x,y), color, 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); } - inline void addPixelColorXY(int x, int y, uint32_t color) { setPixelColorXY(x, y, color_add(getPixelColorXY(x,y), color)); } - inline void addPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) { addPixelColorXY(x, y, RGBW32(r,g,b,w)); } - inline void addPixelColorXY(int x, int y, CRGB c) { addPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0)); } + inline void addPixelColorXY(int x, int y, uint32_t color, bool saturate = false) { setPixelColorXY(x, y, color_add(getPixelColorXY(x,y), color, saturate)); } + inline void addPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0, bool saturate = false) { addPixelColorXY(x, y, RGBW32(r,g,b,w), saturate); } + inline void addPixelColorXY(int x, int y, CRGB c, bool saturate = false) { addPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), saturate); } inline void fadePixelColorXY(uint16_t x, uint16_t y, uint8_t fade) { setPixelColorXY(x, y, color_fade(getPixelColorXY(x,y), fade, true)); } void box_blur(unsigned r = 1U, bool smear = false); // 2D box blur void blur2D(uint8_t blur_amount, bool smear = false); @@ -670,9 +670,9 @@ typedef struct Segment { inline uint32_t getPixelColorXY(int x, int y) { return getPixelColor(x); } inline void blendPixelColorXY(uint16_t x, uint16_t y, uint32_t c, uint8_t blend) { blendPixelColor(x, c, blend); } inline void blendPixelColorXY(uint16_t x, uint16_t y, CRGB c, uint8_t blend) { blendPixelColor(x, RGBW32(c.r,c.g,c.b,0), blend); } - inline void addPixelColorXY(int x, int y, uint32_t color) { addPixelColor(x, color); } - inline void addPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) { addPixelColor(x, RGBW32(r,g,b,w)); } - inline void addPixelColorXY(int x, int y, CRGB c) { addPixelColor(x, RGBW32(c.r,c.g,c.b,0)); } + inline void addPixelColorXY(int x, int y, uint32_t color, bool saturate = false) { addPixelColor(x, color, saturate); } + inline void addPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0, bool saturate = false) { addPixelColor(x, RGBW32(r,g,b,w), saturate); } + inline void addPixelColorXY(int x, int y, CRGB c, bool saturate = false) { addPixelColor(x, RGBW32(c.r,c.g,c.b,0), saturate); } inline void fadePixelColorXY(uint16_t x, uint16_t y, uint8_t fade) { fadePixelColor(x, fade); } inline void box_blur(unsigned i, bool vertical, fract8 blur_amount) {} inline void blur2D(uint8_t blur_amount, bool smear = false) {} diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index 10b85a82e..63595d930 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -188,7 +188,7 @@ void IRAM_ATTR_YN Segment::setPixelColorXY(int x, int y, uint32_t col) int yY = y; for (int j = 0; j < grouping; j++) { // groupping vertically - if(yY >= H) break; + if (yY >= H) break; int xX = x; for (int g = 0; g < grouping; g++) { // groupping horizontally if (xX >= W) continue; // we have reached one dimension's end diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 66aeaab63..7159d21c7 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1169,7 +1169,7 @@ uint32_t Segment::color_wheel(uint8_t pos) const { pos = 255 - pos; if (pos < 85) { return RGBW32((255 - pos * 3), 0, (pos * 3), w); - } else if(pos < 170) { + } else if (pos < 170) { pos -= 85; return RGBW32(0, (pos * 3), (255 - pos * 3), w); } else { diff --git a/wled00/colors.cpp b/wled00/colors.cpp index 54469ebe0..13405be6e 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -36,7 +36,7 @@ uint32_t color_blend(uint32_t color1, uint32_t color2, uint16_t blend, bool b16) * original idea: https://github.com/Aircoookie/WLED/pull/2465 by https://github.com/Proto-molecule * heavily optimized for speed by @dedehai */ -uint32_t color_add(uint32_t c1, uint32_t c2) +uint32_t color_add(uint32_t c1, uint32_t c2, bool desat) { if (c1 == BLACK) return c2; if (c2 == BLACK) return c1; @@ -47,18 +47,27 @@ uint32_t color_add(uint32_t c1, uint32_t c2) uint32_t w = wg >> 16; uint32_t g = wg & 0xFFFF; - unsigned max = r; // check for overflow note: not checking and just topping out at 255 (formerly 'fast') is not any faster (but even slower if not overflowing) - max = g > max ? g : max; - max = b > max ? b : max; - max = w > max ? w : max; + if(desat) { // desaturate + unsigned max = r; // check for overflow note + max = g > max ? g : max; + max = b > max ? b : max; + max = w > max ? w : max; - if (max > 255) { - uint32_t scale = (uint32_t(255)<<8) / max; // division of two 8bit (shifted) values does not work -> use bit shifts and multiplaction instead - rb = ((rb * scale) >> 8) & 0x00FF00FF; // - wg = (wg * scale) & 0xFF00FF00; + if (max > 255) { + uint32_t scale = (uint32_t(255)<<8) / max; // division of two 8bit (shifted) values does not work -> use bit shifts and multiplaction instead + rb = ((rb * scale) >> 8) & 0x00FF00FF; // + wg = (wg * scale) & 0xFF00FF00; + } + else wg = wg << 8; //shift white and green back to correct position + return rb | wg; + } + else { + r = r > 255 ? 255 : r; + g = g > 255 ? 255 : g; + b = b > 255 ? 255 : b; + w = w > 255 ? 255 : w; + return RGBW32(r,g,b,w); } - else wg = wg << 8; //shift white and green back to correct position - return rb | wg; } /* diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index be7ed4462..d32356d7b 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -79,8 +79,8 @@ class NeoGammaWLEDMethod { }; #define gamma32(c) NeoGammaWLEDMethod::Correct32(c) #define gamma8(c) NeoGammaWLEDMethod::rawGamma8(c) -[[gnu::hot]] uint32_t color_blend(uint32_t,uint32_t,uint16_t,bool b16=false); -[[gnu::hot]] uint32_t color_add(uint32_t,uint32_t); +[[gnu::hot]] uint32_t color_blend(uint32_t, uint32_t, uint16_t, bool b16=false); +[[gnu::hot]] uint32_t color_add(uint32_t, uint32_t, bool desat = false); [[gnu::hot]] uint32_t color_fade(uint32_t c1, uint8_t amount, bool video=false); [[gnu::hot]] CRGB ColorFromPaletteWLED(const CRGBPalette16 &pal, unsigned index, uint8_t brightness = (uint8_t)255U, TBlendType blendType = LINEARBLEND); CRGBPalette16 generateHarmonicRandomPalette(CRGBPalette16 &basepalette); From 17d59d333710264a6e0772b3e9d29c8a59c1e189 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Sun, 22 Sep 2024 09:02:42 +0200 Subject: [PATCH 036/287] adding initialization to vStrip, added comment on padding bytes --- wled00/FX.h | 1 + wled00/FX_fcn.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/wled00/FX.h b/wled00/FX.h index 50bcd6624..49277ba11 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -368,6 +368,7 @@ typedef struct Segment { }; uint8_t startY; // start Y coodrinate 2D (top); there should be no more than 255 rows uint8_t stopY; // stop Y coordinate 2D (bottom); there should be no more than 255 rows + //note: here are 3 free bytes of padding char *name; // runtime data diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 7159d21c7..e78608a14 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -712,7 +712,7 @@ void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col) { if (!isActive()) return; // not active #ifndef WLED_DISABLE_2D - int vStrip; + int vStrip = 0; #endif if (i >= virtualLength() || i<0) // pixel would fall out of segment, check if this is a virtual strip NOTE: this is almost always false if not virtual strip, saves the calculation on 'standard' call { From 0a5400263be34b6a9b45e7d11acefce584f17614 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Sun, 22 Sep 2024 13:52:56 +0200 Subject: [PATCH 037/287] removed IRAM_ATTR from inlined function when the function is inlined into a IRAM_ATTR function, it will also reside in IRAM. Forced inlining is recommended by Espressif if I understand this correctly: https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/hardware-abstraction.html --- wled00/FX_fcn.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index e78608a14..9fd78e314 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1884,7 +1884,7 @@ bool WS2812FX::deserializeMap(uint8_t n) { return (customMappingSize > 0); } -__attribute__ ((always_inline)) inline uint16_t IRAM_ATTR WS2812FX::getMappedPixelIndex(uint16_t index) const { +__attribute__ ((always_inline)) inline uint16_t WS2812FX::getMappedPixelIndex(uint16_t index) const { // convert logical address to physical if (index < customMappingSize && (realtimeMode == REALTIME_MODE_INACTIVE || realtimeRespectLedMaps)) index = customMappingTable[index]; From 33cf82a9822a427d941107eba76e562a446a72ab Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Mon, 23 Sep 2024 18:03:17 +0200 Subject: [PATCH 038/287] Indentations and a few optimisations Restore addPixelColor() behaviour. --- wled00/FX.h | 22 ++++++++++++------- wled00/FX_2Dfcn.cpp | 16 ++++++-------- wled00/FX_fcn.cpp | 48 +++++++++++++++------------------------- wled00/cfg.cpp | 6 ++--- wled00/colors.cpp | 52 ++++++++++++++++++++++---------------------- wled00/fcn_declare.h | 2 +- 6 files changed, 69 insertions(+), 77 deletions(-) diff --git a/wled00/FX.h b/wled00/FX.h index 49277ba11..989dfbe33 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -56,6 +56,9 @@ #define RGBW32(r,g,b,w) (uint32_t((byte(w) << 24) | (byte(r) << 16) | (byte(g) << 8) | (byte(b)))) #endif +extern bool realtimeRespectLedMaps; // used in getMappedPixelIndex() +extern byte realtimeMode; // used in getMappedPixelIndex() + /* Not used in all effects yet */ #define WLED_FPS 42 #define FRAMETIME_FIXED (1000/WLED_FPS) @@ -596,9 +599,9 @@ typedef struct Segment { void fadeToBlackBy(uint8_t fadeBy); inline void blendPixelColor(int n, uint32_t color, uint8_t blend) { setPixelColor(n, color_blend(getPixelColor(n), color, blend)); } inline void blendPixelColor(int n, CRGB c, uint8_t blend) { blendPixelColor(n, RGBW32(c.r,c.g,c.b,0), blend); } - inline void addPixelColor(int n, uint32_t color, bool saturate = false) { setPixelColor(n, color_add(getPixelColor(n), color, saturate)); } - inline void addPixelColor(int n, byte r, byte g, byte b, byte w = 0, bool saturate = false) { addPixelColor(n, RGBW32(r,g,b,w), saturate); } - inline void addPixelColor(int n, CRGB c, bool saturate = false) { addPixelColor(n, RGBW32(c.r,c.g,c.b,0), saturate); } + inline void addPixelColor(int n, uint32_t color, bool preserveCR = true) { setPixelColor(n, color_add(getPixelColor(n), color, preserveCR)); } + inline void addPixelColor(int n, byte r, byte g, byte b, byte w = 0, bool preserveCR = true) { addPixelColor(n, RGBW32(r,g,b,w), preserveCR); } + inline void addPixelColor(int n, CRGB c, bool preserveCR = true) { addPixelColor(n, RGBW32(c.r,c.g,c.b,0), preserveCR); } inline void fadePixelColor(uint16_t n, uint8_t fade) { setPixelColor(n, color_fade(getPixelColor(n), fade, true)); } [[gnu::hot]] uint32_t color_from_palette(uint16_t, bool mapping, bool wrap, uint8_t mcol, uint8_t pbri = 255) const; [[gnu::hot]] uint32_t color_wheel(uint8_t pos) const; @@ -633,9 +636,9 @@ typedef struct Segment { // 2D support functions inline void blendPixelColorXY(uint16_t x, uint16_t y, uint32_t color, uint8_t blend) { setPixelColorXY(x, y, color_blend(getPixelColorXY(x,y), color, 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); } - inline void addPixelColorXY(int x, int y, uint32_t color, bool saturate = false) { setPixelColorXY(x, y, color_add(getPixelColorXY(x,y), color, saturate)); } - inline void addPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0, bool saturate = false) { addPixelColorXY(x, y, RGBW32(r,g,b,w), saturate); } - inline void addPixelColorXY(int x, int y, CRGB c, bool saturate = false) { addPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), saturate); } + inline void addPixelColorXY(int x, int y, uint32_t color, bool preserveCR = true) { setPixelColorXY(x, y, color_add(getPixelColorXY(x,y), color, preserveCR)); } + inline void addPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0, bool preserveCR = true) { addPixelColorXY(x, y, RGBW32(r,g,b,w), preserveCR); } + inline void addPixelColorXY(int x, int y, CRGB c, bool preserveCR = true) { addPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), preserveCR); } inline void fadePixelColorXY(uint16_t x, uint16_t y, uint8_t fade) { setPixelColorXY(x, y, color_fade(getPixelColorXY(x,y), fade, true)); } void box_blur(unsigned r = 1U, bool smear = false); // 2D box blur void blur2D(uint8_t blur_amount, bool smear = false); @@ -837,13 +840,16 @@ class WS2812FX { // 96 bytes uint16_t getLengthPhysical() const, getLengthTotal() const, // will include virtual/nonexistent pixels in matrix - getFps() const, - getMappedPixelIndex(uint16_t index) const; + getFps() const; inline uint16_t getFrameTime() const { return _frametime; } // returns amount of time a frame should take (in ms) inline uint16_t getMinShowDelay() const { return MIN_SHOW_DELAY; } // returns minimum amount of time strip.service() can be delayed (constant) inline uint16_t getLength() const { return _length; } // returns actual amount of LEDs on a strip (2D matrix may have less LEDs than W*H) inline uint16_t getTransition() const { return _transitionDur; } // returns currently set transition time (in ms) + inline uint16_t getMappedPixelIndex(uint16_t index) const { // convert logical address to physical + if (index < customMappingSize && (realtimeMode == REALTIME_MODE_INACTIVE || realtimeRespectLedMaps)) index = customMappingTable[index]; + return index; + }; uint32_t now, diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index 63595d930..ca28a9586 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -191,7 +191,7 @@ void IRAM_ATTR_YN Segment::setPixelColorXY(int x, int y, uint32_t col) if (yY >= H) break; int xX = x; for (int g = 0; g < grouping; g++) { // groupping horizontally - if (xX >= W) continue; // we have reached one dimension's end + if (xX >= W) break; // we have reached X dimension's end #ifndef WLED_DISABLE_MODE_BLEND // if blending modes, blend with underlying pixel if (_modeBlend) col = color_blend(strip.getPixelColorXY(start + xX, startY + yY), col, 0xFFFFU - progress(), true); @@ -292,11 +292,10 @@ void Segment::blurRow(uint32_t row, fract8 blur_amount, bool smear){ uint32_t part = color_fade(cur, seep); curnew = color_fade(cur, keep); if (x > 0) { - if (carryover) - curnew = color_add(curnew, carryover); + if (carryover) curnew = color_add(curnew, carryover); uint32_t prev = color_add(lastnew, part); - if (last != prev) // optimization: only set pixel if color has changed - setPixelColorXY(x - 1, row, prev); + // optimization: only set pixel if color has changed + if (last != prev) setPixelColorXY(x - 1, row, prev); } else // first pixel setPixelColorXY(x, row, curnew); lastnew = curnew; @@ -325,11 +324,10 @@ void Segment::blurCol(uint32_t col, fract8 blur_amount, bool smear) { uint32_t part = color_fade(cur, seep); curnew = color_fade(cur, keep); if (y > 0) { - if (carryover) - curnew = color_add(curnew, carryover); + if (carryover) curnew = color_add(curnew, carryover); uint32_t prev = color_add(lastnew, part); - if (last != prev) // optimization: only set pixel if color has changed - setPixelColorXY(col, y - 1, prev); + // optimization: only set pixel if color has changed + if (last != prev) setPixelColorXY(col, y - 1, prev); } else // first pixel setPixelColorXY(col, y, curnew); lastnew = curnew; diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 9fd78e314..545c92f25 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -628,13 +628,7 @@ uint16_t IRAM_ATTR Segment::virtualHeight() const { uint16_t IRAM_ATTR_YN Segment::nrOfVStrips() const { unsigned vLen = 1; #ifndef WLED_DISABLE_2D - if (is2D()) { - switch (map1D2D) { - case M12_pBar: - vLen = virtualWidth(); - break; - } - } + if (is2D() && map1D2D == M12_pBar) vLen = virtualWidth(); #endif return vLen; } @@ -710,17 +704,21 @@ uint16_t IRAM_ATTR Segment::virtualLength() const { void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col) { - if (!isActive()) return; // not active + if (!isActive() || i < 0) return; // not active or invalid index #ifndef WLED_DISABLE_2D int vStrip = 0; #endif - if (i >= virtualLength() || i<0) // pixel would fall out of segment, check if this is a virtual strip NOTE: this is almost always false if not virtual strip, saves the calculation on 'standard' call - { + // if the 1D effect is using virtual strips "i" will have virtual strip id stored in upper 16 bits + // in such case "i" will be > virtualLength() + if (i >= virtualLength()) { + // check if this is a virtual strip #ifndef WLED_DISABLE_2D vStrip = i>>16; // hack to allow running on virtual strips (2D segment columns/rows) + i &= 0xFFFF; //truncate vstrip index + if (i >= virtualLength()) return; // if pixel would still fall out of segment just exit + #else + return; #endif - i &= 0xFFFF; //truncate vstrip index - if (i >= virtualLength() || i<0) return; // if pixel would still fall out of segment just exit } #ifndef WLED_DISABLE_2D @@ -734,12 +732,12 @@ void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col) break; 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); + if (vStrip > 0) setPixelColorXY(vStrip - 1, vH - i - 1, col); + else for (int x = 0; x < vW; x++) setPixelColorXY(x, vH - i - 1, col); break; case M12_pArc: // expand in circular fashion from center - if (i==0) + if (i == 0) setPixelColorXY(0, 0, col); else { float r = i; @@ -910,9 +908,6 @@ void Segment::setPixelColor(float i, uint32_t col, bool aa) uint32_t IRAM_ATTR_YN Segment::getPixelColor(int i) const { if (!isActive()) return 0; // not active -#ifndef WLED_DISABLE_2D - int vStrip = i>>16; -#endif #ifndef WLED_DISABLE_2D if (is2D()) { @@ -922,10 +917,11 @@ uint32_t IRAM_ATTR_YN Segment::getPixelColor(int i) const case M12_Pixels: return getPixelColorXY(i % vW, i / vW); break; - case M12_pBar: - if (vStrip>0) { i &= 0xFFFF; return getPixelColorXY(vStrip - 1, vH - i -1); } - else return getPixelColorXY(0, vH - i -1); - break; + case M12_pBar: { + int vStrip = i>>16; // virtual strips are only relevant in Bar expansion mode + if (vStrip > 0) return getPixelColorXY(vStrip - 1, vH - (i & 0xFFFF) -1); + else return getPixelColorXY(0, vH - i -1); + break; } case M12_pArc: if (i >= vW && i >= vH) { unsigned vI = sqrt16(i*i/2); @@ -1884,14 +1880,6 @@ bool WS2812FX::deserializeMap(uint8_t n) { return (customMappingSize > 0); } -__attribute__ ((always_inline)) inline uint16_t WS2812FX::getMappedPixelIndex(uint16_t index) const { - // convert logical address to physical - if (index < customMappingSize - && (realtimeMode == REALTIME_MODE_INACTIVE || realtimeRespectLedMaps)) index = customMappingTable[index]; - - return index; -} - WS2812FX* WS2812FX::instance = nullptr; diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index 8983da65b..00e219138 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -437,9 +437,9 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { if (light_gc_col > 1.0f) gammaCorrectCol = true; else gammaCorrectCol = false; if (gammaCorrectVal <= 1.0f || gammaCorrectVal > 3) { - gammaCorrectVal = 1.0f; // no gamma correction - gammaCorrectBri = false; - gammaCorrectCol = false; + gammaCorrectVal = 1.0f; // no gamma correction + gammaCorrectBri = false; + gammaCorrectCol = false; } NeoGammaWLEDMethod::calcGammaTable(gammaCorrectVal); // fill look-up table diff --git a/wled00/colors.cpp b/wled00/colors.cpp index 13405be6e..233d3d116 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -34,9 +34,9 @@ uint32_t color_blend(uint32_t color1, uint32_t color2, uint16_t blend, bool b16) /* * color add function that preserves ratio * original idea: https://github.com/Aircoookie/WLED/pull/2465 by https://github.com/Proto-molecule - * heavily optimized for speed by @dedehai + * speed optimisations by @dedehai */ -uint32_t color_add(uint32_t c1, uint32_t c2, bool desat) +uint32_t color_add(uint32_t c1, uint32_t c2, bool preserveCR) { if (c1 == BLACK) return c2; if (c2 == BLACK) return c1; @@ -47,21 +47,21 @@ uint32_t color_add(uint32_t c1, uint32_t c2, bool desat) uint32_t w = wg >> 16; uint32_t g = wg & 0xFFFF; - if(desat) { // desaturate - unsigned max = r; // check for overflow note - max = g > max ? g : max; - max = b > max ? b : max; - max = w > max ? w : max; - + if (preserveCR) { // preserve color ratios + unsigned max = std::max(r,g); // check for overflow note + max = std::max(max,b); + max = std::max(max,w); + //unsigned max = r; // check for overflow note + //max = g > max ? g : max; + //max = b > max ? b : max; + //max = w > max ? w : max; if (max > 255) { uint32_t scale = (uint32_t(255)<<8) / max; // division of two 8bit (shifted) values does not work -> use bit shifts and multiplaction instead rb = ((rb * scale) >> 8) & 0x00FF00FF; // wg = (wg * scale) & 0xFF00FF00; - } - else wg = wg << 8; //shift white and green back to correct position + } else wg = wg << 8; //shift white and green back to correct position return rb | wg; - } - else { + } else { r = r > 255 ? 255 : r; g = g > 255 ? 255 : g; b = b > 255 ? 255 : b; @@ -106,20 +106,20 @@ CRGB ColorFromPaletteWLED(const CRGBPalette16& pal, unsigned index, uint8_t brig unsigned red1 = entry->r; unsigned green1 = entry->g; unsigned blue1 = entry->b; - if(blendType != NOBLEND) { - if(hi4 == 15) entry = &(pal[0]); - else ++entry; - unsigned f2 = ((index & 0x0F) << 4) + 1; // +1 so we scale by 256 as a max value, then result can just be shifted by 8 - unsigned f1 = (257 - f2); // f2 is 1 minimum, so this is 256 max - red1 = (red1 * f1 + (unsigned)entry->r * f2) >> 8; - green1 = (green1 * f1 + (unsigned)entry->g * f2) >> 8; - blue1 = (blue1 * f1 + (unsigned)entry->b * f2) >> 8; + if (blendType != NOBLEND) { + if (hi4 == 15) entry = &(pal[0]); + else ++entry; + unsigned f2 = ((index & 0x0F) << 4) + 1; // +1 so we scale by 256 as a max value, then result can just be shifted by 8 + unsigned f1 = (257 - f2); // f2 is 1 minimum, so this is 256 max + red1 = (red1 * f1 + (unsigned)entry->r * f2) >> 8; + green1 = (green1 * f1 + (unsigned)entry->g * f2) >> 8; + blue1 = (blue1 * f1 + (unsigned)entry->b * f2) >> 8; } - if( brightness < 255) { // note: zero checking could be done to return black but that is hardly ever used so it is omitted - uint32_t scale = brightness + 1; // adjust for rounding (bitshift) - red1 = (red1 * scale) >> 8; - green1 = (green1 * scale) >> 8; - blue1 = (blue1 * scale) >> 8; + if (brightness < 255) { // note: zero checking could be done to return black but that is hardly ever used so it is omitted + uint32_t scale = brightness + 1; // adjust for rounding (bitshift) + red1 = (red1 * scale) >> 8; + green1 = (green1 * scale) >> 8; + blue1 = (blue1 * scale) >> 8; } return CRGB((uint8_t)red1, (uint8_t)green1, (uint8_t)blue1); } @@ -485,7 +485,7 @@ uint16_t approximateKelvinFromRGB(uint32_t rgb) { } } -//gamma 2.8 lookup table used for color correction +// gamma lookup table used for color correction (filled on 1st use (cfg.cpp & set.cpp)) uint8_t NeoGammaWLEDMethod::gammaT[256]; // re-calculates & fills gamma table diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index d32356d7b..741e1eefd 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -80,7 +80,7 @@ class NeoGammaWLEDMethod { #define gamma32(c) NeoGammaWLEDMethod::Correct32(c) #define gamma8(c) NeoGammaWLEDMethod::rawGamma8(c) [[gnu::hot]] uint32_t color_blend(uint32_t, uint32_t, uint16_t, bool b16=false); -[[gnu::hot]] uint32_t color_add(uint32_t, uint32_t, bool desat = false); +[[gnu::hot]] uint32_t color_add(uint32_t, uint32_t, bool preserveCR = false); [[gnu::hot]] uint32_t color_fade(uint32_t c1, uint8_t amount, bool video=false); [[gnu::hot]] CRGB ColorFromPaletteWLED(const CRGBPalette16 &pal, unsigned index, uint8_t brightness = (uint8_t)255U, TBlendType blendType = LINEARBLEND); CRGBPalette16 generateHarmonicRandomPalette(CRGBPalette16 &basepalette); From 906f8fc2e72cc901be7136fe533a40a27a97fd2d Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Wed, 25 Sep 2024 18:49:10 +0200 Subject: [PATCH 039/287] Fix C3 compiler issue. --- wled00/colors.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/colors.cpp b/wled00/colors.cpp index 233d3d116..00f184601 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -48,7 +48,7 @@ uint32_t color_add(uint32_t c1, uint32_t c2, bool preserveCR) uint32_t g = wg & 0xFFFF; if (preserveCR) { // preserve color ratios - unsigned max = std::max(r,g); // check for overflow note + uint32_t max = std::max(r,g); // check for overflow note max = std::max(max,b); max = std::max(max,w); //unsigned max = r; // check for overflow note From bef1ac2668eb17312549e637f81c3954ac3052cd Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Wed, 25 Sep 2024 19:36:20 +0200 Subject: [PATCH 040/287] Added HSV2RGB and RGB2HSV functions for higher accuracy conversions - also added a struct to handle HSV with 16bit hue better (including some conversions, can be extended easily) - the functions are optimized for speed and flash use. They are faster and more accurate than what fastled offers (and use much less flash). - replaced colorHStoRGB() with a call to the new hsv2rgb() function, saving even more flash (new function is untested!) - the 16bit hue calculations result in an almost perfect conversion from RGB to HSV and back, the maximum error was 1/255 in the cases I tested. --- wled00/colors.cpp | 74 +++++++++++++++++++++++++++++++++----------- wled00/fcn_declare.h | 29 ++++++++++++++++- wled00/ir.cpp | 4 +-- 3 files changed, 86 insertions(+), 21 deletions(-) diff --git a/wled00/colors.cpp b/wled00/colors.cpp index 00f184601..163429f7f 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -239,26 +239,64 @@ CRGBPalette16 generateRandomPalette() //generate fully random palette CHSV(random8(), random8(160, 255), random8(128, 255))); } -void colorHStoRGB(uint16_t hue, byte sat, byte* rgb) //hue, sat to rgb +void hsv2rgb(const CHSV32& hsv, uint32_t& rgb) // convert HSV (16bit hue) to RGB (32bit with white = 0) { - float h = ((float)hue)/10922.5f; // hue*6/65535 - float s = ((float)sat)/255.0f; - int i = int(h); - float f = h - i; - int p = int(255.0f * (1.0f-s)); - int q = int(255.0f * (1.0f-s*f)); - int t = int(255.0f * (1.0f-s*(1.0f-f))); - p = constrain(p, 0, 255); - q = constrain(q, 0, 255); - t = constrain(t, 0, 255); - switch (i%6) { - case 0: rgb[0]=255,rgb[1]=t, rgb[2]=p; break; - case 1: rgb[0]=q, rgb[1]=255,rgb[2]=p; break; - case 2: rgb[0]=p, rgb[1]=255,rgb[2]=t; break; - case 3: rgb[0]=p, rgb[1]=q, rgb[2]=255;break; - case 4: rgb[0]=t, rgb[1]=p, rgb[2]=255;break; - case 5: rgb[0]=255,rgb[1]=p, rgb[2]=q; break; + unsigned int remainder, region, p, q, t; + unsigned int h = hsv.h; + unsigned int s = hsv.s; + unsigned int v = hsv.v; + if (s == 0) { + rgb = v << 16 | v << 8 | v; + return; } + region = h / 10923; // 65536 / 6 = 10923 + remainder = (h - (region * 10923)) * 6; + p = (v * (256 - s)) >> 8; + q = (v * (255 - ((s * remainder) >> 16))) >> 8; + t = (v * (255 - ((s * (65535 - remainder)) >> 16))) >> 8; + switch (region) { + case 0: + rgb = v << 16 | t << 8 | p; break; + case 1: + rgb = q << 16 | v << 8 | p; break; + case 2: + rgb = p << 16 | v << 8 | t; break; + case 3: + rgb = p << 16 | q << 8 | v; break; + case 4: + rgb = t << 16 | p << 8 | v; break; + default: + rgb = v << 16 | p << 8 | q; break; + } +} + +void rgb2hsv(const uint32_t rgb, CHSV32& hsv) // convert RGB to HSV (16bit hue), much more accurate and faster than fastled version +{ + hsv.raw = 0; + int32_t r = (rgb>>16)&0xFF; + int32_t g = (rgb>>8)&0xFF; + int32_t b = rgb&0xFF; + int32_t minval, maxval, delta; + minval = min(r, g); + minval = min(minval, b); + maxval = max(r, g); + maxval = max(maxval, b); + if (maxval == 0) return; // black + hsv.v = maxval; + delta = maxval - minval; + hsv.s = (255 * delta) / maxval; + if (hsv.s == 0) return; // gray value + if (maxval == r) hsv.h = (10923 * (g - b)) / delta; + else if (maxval == g) hsv.h = 21845 + (10923 * (b - r)) / delta; + else hsv.h = 43690 + (10923 * (r - g)) / delta; +} + +void colorHStoRGB(uint16_t hue, byte sat, byte* rgb) { //hue, sat to rgb + uint32_t crgb; + hsv2rgb(CHSV32(hue, sat, 255), crgb); + rgb[0] = byte((crgb) >> 16); + rgb[1] = byte((crgb) >> 8); + rgb[2] = byte(crgb); } //get RGB values from color temperature in K (https://tannerhelland.com/2012/09/18/convert-temperature-rgb-algorithm-code.html) diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 741e1eefd..b5bd1c28e 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -67,6 +67,30 @@ typedef struct WiFiConfig { //colors.cpp #define ColorFromPalette ColorFromPaletteWLED // override fastled version + +struct CHSV32 { // 32bit HSV color with 16bit hue for more accurate conversions + union { + struct { + uint16_t h; // hue + uint8_t s; // saturation + uint8_t v; // value + }; + uint32_t raw; // 32bit access + }; + inline CHSV32() __attribute__((always_inline)) = default; // default constructor + + /// Allow construction from hue, saturation, and value + /// @param ih input hue + /// @param is input saturation + /// @param iv input value + inline CHSV32(uint16_t ih, uint8_t is, uint8_t iv) __attribute__((always_inline)) // constructor from 16bit h, s, v + : h(ih), s(is), v(iv) {} + inline CHSV32(uint8_t ih, uint8_t is, uint8_t iv) __attribute__((always_inline)) // constructor from 8bit h, s, v + : h((uint16_t)ih << 8), s(is), v(iv) {} + inline CHSV32(const CHSV& chsv) __attribute__((always_inline)) // constructor from CHSV + : h((uint16_t)chsv.h << 8), s(chsv.s), v(chsv.v) {} + inline operator CHSV() const { return CHSV((uint8_t)(h >> 8), s, v); } // typecast to CHSV +}; // similar to NeoPixelBus NeoGammaTableMethod but allows dynamic changes (superseded by NPB::NeoGammaDynamicTableMethod) class NeoGammaWLEDMethod { public: @@ -86,7 +110,10 @@ class NeoGammaWLEDMethod { CRGBPalette16 generateHarmonicRandomPalette(CRGBPalette16 &basepalette); CRGBPalette16 generateRandomPalette(); inline uint32_t colorFromRgbw(byte* rgbw) { return uint32_t((byte(rgbw[3]) << 24) | (byte(rgbw[0]) << 16) | (byte(rgbw[1]) << 8) | (byte(rgbw[2]))); } -void colorHStoRGB(uint16_t hue, byte sat, byte* rgb); //hue, sat to rgb +void hsv2rgb(const CHSV32& hsv, uint32_t& rgb); +void colorHStoRGB(uint16_t hue, byte sat, byte* rgb); +void rgb2hsv(const uint32_t rgb, CHSV32& hsv); +inline CHSV rgb2hsv(const CRGB c) { CHSV32 hsv; rgb2hsv((uint32_t((byte(c.r) << 16) | (byte(c.g) << 8) | (byte(c.b)))), hsv); return CHSV(hsv); } // CRGB to hsv void colorKtoRGB(uint16_t kelvin, byte* rgb); void colorCTtoRGB(uint16_t mired, byte* rgb); //white spectrum to rgb void colorXYtoRGB(float x, float y, byte* rgb); // only defined if huesync disabled TODO diff --git a/wled00/ir.cpp b/wled00/ir.cpp index e4541cd90..f094d3b87 100644 --- a/wled00/ir.cpp +++ b/wled00/ir.cpp @@ -129,7 +129,7 @@ static void changeEffectSpeed(int8_t amount) } else { // if Effect == "solid Color", change the hue of the primary color Segment& sseg = irApplyToAllSelected ? strip.getFirstSelectedSeg() : strip.getMainSegment(); CRGB fastled_col = CRGB(sseg.colors[0]); - CHSV prim_hsv = rgb2hsv_approximate(fastled_col); + CHSV prim_hsv = rgb2hsv(fastled_col); int16_t new_val = (int16_t)prim_hsv.h + amount; if (new_val > 255) new_val -= 255; // roll-over if bigger than 255 if (new_val < 0) new_val += 255; // roll-over if smaller than 0 @@ -173,7 +173,7 @@ static void changeEffectIntensity(int8_t amount) } else { // if Effect == "solid Color", change the saturation of the primary color Segment& sseg = irApplyToAllSelected ? strip.getFirstSelectedSeg() : strip.getMainSegment(); CRGB fastled_col = CRGB(sseg.colors[0]); - CHSV prim_hsv = rgb2hsv_approximate(fastled_col); + CHSV prim_hsv = rgb2hsv(fastled_col); int16_t new_val = (int16_t) prim_hsv.s + amount; prim_hsv.s = (byte)constrain(new_val,0,255); // constrain to 0-255 hsv2rgb_rainbow(prim_hsv, fastled_col); From b40445836931f769dce87168ff80c0ec489afc2c Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Thu, 26 Sep 2024 18:29:31 +0200 Subject: [PATCH 041/287] fixed one forgotten replacement of rgb2hsv_approximate --- wled00/colors.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/colors.cpp b/wled00/colors.cpp index 163429f7f..d705b9a7f 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -138,7 +138,7 @@ CRGBPalette16 generateHarmonicRandomPalette(CRGBPalette16 &basepalette) { CHSV palettecolors[4]; //array of colors for the new palette uint8_t keepcolorposition = random8(4); //color position of current random palette to keep - palettecolors[keepcolorposition] = rgb2hsv_approximate(basepalette.entries[keepcolorposition*5]); //read one of the base colors of the current palette + palettecolors[keepcolorposition] = rgb2hsv(basepalette.entries[keepcolorposition*5]); //read one of the base colors of the current palette palettecolors[keepcolorposition].hue += random8(10)-5; // +/- 5 randomness of base color //generate 4 saturation and brightness value numbers //only one saturation is allowed to be below 200 creating mostly vibrant colors From a76a895f1d86f9405b94fca5dc91a6649bdf48dd Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Fri, 27 Sep 2024 06:17:26 +0200 Subject: [PATCH 042/287] bugfix --- wled00/colors.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/colors.cpp b/wled00/colors.cpp index d705b9a7f..1b6a6f8d9 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -251,7 +251,7 @@ void hsv2rgb(const CHSV32& hsv, uint32_t& rgb) // convert HSV (16bit hue) to RGB } region = h / 10923; // 65536 / 6 = 10923 remainder = (h - (region * 10923)) * 6; - p = (v * (256 - s)) >> 8; + p = (v * (255 - s)) >> 8; q = (v * (255 - ((s * remainder) >> 16))) >> 8; t = (v * (255 - ((s * (65535 - remainder)) >> 16))) >> 8; switch (region) { From 7c0fe1285aa21e20b16c1185e93fc5e65cd32b3c Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Sat, 28 Sep 2024 15:26:14 +0200 Subject: [PATCH 043/287] updated setPixelColor() and getPixelColor() functions uint16_t to unsigned to make it consisten throughout the hand-down. colorFromPaletteWLED now returns uint32_t which saves the conversion to CRGB and back to uint32_t (in most uses at least). also added (preliminary) CRGBW struct. I tried to use it in place of uint32_t colors but it adds a lot of overhead when passing the struct so reverted to uint32_t in most places. updated a few FX to use the CRGBW struct and also cleaned some code to improve flash useage. --- wled00/FX.cpp | 92 ++++++++++++++++++------------------------ wled00/FX.h | 6 +-- wled00/FX_2Dfcn.cpp | 2 +- wled00/FX_fcn.cpp | 7 ++-- wled00/bus_manager.cpp | 20 ++++----- wled00/bus_manager.h | 24 +++++------ wled00/colors.cpp | 4 +- wled00/fcn_declare.h | 60 ++++++++++++++++++++++++++- 8 files changed, 129 insertions(+), 86 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index ad843f0f9..3df91547d 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -1937,7 +1937,7 @@ uint16_t mode_juggle(void) { for (int i = 0; i < 8; i++) { int index = 0 + beatsin88((16 + SEGMENT.speed)*(i + 7), 0, SEGLEN -1); fastled_col = CRGB(SEGMENT.getPixelColor(index)); - fastled_col |= (SEGMENT.palette==0)?CHSV(dothue, 220, 255):ColorFromPalette(SEGPALETTE, dothue, 255); + fastled_col |= (SEGMENT.palette==0)?CHSV(dothue, 220, 255):CRGB(ColorFromPalette(SEGPALETTE, dothue, 255)); SEGMENT.setPixelColor(index, fastled_col); dothue += 32; } @@ -2282,33 +2282,33 @@ uint16_t mode_colortwinkle() { unsigned dataSize = (SEGLEN+7) >> 3; //1 bit per LED if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed - CRGB fastled_col, prev; + CRGBW col, prev; fract8 fadeUpAmount = strip.getBrightness()>28 ? 8 + (SEGMENT.speed>>2) : 68-strip.getBrightness(); fract8 fadeDownAmount = strip.getBrightness()>28 ? 8 + (SEGMENT.speed>>3) : 68-strip.getBrightness(); for (int i = 0; i < SEGLEN; i++) { - fastled_col = SEGMENT.getPixelColor(i); - prev = fastled_col; + CRGBW cur = SEGMENT.getPixelColor(i); + prev = cur; unsigned index = i >> 3; unsigned bitNum = i & 0x07; bool fadeUp = bitRead(SEGENV.data[index], bitNum); if (fadeUp) { - CRGB incrementalColor = fastled_col; - incrementalColor.nscale8_video(fadeUpAmount); - fastled_col += incrementalColor; + CRGBW incrementalColor = color_fade(col, fadeUpAmount, true); + col = color_add(cur, incrementalColor); - if (fastled_col.red == 255 || fastled_col.green == 255 || fastled_col.blue == 255) { + if (col.r == 255 || col.g == 255 || col.b == 255) { bitWrite(SEGENV.data[index], bitNum, false); } - SEGMENT.setPixelColor(i, fastled_col.red, fastled_col.green, fastled_col.blue); - if (SEGMENT.getPixelColor(i) == RGBW32(prev.r, prev.g, prev.b, 0)) { //fix "stuck" pixels - fastled_col += fastled_col; - SEGMENT.setPixelColor(i, fastled_col); + if (cur == prev) { //fix "stuck" pixels + color_add(col, col); + SEGMENT.setPixelColor(i, col); } - } else { - fastled_col.nscale8(255 - fadeDownAmount); - SEGMENT.setPixelColor(i, fastled_col); + else SEGMENT.setPixelColor(i, col); + } + else { + col = color_fade(cur, 255 - fadeDownAmount); + SEGMENT.setPixelColor(i, col); } } @@ -2317,11 +2317,10 @@ uint16_t mode_colortwinkle() { for (unsigned times = 0; times < 5; times++) { //attempt to spawn a new pixel 5 times int i = random16(SEGLEN); if (SEGMENT.getPixelColor(i) == 0) { - fastled_col = ColorFromPalette(SEGPALETTE, random8(), 64, NOBLEND); unsigned index = i >> 3; unsigned bitNum = i & 0x07; bitWrite(SEGENV.data[index], bitNum, true); - SEGMENT.setPixelColor(i, fastled_col); + SEGMENT.setPixelColor(i, ColorFromPalette(SEGPALETTE, random8(), 64, NOBLEND)); break; //only spawn 1 new pixel per frame per 50 LEDs } } @@ -2378,8 +2377,7 @@ uint16_t mode_meteor() { index = map(i,0,SEGLEN,0,max); bri = trail[i]; } - uint32_t col = SEGMENT.color_from_palette(index, false, false, idx, bri); // full brightness for Fire - SEGMENT.setPixelColor(i, col); + SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(index, false, false, idx, bri)); // full brightness for Fire } } @@ -2392,8 +2390,7 @@ uint16_t mode_meteor() { i = map(index,0,SEGLEN,0,max); idx = 0; } - uint32_t col = SEGMENT.color_from_palette(i, false, false, idx, 255); // full brightness - SEGMENT.setPixelColor(index, col); + SEGMENT.setPixelColor(index, SEGMENT.color_from_palette(i, false, false, idx, 255)); // full brightness } return FRAMETIME; @@ -2419,8 +2416,7 @@ uint16_t mode_meteor_smooth() { if (/*trail[i] != 0 &&*/ random8() <= 255 - SEGMENT.intensity) { int change = trail[i] + 4 - random8(24); //change each time between -20 and +4 trail[i] = constrain(change, 0, max); - uint32_t col = SEGMENT.check1 ? SEGMENT.color_from_palette(i, true, false, 0, trail[i]) : SEGMENT.color_from_palette(trail[i], false, true, 255); - SEGMENT.setPixelColor(i, col); + SEGMENT.setPixelColor(i, SEGMENT.check1 ? SEGMENT.color_from_palette(i, true, false, 0, trail[i]) : SEGMENT.color_from_palette(trail[i], false, true, 255)); } } @@ -2431,8 +2427,7 @@ uint16_t mode_meteor_smooth() { index -= SEGLEN; } trail[index] = max; - uint32_t col = SEGMENT.check1 ? SEGMENT.color_from_palette(index, true, false, 0, trail[index]) : SEGMENT.color_from_palette(trail[index], false, true, 255); - SEGMENT.setPixelColor(index, col); + SEGMENT.setPixelColor(index, SEGMENT.check1 ? SEGMENT.color_from_palette(index, true, false, 0, trail[index]) : SEGMENT.color_from_palette(trail[index], false, true, 255)); } SEGENV.step += SEGMENT.speed +1; @@ -2680,7 +2675,7 @@ static uint16_t twinklefox_base(bool cat) if (deltabright >= 32 || (!bg)) { // If the new pixel is significantly brighter than the background color, // use the new color. - SEGMENT.setPixelColor(i, c.red, c.green, c.blue); + SEGMENT.setPixelColor(i, c); } else if (deltabright > 0) { // If the new pixel is just slightly brighter than the background color, // mix a blend of the new color and the background color @@ -2688,7 +2683,7 @@ static uint16_t twinklefox_base(bool cat) } else { // if the new pixel is not at all brighter than the background color, // just use the background color. - SEGMENT.setPixelColor(i, bg.r, bg.g, bg.b); + SEGMENT.setPixelColor(i, bg); } } return FRAMETIME; @@ -3532,7 +3527,7 @@ uint16_t mode_starburst(void) { if (start == end) end++; if (end > SEGLEN) end = SEGLEN; for (int p = start; p < end; p++) { - SEGMENT.setPixelColor(p, c.r, c.g, c.b); + SEGMENT.setPixelColor(p, c); } } } @@ -3650,17 +3645,17 @@ uint16_t mode_exploding_fireworks(void) if (SEGMENT.is2D() && !(sparks[i].posX >= 0 && sparks[i].posX < cols)) continue; unsigned prog = sparks[i].col; uint32_t spColor = (SEGMENT.palette) ? SEGMENT.color_wheel(sparks[i].colIndex) : SEGCOLOR(0); - CRGB c = CRGB::Black; //HeatColor(sparks[i].col); + CRGBW c = BLACK; //HeatColor(sparks[i].col); if (prog > 300) { //fade from white to spark color - c = CRGB(color_blend(spColor, WHITE, (prog - 300)*5)); + c = color_blend(spColor, WHITE, (prog - 300)*5); } else if (prog > 45) { //fade from spark color to black - c = CRGB(color_blend(BLACK, spColor, prog - 45)); + c = color_blend(BLACK, spColor, prog - 45); unsigned cooling = (300 - prog) >> 5; c.g = qsub8(c.g, cooling); c.b = qsub8(c.b, cooling * 2); } - if (SEGMENT.is2D()) SEGMENT.setPixelColorXY(int(sparks[i].posX), rows - int(sparks[i].pos) - 1, c.red, c.green, c.blue); - else SEGMENT.setPixelColor(int(sparks[i].posX) ? rows - int(sparks[i].pos) - 1 : int(sparks[i].pos), c.red, c.green, c.blue); + if (SEGMENT.is2D()) SEGMENT.setPixelColorXY(int(sparks[i].posX), rows - int(sparks[i].pos) - 1, c); + else SEGMENT.setPixelColor(int(sparks[i].posX) ? rows - int(sparks[i].pos) - 1 : int(sparks[i].pos), c); } } if (SEGMENT.check3) SEGMENT.blur(16); @@ -4006,7 +4001,7 @@ static CRGB pacifica_one_layer(uint16_t i, CRGBPalette16& p, uint16_t cistart, u ci += (cs * i); unsigned sindex16 = sin16(ci) + 32768; unsigned sindex8 = scale16(sindex16, 240); - return ColorFromPalette(p, sindex8, bri, LINEARBLEND); + return CRGB(ColorFromPalette(p, sindex8, bri, LINEARBLEND)); } uint16_t mode_pacifica() @@ -4077,7 +4072,7 @@ uint16_t mode_pacifica() c.green = scale8(c.green, 200); c |= CRGB( 2, 5, 7); - SEGMENT.setPixelColor(i, c.red, c.green, c.blue); + SEGMENT.setPixelColor(i, c); } strip.now = nowOld; @@ -4119,13 +4114,10 @@ uint16_t mode_sunrise() { for (int i = 0; i <= SEGLEN/2; i++) { - //default palette is Fire - uint32_t c = SEGMENT.color_from_palette(0, false, true, 255); //background - + //default palette is Fire unsigned wave = triwave16((i * stage) / SEGLEN); - wave = (wave >> 8) + ((wave * SEGMENT.intensity) >> 15); - + uint32_t c; if (wave > 240) { //clipped, full white sun c = SEGMENT.color_from_palette( 240, false, true, 255); } else { //transition @@ -4217,8 +4209,6 @@ uint16_t mode_noisepal(void) { // Slow noise palettes[1] = CRGBPalette16(CHSV(baseI+random8(64), 255, random8(128,255)), CHSV(baseI+128, 255, random8(128,255)), CHSV(baseI+random8(92), 192, random8(128,255)), CHSV(baseI+random8(92), 255, random8(128,255))); } - CRGB color; - //EVERY_N_MILLIS(10) { //(don't have to time this, effect function is only called every 24ms) nblendPaletteTowardPalette(palettes[0], palettes[1], 48); // Blend towards the target palette over 48 iterations. @@ -4226,8 +4216,7 @@ uint16_t mode_noisepal(void) { // Slow noise for (int i = 0; i < SEGLEN; i++) { unsigned index = inoise8(i*scale, SEGENV.aux0+i*scale); // Get a value from the noise function. I'm using both x and y axis. - color = ColorFromPalette(palettes[0], index, 255, LINEARBLEND); // Use the my own palette. - SEGMENT.setPixelColor(i, color.red, color.green, color.blue); + SEGMENT.setPixelColor(i, ColorFromPalette(palettes[0], index, 255, LINEARBLEND)); // Use my own palette. } SEGENV.aux0 += beatsin8(10,1,4); // Moving along the distance. Vary it a bit with a sine wave. @@ -4314,9 +4303,8 @@ uint16_t mode_chunchun(void) counter -= span; unsigned megumin = sin16(counter) + 0x8000; unsigned bird = uint32_t(megumin * SEGLEN) >> 16; - uint32_t c = SEGMENT.color_from_palette((i * 255)/ numBirds, false, false, 0); // no palette wrapping bird = constrain(bird, 0U, SEGLEN-1U); - SEGMENT.setPixelColor(bird, c); + SEGMENT.setPixelColor(bird, SEGMENT.color_from_palette((i * 255)/ numBirds, false, false, 0)); // no palette wrapping } return FRAMETIME; } @@ -4934,7 +4922,7 @@ uint16_t mode_2DColoredBursts() { // By: ldirko https://editor.so byte x2 = beatsin8(1 + SEGMENT.speed/16, 0, (cols - 1)); byte y1 = beatsin8(5 + SEGMENT.speed/16, 0, (rows - 1), 0, i * 24); byte y2 = beatsin8(3 + SEGMENT.speed/16, 0, (rows - 1), 0, i * 48 + 64); - CRGB color = ColorFromPalette(SEGPALETTE, i * 255 / numLines + (SEGENV.aux0&0xFF), 255, LINEARBLEND); + uint32_t color = ColorFromPalette(SEGPALETTE, i * 255 / numLines + (SEGENV.aux0&0xFF), 255, LINEARBLEND); byte xsteps = abs8(x1 - y1) + 1; byte ysteps = abs8(x2 - y2) + 1; @@ -5831,7 +5819,7 @@ uint16_t mode_2Dspaceships(void) { //// Space ships by stepko (c)05.02.21 [ht for (size_t i = 0; i < 8; i++) { int x = beatsin8(12 + i, 2, cols - 3); int y = beatsin8(15 + i, 2, rows - 3); - CRGB color = ColorFromPalette(SEGPALETTE, beatsin8(12 + i, 0, 255), 255); + uint32_t color = ColorFromPalette(SEGPALETTE, beatsin8(12 + i, 0, 255), 255); SEGMENT.addPixelColorXY(x, y, color); if (cols > 24 || rows > 24) { SEGMENT.addPixelColorXY(x+1, y, color); @@ -6725,8 +6713,7 @@ uint16_t mode_noisefire(void) { // Noisefire. By Andrew Tuline. index = (255 - i*256/SEGLEN) * index/(256-SEGMENT.intensity); // Now we need to scale index so that it gets blacker as we get close to one of the ends. // This is a simple y=mx+b equation that's been scaled. index/128 is another scaling. - CRGB color = ColorFromPalette(myPal, index, volumeSmth*2, LINEARBLEND); // Use the my own palette. - SEGMENT.setPixelColor(i, color); + SEGMENT.setPixelColor(i, ColorFromPalette(myPal, index, volumeSmth*2, LINEARBLEND)); // Use my own palette. } return FRAMETIME; @@ -7532,7 +7519,7 @@ uint16_t mode_2DAkemi(void) { unsigned band = x * cols/8; band = constrain(band, 0, 15); int barHeight = map(fftResult[band], 0, 255, 0, 17*rows/32); - CRGB color = CRGB(SEGMENT.color_from_palette((band * 35), false, PALETTE_SOLID_WRAP, 0)); + uint32_t color = SEGMENT.color_from_palette((band * 35), false, PALETTE_SOLID_WRAP, 0); for (int y=0; y < barHeight; y++) { SEGMENT.setPixelColorXY(x, rows/2-y, color); @@ -7760,8 +7747,7 @@ uint16_t mode_2Doctopus() { //CRGB c = CHSV(SEGENV.step / 2 - radius, 255, sin8(sin8((angle * 4 - radius) / 4 + SEGENV.step) + radius - SEGENV.step * 2 + angle * (SEGMENT.custom3/3+1))); unsigned intensity = sin8(sin8((angle * 4 - radius) / 4 + SEGENV.step/2) + radius - SEGENV.step + angle * (SEGMENT.custom3/4+1)); intensity = map((intensity*intensity) & 0xFFFF, 0, 65535, 0, 255); // add a bit of non-linearity for cleaner display - CRGB c = ColorFromPalette(SEGPALETTE, SEGENV.step / 2 - radius, intensity); - SEGMENT.setPixelColorXY(x, y, c); + SEGMENT.setPixelColorXY(x, y, ColorFromPalette(SEGPALETTE, SEGENV.step / 2 - radius, intensity)); } } return FRAMETIME; diff --git a/wled00/FX.h b/wled00/FX.h index 989dfbe33..825c722e7 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -851,10 +851,8 @@ class WS2812FX { // 96 bytes return index; }; - uint32_t - now, - timebase, - getPixelColor(uint16_t) const; + uint32_t now, timebase; + uint32_t getPixelColor(unsigned) const; inline uint32_t getLastShow() const { return _lastShow; } // returns millis() timestamp of last strip.show() call inline uint32_t segColor(uint8_t i) const { return _colors_t[i]; } // returns currently valid color (for slot i) AKA SEGCOLOR(); may be blended between two colors while in transition diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index ca28a9586..41fd67319 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -671,7 +671,7 @@ void Segment::drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, case 60: bits = pgm_read_byte_near(&console_font_5x12[(chr * h) + i]); break; // 5x12 font default: return; } - col = ColorFromPalette(grad, (i+1)*255/h, 255, NOBLEND); + uint32_t col = ColorFromPaletteWLED(grad, (i+1)*255/h, 255, NOBLEND); for (int j = 0; j 1) paletteIndex = (i*255)/(virtualLength() -1); // paletteBlend: 0 - wrap when moving, 1 - always wrap, 2 - never wrap, 3 - none (undefined) if (!wrap && strip.paletteBlend != 3) paletteIndex = scale8(paletteIndex, 240); //cut off blend at palette "end" - CRGB fastled_col = ColorFromPalette(_currentPalette, paletteIndex, pbri, (strip.paletteBlend == 3)? NOBLEND:LINEARBLEND); // NOTE: paletteBlend should be global + CRGBW palcol = ColorFromPalette(_currentPalette, paletteIndex, pbri, (strip.paletteBlend == 3)? NOBLEND:LINEARBLEND); // NOTE: paletteBlend should be global + palcol.w = gamma8(W(color)); - return RGBW32(fastled_col.r, fastled_col.g, fastled_col.b, gamma8(W(color))); + return palcol.color32; } @@ -1419,7 +1420,7 @@ void IRAM_ATTR WS2812FX::setPixelColor(unsigned i, uint32_t col) { BusManager::setPixelColor(i, col); } -uint32_t IRAM_ATTR WS2812FX::getPixelColor(uint16_t i) const { +uint32_t IRAM_ATTR WS2812FX::getPixelColor(unsigned i) const { i = getMappedPixelIndex(i); if (i >= _length) return 0; return BusManager::getPixelColor(i); diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index 5b948b9c4..404c33449 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -306,7 +306,7 @@ void BusDigital::setStatusPixel(uint32_t c) { } } -void IRAM_ATTR BusDigital::setPixelColor(uint16_t pix, uint32_t c) { +void IRAM_ATTR BusDigital::setPixelColor(unsigned pix, uint32_t c) { if (!_valid) return; uint8_t cctWW = 0, cctCW = 0; if (hasWhite()) c = autoWhiteCalc(c); @@ -342,7 +342,7 @@ void IRAM_ATTR BusDigital::setPixelColor(uint16_t pix, uint32_t c) { } // returns original color if global buffering is enabled, else returns lossly restored color from bus -uint32_t IRAM_ATTR BusDigital::getPixelColor(uint16_t pix) const { +uint32_t IRAM_ATTR BusDigital::getPixelColor(unsigned pix) const { if (!_valid) return 0; if (_data) { size_t offset = pix * getNumberOfChannels(); @@ -501,7 +501,7 @@ BusPwm::BusPwm(BusConfig &bc) DEBUG_PRINTF_P(PSTR("%successfully inited PWM strip with type %u, frequency %u, bit depth %u and pins %u,%u,%u,%u,%u\n"), _valid?"S":"Uns", bc.type, _frequency, _depth, _pins[0], _pins[1], _pins[2], _pins[3], _pins[4]); } -void BusPwm::setPixelColor(uint16_t pix, uint32_t c) { +void BusPwm::setPixelColor(unsigned pix, uint32_t c) { if (pix != 0 || !_valid) return; //only react to first pixel if (_type != TYPE_ANALOG_3CH) c = autoWhiteCalc(c); if (Bus::_cct >= 1900 && (_type == TYPE_ANALOG_3CH || _type == TYPE_ANALOG_4CH)) { @@ -538,7 +538,7 @@ void BusPwm::setPixelColor(uint16_t pix, uint32_t c) { } //does no index check -uint32_t BusPwm::getPixelColor(uint16_t pix) const { +uint32_t BusPwm::getPixelColor(unsigned pix) const { if (!_valid) return 0; // TODO getting the reverse from CCT is involved (a quick approximation when CCT blending is ste to 0 implemented) switch (_type) { @@ -674,7 +674,7 @@ BusOnOff::BusOnOff(BusConfig &bc) DEBUG_PRINTF_P(PSTR("%successfully inited On/Off strip with pin %u\n"), _valid?"S":"Uns", _pin); } -void BusOnOff::setPixelColor(uint16_t pix, uint32_t c) { +void BusOnOff::setPixelColor(unsigned pix, uint32_t c) { if (pix != 0 || !_valid) return; //only react to first pixel c = autoWhiteCalc(c); uint8_t r = R(c); @@ -684,7 +684,7 @@ void BusOnOff::setPixelColor(uint16_t pix, uint32_t c) { _data[0] = bool(r|g|b|w) && bool(_bri) ? 0xFF : 0; } -uint32_t BusOnOff::getPixelColor(uint16_t pix) const { +uint32_t BusOnOff::getPixelColor(unsigned pix) const { if (!_valid) return 0; return RGBW32(_data[0], _data[0], _data[0], _data[0]); } @@ -734,7 +734,7 @@ BusNetwork::BusNetwork(BusConfig &bc) DEBUG_PRINTF_P(PSTR("%successfully inited virtual strip with type %u and IP %u.%u.%u.%u\n"), _valid?"S":"Uns", bc.type, bc.pins[0], bc.pins[1], bc.pins[2], bc.pins[3]); } -void BusNetwork::setPixelColor(uint16_t pix, uint32_t c) { +void BusNetwork::setPixelColor(unsigned pix, uint32_t c) { if (!_valid || pix >= _len) return; if (_hasWhite) c = autoWhiteCalc(c); if (Bus::_cct >= 1900) c = colorBalanceFromKelvin(Bus::_cct, c); //color correction from CCT @@ -745,7 +745,7 @@ void BusNetwork::setPixelColor(uint16_t pix, uint32_t c) { if (_hasWhite) _data[offset+3] = W(c); } -uint32_t BusNetwork::getPixelColor(uint16_t pix) const { +uint32_t BusNetwork::getPixelColor(unsigned pix) const { if (!_valid || pix >= _len) return 0; unsigned offset = pix * _UDPchannels; return RGBW32(_data[offset], _data[offset+1], _data[offset+2], (hasWhite() ? _data[offset+3] : 0)); @@ -952,7 +952,7 @@ void BusManager::setStatusPixel(uint32_t c) { } } -void IRAM_ATTR BusManager::setPixelColor(uint16_t pix, uint32_t c) { +void IRAM_ATTR BusManager::setPixelColor(unsigned pix, uint32_t c) { for (unsigned i = 0; i < numBusses; i++) { unsigned bstart = busses[i]->getStart(); if (pix < bstart || pix >= bstart + busses[i]->getLength()) continue; @@ -975,7 +975,7 @@ void BusManager::setSegmentCCT(int16_t cct, bool allowWBCorrection) { Bus::setCCT(cct); } -uint32_t BusManager::getPixelColor(uint16_t pix) { +uint32_t BusManager::getPixelColor(unsigned pix) { for (unsigned i = 0; i < numBusses; i++) { unsigned bstart = busses[i]->getStart(); if (!busses[i]->containsPixel(pix)) continue; diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h index e96b9de71..1b324d713 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -82,10 +82,10 @@ class Bus { virtual void show() = 0; virtual bool canShow() const { return true; } virtual void setStatusPixel(uint32_t c) {} - virtual void setPixelColor(uint16_t pix, uint32_t c) = 0; + virtual void setPixelColor(unsigned pix, uint32_t c) = 0; virtual void setBrightness(uint8_t b) { _bri = b; }; virtual void setColorOrder(uint8_t co) {} - virtual uint32_t getPixelColor(uint16_t pix) const { return 0; } + virtual uint32_t getPixelColor(unsigned pix) const { return 0; } virtual uint8_t getPins(uint8_t* pinArray = nullptr) const { return 0; } virtual uint16_t getLength() const { return isOk() ? _len : 0; } virtual uint8_t getColorOrder() const { return COL_ORDER_RGB; } @@ -203,9 +203,9 @@ class BusDigital : public Bus { bool canShow() const override; void setBrightness(uint8_t b) override; void setStatusPixel(uint32_t c) override; - [[gnu::hot]] void setPixelColor(uint16_t pix, uint32_t c) override; + [[gnu::hot]] void setPixelColor(unsigned pix, uint32_t c) override; void setColorOrder(uint8_t colorOrder) override; - [[gnu::hot]] uint32_t getPixelColor(uint16_t pix) const override; + [[gnu::hot]] uint32_t getPixelColor(unsigned pix) const override; uint8_t getColorOrder() const override { return _colorOrder; } uint8_t getPins(uint8_t* pinArray = nullptr) const override; uint8_t skippedLeds() const override { return _skip; } @@ -251,8 +251,8 @@ class BusPwm : public Bus { BusPwm(BusConfig &bc); ~BusPwm() { cleanup(); } - void setPixelColor(uint16_t pix, uint32_t c) override; - uint32_t getPixelColor(uint16_t pix) const override; //does no index check + void setPixelColor(unsigned pix, uint32_t c) override; + uint32_t getPixelColor(unsigned pix) const override; //does no index check uint8_t getPins(uint8_t* pinArray = nullptr) const override; uint16_t getFrequency() const override { return _frequency; } void show() override; @@ -278,8 +278,8 @@ class BusOnOff : public Bus { BusOnOff(BusConfig &bc); ~BusOnOff() { cleanup(); } - void setPixelColor(uint16_t pix, uint32_t c) override; - uint32_t getPixelColor(uint16_t pix) const override; + void setPixelColor(unsigned pix, uint32_t c) override; + uint32_t getPixelColor(unsigned pix) const override; uint8_t getPins(uint8_t* pinArray) const override; void show() override; void cleanup() { PinManager::deallocatePin(_pin, PinOwner::BusOnOff); } @@ -298,8 +298,8 @@ class BusNetwork : public Bus { ~BusNetwork() { cleanup(); } bool canShow() const override { return !_broadcastLock; } // this should be a return value from UDP routine if it is still sending data out - void setPixelColor(uint16_t pix, uint32_t c) override; - uint32_t getPixelColor(uint16_t pix) const override; + void setPixelColor(unsigned pix, uint32_t c) override; + uint32_t getPixelColor(unsigned pix) const override; uint8_t getPins(uint8_t* pinArray = nullptr) const override; void show() override; void cleanup(); @@ -384,13 +384,13 @@ class BusManager { static void show(); static bool canAllShow(); static void setStatusPixel(uint32_t c); - [[gnu::hot]] static void setPixelColor(uint16_t pix, uint32_t c); + [[gnu::hot]] static void setPixelColor(unsigned pix, uint32_t c); static void setBrightness(uint8_t b); // for setSegmentCCT(), cct can only be in [-1,255] range; allowWBCorrection will convert it to K // WARNING: setSegmentCCT() is a misleading name!!! much better would be setGlobalCCT() or just setCCT() static void setSegmentCCT(int16_t cct, bool allowWBCorrection = false); static inline void setMilliampsMax(uint16_t max) { _milliAmpsMax = max;} - static uint32_t getPixelColor(uint16_t pix); + [[gnu::hot]] static uint32_t getPixelColor(unsigned pix); static inline int16_t getSegmentCCT() { return Bus::getCCT(); } static Bus* getBus(uint8_t busNr); diff --git a/wled00/colors.cpp b/wled00/colors.cpp index 1b6a6f8d9..1c4844371 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -96,7 +96,7 @@ uint32_t color_fade(uint32_t c1, uint8_t amount, bool video) } // 1:1 replacement of fastled function optimized for ESP, slightly faster, more accurate and uses less flash (~ -200bytes) -CRGB ColorFromPaletteWLED(const CRGBPalette16& pal, unsigned index, uint8_t brightness, TBlendType blendType) +uint32_t ColorFromPaletteWLED(const CRGBPalette16& pal, unsigned index, uint8_t brightness, TBlendType blendType) { if (blendType == LINEARBLEND_NOWRAP) { index = (index*240) >> 8; // Blend range is affected by lo4 blend of values, remap to avoid wrapping @@ -121,7 +121,7 @@ CRGB ColorFromPaletteWLED(const CRGBPalette16& pal, unsigned index, uint8_t brig green1 = (green1 * scale) >> 8; blue1 = (blue1 * scale) >> 8; } - return CRGB((uint8_t)red1, (uint8_t)green1, (uint8_t)blue1); + return RGBW32(red1,green1,blue1,0); } void setRandomColor(byte* rgb) diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index ff96cf749..6568304f8 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -68,6 +68,64 @@ typedef struct WiFiConfig { //colors.cpp #define ColorFromPalette ColorFromPaletteWLED // override fastled version +// CRGBW can be used to manipulate 32bit colors faster. However: if it is passed to functions, it adds overhead compared to a uint32_t color +// use with caution and pay attention to flash size. Usually converting a uint32_t to CRGBW to extract r, g, b, w values is slower than using bitshifts +// it can be useful to avoid back and forth conversions between uint32_t and fastled CRGB +struct CRGBW { + union { + uint32_t color32; // Access as a 32-bit value (0xWWRRGGBB) + uint8_t raw[4]; // Access as an array in the order B, G, R, W + struct { + uint8_t b; + uint8_t g; + uint8_t r; + uint8_t w; + }; + }; + + // Default constructor + inline CRGBW() __attribute__((always_inline)) = default; + + // Constructor from a 32-bit color (0xWWRRGGBB) + constexpr CRGBW(uint32_t color) __attribute__((always_inline)) : color32(color) {} + + // Constructor with r, g, b, w values + constexpr CRGBW(uint8_t red, uint8_t green, uint8_t blue, uint8_t white = 0) __attribute__((always_inline)) : r(red), g(green), b(blue), w(white) {} + + // Constructor from CRGB + constexpr CRGBW(CRGB rgb) __attribute__((always_inline)) : r(rgb.r), g(rgb.g), b(rgb.b), w(0) {} + + // Access as an array + inline const uint8_t& operator[] (uint8_t x) const __attribute__((always_inline)) { return raw[x]; } + + // Assignment from 32-bit color + inline CRGBW& operator=(uint32_t color) __attribute__((always_inline)) { color32 = color; return *this; } + + // Assignment from r, g, b, w + inline CRGBW& operator=(const CRGB& rgb) __attribute__((always_inline)) { r = rgb.r; g = rgb.g; b = rgb.b; w = 0; return *this; } + + // Conversion operator to uint32_t + inline operator uint32_t() const __attribute__((always_inline)) { + return color32; + } + /* + // Conversion operator to CRGB + inline operator CRGB() const __attribute__((always_inline)) { + return CRGB(r, g, b); + } + + CRGBW& scale32 (uint8_t scaledown) // 32bit math + { + if (color32 == 0) return *this; // 2 extra instructions, worth it if called a lot on black (which probably is true) adding check if scaledown is zero adds much more overhead as its 8bit + uint32_t scale = scaledown + 1; + uint32_t rb = (((color32 & 0x00FF00FF) * scale) >> 8) & 0x00FF00FF; // scale red and blue + uint32_t wg = (((color32 & 0xFF00FF00) >> 8) * scale) & 0xFF00FF00; // scale white and green + color32 = rb | wg; + return *this; + }*/ + +}; + struct CHSV32 { // 32bit HSV color with 16bit hue for more accurate conversions union { struct { @@ -106,7 +164,7 @@ class NeoGammaWLEDMethod { [[gnu::hot]] uint32_t color_blend(uint32_t, uint32_t, uint16_t, bool b16=false); [[gnu::hot]] uint32_t color_add(uint32_t, uint32_t, bool preserveCR = false); [[gnu::hot]] uint32_t color_fade(uint32_t c1, uint8_t amount, bool video=false); -[[gnu::hot]] CRGB ColorFromPaletteWLED(const CRGBPalette16 &pal, unsigned index, uint8_t brightness = (uint8_t)255U, TBlendType blendType = LINEARBLEND); +[[gnu::hot]] uint32_t ColorFromPaletteWLED(const CRGBPalette16 &pal, unsigned index, uint8_t brightness = (uint8_t)255U, TBlendType blendType = LINEARBLEND); CRGBPalette16 generateHarmonicRandomPalette(CRGBPalette16 &basepalette); CRGBPalette16 generateRandomPalette(); inline uint32_t colorFromRgbw(byte* rgbw) { return uint32_t((byte(rgbw[3]) << 24) | (byte(rgbw[0]) << 16) | (byte(rgbw[1]) << 8) | (byte(rgbw[2]))); } From 202901b09f2a8c6da3a453b0d3ed8c8140ea5557 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Sat, 28 Sep 2024 15:38:41 +0200 Subject: [PATCH 044/287] bugfix, ESP32 compiler requires the color order to be identical --- wled00/fcn_declare.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 6568304f8..704fae852 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -74,13 +74,13 @@ typedef struct WiFiConfig { struct CRGBW { union { uint32_t color32; // Access as a 32-bit value (0xWWRRGGBB) - uint8_t raw[4]; // Access as an array in the order B, G, R, W struct { uint8_t b; uint8_t g; uint8_t r; uint8_t w; }; + uint8_t raw[4]; // Access as an array in the order B, G, R, W }; // Default constructor @@ -90,10 +90,10 @@ struct CRGBW { constexpr CRGBW(uint32_t color) __attribute__((always_inline)) : color32(color) {} // Constructor with r, g, b, w values - constexpr CRGBW(uint8_t red, uint8_t green, uint8_t blue, uint8_t white = 0) __attribute__((always_inline)) : r(red), g(green), b(blue), w(white) {} + constexpr CRGBW(uint8_t red, uint8_t green, uint8_t blue, uint8_t white = 0) __attribute__((always_inline)) : b(blue), g(green), r(red), w(white) {} // Constructor from CRGB - constexpr CRGBW(CRGB rgb) __attribute__((always_inline)) : r(rgb.r), g(rgb.g), b(rgb.b), w(0) {} + constexpr CRGBW(CRGB rgb) __attribute__((always_inline)) : b(rgb.b), g(rgb.g), r(rgb.r), w(0) {} // Access as an array inline const uint8_t& operator[] (uint8_t x) const __attribute__((always_inline)) { return raw[x]; } @@ -102,7 +102,7 @@ struct CRGBW { inline CRGBW& operator=(uint32_t color) __attribute__((always_inline)) { color32 = color; return *this; } // Assignment from r, g, b, w - inline CRGBW& operator=(const CRGB& rgb) __attribute__((always_inline)) { r = rgb.r; g = rgb.g; b = rgb.b; w = 0; return *this; } + inline CRGBW& operator=(const CRGB& rgb) __attribute__((always_inline)) { b = rgb.b; g = rgb.g; r = rgb.r; w = 0; return *this; } // Conversion operator to uint32_t inline operator uint32_t() const __attribute__((always_inline)) { From c842994df5d5e9120c069c84cba97ba8c686ab0d Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Sat, 28 Sep 2024 18:14:43 +0200 Subject: [PATCH 045/287] Pre-calculate virtual - move SEGCOLOR() to Segment class - add SEG_H, SEG_W macros - try to speed up virtualXxxxx() - compile warning fixes --- wled00/FX.cpp | 347 ++++++++++++++++++++++---------------------- wled00/FX.h | 64 ++++---- wled00/FX_2Dfcn.cpp | 146 ++++++++++--------- wled00/FX_fcn.cpp | 123 +++++++++------- wled00/udp.cpp | 54 +++---- wled00/xml.cpp | 4 +- 6 files changed, 367 insertions(+), 371 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 3df91547d..2ec31014f 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -118,7 +118,7 @@ uint16_t blink(uint32_t color1, uint32_t color2, bool strobe, bool do_palette) { uint32_t color = on ? color1 : color2; if (color == color1 && do_palette) { - for (int i = 0; i < SEGLEN; i++) { + for (unsigned i = 0; i < SEGLEN; i++) { SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0)); } } else SEGMENT.fill(color); @@ -300,25 +300,25 @@ uint16_t mode_dynamic(void) { if(SEGENV.call == 0) { //SEGMENT.fill(BLACK); - for (int i = 0; i < SEGLEN; i++) SEGENV.data[i] = random8(); + for (unsigned i = 0; i < SEGLEN; i++) SEGENV.data[i] = random8(); } uint32_t cycleTime = 50 + (255 - SEGMENT.speed)*15; uint32_t it = strip.now / cycleTime; if (it != SEGENV.step && SEGMENT.speed != 0) //new color { - for (int i = 0; i < SEGLEN; i++) { + for (unsigned i = 0; i < SEGLEN; i++) { if (random8() <= SEGMENT.intensity) SEGENV.data[i] = random8(); // random color index } SEGENV.step = it; } if (SEGMENT.check1) { - for (int i = 0; i < SEGLEN; i++) { + for (unsigned i = 0; i < SEGLEN; i++) { SEGMENT.blendPixelColor(i, SEGMENT.color_wheel(SEGENV.data[i]), 16); } } else { - for (int i = 0; i < SEGLEN; i++) { + for (unsigned i = 0; i < SEGLEN; i++) { SEGMENT.setPixelColor(i, SEGMENT.color_wheel(SEGENV.data[i])); } } @@ -353,7 +353,7 @@ uint16_t mode_breath(void) { } unsigned lum = 30 + var; - for (int i = 0; i < SEGLEN; i++) { + for (unsigned i = 0; i < SEGLEN; i++) { SEGMENT.setPixelColor(i, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0), lum)); } @@ -369,7 +369,7 @@ uint16_t mode_fade(void) { unsigned counter = (strip.now * ((SEGMENT.speed >> 3) +10)); unsigned lum = triwave16(counter) >> 8; - for (int i = 0; i < SEGLEN; i++) { + for (unsigned i = 0; i < SEGLEN; i++) { SEGMENT.setPixelColor(i, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0), lum)); } @@ -452,7 +452,7 @@ uint16_t mode_rainbow_cycle(void) { unsigned counter = (strip.now * ((SEGMENT.speed >> 2) +2)) & 0xFFFF; counter = counter >> 8; - for (int i = 0; i < SEGLEN; i++) { + for (unsigned i = 0; i < SEGLEN; i++) { //intensity/29 = 0 (1/16) 1 (1/8) 2 (1/4) 3 (1/2) 4 (1) 5 (2) 6 (4) 7 (8) 8 (16) uint8_t index = (i * (16 << (SEGMENT.intensity /29)) / SEGLEN) + counter; SEGMENT.setPixelColor(i, SEGMENT.color_wheel(index)); @@ -472,7 +472,7 @@ static uint16_t running(uint32_t color1, uint32_t color2, bool theatre = false) uint32_t it = strip.now / cycleTime; bool usePalette = color1 == SEGCOLOR(0); - for (int i = 0; i < SEGLEN; i++) { + for (unsigned i = 0; i < SEGLEN; i++) { uint32_t col = color2; if (usePalette) color1 = SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0); if (theatre) { @@ -519,7 +519,7 @@ static uint16_t running_base(bool saw, bool dual=false) { unsigned x_scale = SEGMENT.intensity >> 2; uint32_t counter = (strip.now * SEGMENT.speed) >> 9; - for (int i = 0; i < SEGLEN; i++) { + for (unsigned i = 0; i < SEGLEN; i++) { unsigned a = i*x_scale - counter; if (saw) { a &= 0xFF; @@ -622,7 +622,7 @@ uint16_t dissolve(uint32_t color) { SEGENV.aux0 = 1; } - for (int j = 0; j <= SEGLEN / 15; j++) { + for (unsigned j = 0; j <= SEGLEN / 15; j++) { if (random8() <= SEGMENT.intensity) { for (size_t times = 0; times < 10; times++) { //attempt to spawn a new pixel 10 times unsigned i = random16(SEGLEN); @@ -684,7 +684,7 @@ static const char _data_FX_MODE_DISSOLVE_RANDOM[] PROGMEM = "Dissolve Rnd@Repeat * Inspired by www.tweaking4all.com/hardware/arduino/adruino-led-strip-effects/ */ uint16_t mode_sparkle(void) { - if (!SEGMENT.check2) for(int i = 0; i < SEGLEN; i++) { + if (!SEGMENT.check2) for(unsigned i = 0; i < SEGLEN; i++) { SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 1)); } uint32_t cycleTime = 10 + (255 - SEGMENT.speed)*2; @@ -706,7 +706,7 @@ static const char _data_FX_MODE_SPARKLE[] PROGMEM = "Sparkle@!,,,,,,Overlay;!,!; * Inspired by www.tweaking4all.com/hardware/arduino/adruino-led-strip-effects/ */ uint16_t mode_flash_sparkle(void) { - if (!SEGMENT.check2) for (int i = 0; i < SEGLEN; i++) { + if (!SEGMENT.check2) for (unsigned i = 0; i < SEGLEN; i++) { SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0)); } @@ -727,13 +727,14 @@ static const char _data_FX_MODE_FLASH_SPARKLE[] PROGMEM = "Sparkle Dark@!,!,,,,, * Inspired by www.tweaking4all.com/hardware/arduino/adruino-led-strip-effects/ */ uint16_t mode_hyper_sparkle(void) { - if (!SEGMENT.check2) for (int i = 0; i < SEGLEN; i++) { + if (!SEGMENT.check2) for (unsigned i = 0; i < SEGLEN; i++) { SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0)); } if (strip.now - SEGENV.aux0 > SEGENV.step) { if (random8((255-SEGMENT.intensity) >> 4) == 0) { - for (int i = 0; i < max(1, SEGLEN/3); i++) { + int len = max(1, (int)SEGLEN/3); + for (int i = 0; i < len; i++) { SEGMENT.setPixelColor(random16(SEGLEN), SEGCOLOR(1)); } } @@ -749,7 +750,7 @@ static const char _data_FX_MODE_HYPER_SPARKLE[] PROGMEM = "Sparkle+@!,!,,,,,Over * Strobe effect with different strobe count and pause, controlled by speed. */ uint16_t mode_multi_strobe(void) { - for (int i = 0; i < SEGLEN; i++) { + for (unsigned i = 0; i < SEGLEN; i++) { SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 1)); } @@ -780,7 +781,7 @@ static const char _data_FX_MODE_MULTI_STROBE[] PROGMEM = "Strobe Mega@!,!;!,!;!; */ uint16_t mode_android(void) { - for (int i = 0; i < SEGLEN; i++) { + for (unsigned i = 0; i < SEGLEN; i++) { SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 1)); } @@ -995,10 +996,10 @@ static const char _data_FX_MODE_COLORFUL[] PROGMEM = "Colorful@!,Saturation;1,2, */ uint16_t mode_traffic_light(void) { if (SEGLEN == 1) return mode_static(); - for (int i=0; i < SEGLEN; i++) + for (unsigned i=0; i < SEGLEN; i++) SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 1)); uint32_t mdelay = 500; - for (int i = 0; i < SEGLEN-2 ; i+=3) + for (unsigned i = 0; i < SEGLEN-2 ; i+=3) { switch (SEGENV.aux0) { @@ -1030,7 +1031,7 @@ uint16_t mode_chase_flash(void) { if (SEGLEN == 1) return mode_static(); unsigned flash_step = SEGENV.call % ((FLASH_COUNT * 2) + 1); - for (int i = 0; i < SEGLEN; i++) { + for (unsigned i = 0; i < SEGLEN; i++) { SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0)); } @@ -1226,8 +1227,8 @@ static const char _data_FX_MODE_COMET[] PROGMEM = "Lighthouse@!,Fade rate;!,!;!" */ uint16_t mode_fireworks() { if (SEGLEN == 1) return mode_static(); - const uint16_t width = SEGMENT.is2D() ? SEGMENT.virtualWidth() : SEGMENT.virtualLength(); - const uint16_t height = SEGMENT.virtualHeight(); + const uint16_t width = SEGMENT.is2D() ? SEG_W : SEGLEN; + const uint16_t height = SEG_H; if (SEGENV.call == 0) { SEGENV.aux0 = UINT16_MAX; @@ -1268,8 +1269,8 @@ static const char _data_FX_MODE_FIREWORKS[] PROGMEM = "Fireworks@,Frequency;!,!; //Twinkling LEDs running. Inspired by https://github.com/kitesurfer1404/WS2812FX/blob/master/src/custom/Rain.h uint16_t mode_rain() { if (SEGLEN == 1) return mode_static(); - const unsigned width = SEGMENT.virtualWidth(); - const unsigned height = SEGMENT.virtualHeight(); + const unsigned width = SEG_W; + const unsigned height = SEG_H; SEGENV.step += FRAMETIME; if (SEGENV.call && SEGENV.step > SPEED_FORMULA_L) { SEGENV.step = 1; @@ -1283,7 +1284,7 @@ uint16_t mode_rain() { } else { //shift all leds left uint32_t ctemp = SEGMENT.getPixelColor(0); - for (int i = 0; i < SEGLEN - 1; i++) { + for (unsigned i = 0; i < SEGLEN - 1; i++) { SEGMENT.setPixelColor(i, SEGMENT.getPixelColor(i+1)); } SEGMENT.setPixelColor(SEGLEN -1, ctemp); // wrap around @@ -1314,7 +1315,7 @@ uint16_t mode_fire_flicker(void) { byte b = (SEGCOLOR(0) ); byte lum = (SEGMENT.palette == 0) ? MAX(w, MAX(r, MAX(g, b))) : 255; lum /= (((256-SEGMENT.intensity)/16)+1); - for (int i = 0; i < SEGLEN; i++) { + for (unsigned i = 0; i < SEGLEN; i++) { byte flicker = random8(lum); if (SEGMENT.palette == 0) { SEGMENT.setPixelColor(i, MAX(r - flicker, 0), MAX(g - flicker, 0), MAX(b - flicker, 0), MAX(w - flicker, 0)); @@ -1343,7 +1344,7 @@ uint16_t gradient_base(bool loading) { int p1 = pp-SEGLEN; int p2 = pp+SEGLEN; - for (int i = 0; i < SEGLEN; i++) { + for (int i = 0; i < (int)SEGLEN; i++) { if (loading) { val = abs(((i>pp) ? p2:pp) - i); } else { @@ -1428,7 +1429,7 @@ typedef struct Flasher { uint16_t mode_fairy() { //set every pixel to a 'random' color from palette (using seed so it doesn't change between frames) uint16_t PRNG16 = 5100 + strip.getCurrSegmentId(); - for (int i = 0; i < SEGLEN; i++) { + for (unsigned i = 0; i < SEGLEN; i++) { PRNG16 = (uint16_t)(PRNG16 * 2053) + 1384; //next 'random' number SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(PRNG16 >> 8, false, false, 0)); } @@ -1513,7 +1514,7 @@ uint16_t mode_fairytwinkle() { unsigned riseFallTime = 400 + (255-SEGMENT.speed)*3; unsigned maxDur = riseFallTime/100 + ((255 - SEGMENT.intensity) >> 2) + 13 + ((255 - SEGMENT.intensity) >> 1); - for (int f = 0; f < SEGLEN; f++) { + for (unsigned f = 0; f < SEGLEN; f++) { unsigned stateTime = now16 - flashers[f].stateStart; //random on/off time reached, switch state if (stateTime > flashers[f].stateDur * 100) { @@ -1558,7 +1559,7 @@ uint16_t tricolor_chase(uint32_t color1, uint32_t color2) { unsigned width = (1 + (SEGMENT.intensity>>4)); // value of 1-16 for each colour unsigned index = it % (width*3); - for (int i = 0; i < SEGLEN; i++, index++) { + for (unsigned i = 0; i < SEGLEN; i++, index++) { if (index > (width*3)-1) index = 0; uint32_t color = color1; @@ -1631,7 +1632,7 @@ uint16_t mode_tricolor_wipe(void) { unsigned ledIndex = (prog * SEGLEN * 3) >> 16; unsigned ledOffset = ledIndex; - for (int i = 0; i < SEGLEN; i++) + for (unsigned i = 0; i < SEGLEN; i++) { SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 2)); } @@ -1970,8 +1971,8 @@ uint16_t mode_palette() { constexpr float (*cosFunction)(float) = &cos_t; #endif const bool isMatrix = strip.isMatrix; - const int cols = SEGMENT.virtualWidth(); - const int rows = isMatrix ? SEGMENT.virtualHeight() : strip.getActiveSegmentsNum(); + const int cols = SEG_W; + const int rows = isMatrix ? SEG_H : strip.getActiveSegmentsNum(); const int inputShift = SEGMENT.speed; const int inputSize = SEGMENT.intensity; @@ -2084,10 +2085,10 @@ uint16_t mode_fire_2012() { struct virtualStrip { static void runStrip(uint16_t stripNr, byte* heat, uint32_t it) { - const uint8_t ignition = max(3,SEGLEN/10); // ignition area: 10% of segment length or minimum 3 pixels + const uint8_t ignition = MAX(3,SEGLEN/10); // ignition area: 10% of segment length or minimum 3 pixels // Step 1. Cool down every cell a little - for (int i = 0; i < SEGLEN; i++) { + for (unsigned i = 0; i < SEGLEN; i++) { uint8_t cool = (it != SEGENV.step) ? random8((((20 + SEGMENT.speed/3) * 16) / SEGLEN)+2) : random8(4); uint8_t minTemp = (i> 8; unsigned h16_128 = hue16 >> 7; @@ -2183,7 +2184,7 @@ static const char _data_FX_MODE_COLORWAVES[] PROGMEM = "Colorwaves@!,Hue;!;!"; uint16_t mode_bpm() { uint32_t stp = (strip.now / 20) & 0xFF; uint8_t beat = beatsin8(SEGMENT.speed, 64, 255); - for (int i = 0; i < SEGLEN; i++) { + for (unsigned i = 0; i < SEGLEN; i++) { SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(stp + (i * 2), false, PALETTE_SOLID_WRAP, 0, beat - stp + (i * 10))); } @@ -2194,7 +2195,7 @@ static const char _data_FX_MODE_BPM[] PROGMEM = "Bpm@!;!;!;;sx=64"; uint16_t mode_fillnoise8() { if (SEGENV.call == 0) SEGENV.step = random16(12345); - for (int i = 0; i < SEGLEN; i++) { + for (unsigned i = 0; i < SEGLEN; i++) { unsigned index = inoise8(i * SEGLEN, SEGENV.step + i * SEGLEN); SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(index, false, PALETTE_SOLID_WRAP, 0)); } @@ -2209,7 +2210,7 @@ uint16_t mode_noise16_1() { unsigned scale = 320; // the "zoom factor" for the noise SEGENV.step += (1 + SEGMENT.speed/16); - for (int i = 0; i < SEGLEN; i++) { + for (unsigned i = 0; i < SEGLEN; i++) { unsigned shift_x = beatsin8(11); // the x position of the noise field swings @ 17 bpm unsigned shift_y = SEGENV.step/42; // the y position becomes slowly incremented unsigned real_x = (i + shift_x) * scale; // the x position of the noise field swings @ 17 bpm @@ -2230,7 +2231,7 @@ uint16_t mode_noise16_2() { unsigned scale = 1000; // the "zoom factor" for the noise SEGENV.step += (1 + (SEGMENT.speed >> 1)); - for (int i = 0; i < SEGLEN; i++) { + for (unsigned i = 0; i < SEGLEN; i++) { unsigned shift_x = SEGENV.step >> 6; // x as a function of time uint32_t real_x = (i + shift_x) * scale; // calculate the coordinates within the noise field unsigned noise = inoise16(real_x, 0, 4223) >> 8; // get the noise data and scale it down @@ -2248,7 +2249,7 @@ uint16_t mode_noise16_3() { unsigned scale = 800; // the "zoom factor" for the noise SEGENV.step += (1 + SEGMENT.speed); - for (int i = 0; i < SEGLEN; i++) { + for (unsigned i = 0; i < SEGLEN; i++) { unsigned shift_x = 4223; // no movement along x and y unsigned shift_y = 1234; uint32_t real_x = (i + shift_x) * scale; // calculate the coordinates within the noise field @@ -2268,7 +2269,7 @@ static const char _data_FX_MODE_NOISE16_3[] PROGMEM = "Noise 3@!;!;!"; //https://github.com/aykevl/ledstrip-spark/blob/master/ledstrip.ino uint16_t mode_noise16_4() { uint32_t stp = (strip.now * SEGMENT.speed) >> 7; - for (int i = 0; i < SEGLEN; i++) { + for (unsigned i = 0; i < SEGLEN; i++) { int index = inoise16(uint32_t(i) << 12, stp); SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(index, false, PALETTE_SOLID_WRAP, 0)); } @@ -2285,7 +2286,7 @@ uint16_t mode_colortwinkle() { CRGBW col, prev; fract8 fadeUpAmount = strip.getBrightness()>28 ? 8 + (SEGMENT.speed>>2) : 68-strip.getBrightness(); fract8 fadeDownAmount = strip.getBrightness()>28 ? 8 + (SEGMENT.speed>>3) : 68-strip.getBrightness(); - for (int i = 0; i < SEGLEN; i++) { + for (unsigned i = 0; i < SEGLEN; i++) { CRGBW cur = SEGMENT.getPixelColor(i); prev = cur; unsigned index = i >> 3; @@ -2338,7 +2339,7 @@ uint16_t mode_lake() { int wave2 = beatsin8(sp +1, -64,64); int wave3 = beatsin8(sp +2, 0,80); - for (int i = 0; i < SEGLEN; i++) + for (unsigned i = 0; i < SEGLEN; i++) { int index = cos8((i*15)+ wave1)/2 + cubicwave8((i*23)+ wave2)/2; uint8_t lum = (index > wave3) ? index - wave3 : 0; @@ -2365,7 +2366,7 @@ uint16_t mode_meteor() { const int max = SEGMENT.palette==5 ? 239 : 255; // "* Colors only" palette blends end with start // fade all leds to colors[1] in LEDs one step - for (int i = 0; i < SEGLEN; i++) { + for (unsigned i = 0; i < SEGLEN; i++) { if (random8() <= 255 - SEGMENT.intensity) { int meteorTrailDecay = 128 + random8(127); trail[i] = scale8(trail[i], meteorTrailDecay); @@ -2454,7 +2455,7 @@ uint16_t mode_railway() { if (p0 < 255) pos = p0; } if (SEGENV.aux0) pos = 255 - pos; - for (int i = 0; i < SEGLEN; i += 2) + for (unsigned i = 0; i < SEGLEN; i += 2) { SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(255 - pos, false, false, 255)); // do not use color 1 or 2, always use palette if (i < SEGLEN -1) @@ -2485,7 +2486,7 @@ typedef struct Ripple { #define MAX_RIPPLES 100 #endif static uint16_t ripple_base() { - unsigned maxRipples = min(1 + (SEGLEN >> 2), MAX_RIPPLES); // 56 max for 16 segment ESP8266 + unsigned maxRipples = min(1 + (int)(SEGLEN >> 2), MAX_RIPPLES); // 56 max for 16 segment ESP8266 unsigned dataSize = sizeof(ripple) * maxRipples; if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed @@ -2655,7 +2656,7 @@ static uint16_t twinklefox_base(bool cat) unsigned backgroundBrightness = bg.getAverageLight(); - for (int i = 0; i < SEGLEN; i++) { + for (unsigned i = 0; i < SEGLEN; i++) { PRNG16 = (uint16_t)(PRNG16 * 2053) + 1384; // next 'random' number unsigned myclockoffset16= PRNG16; // use that number as clock offset @@ -2727,8 +2728,8 @@ uint16_t mode_halloween_eyes() }; if (SEGLEN == 1) return mode_static(); - const unsigned maxWidth = strip.isMatrix ? SEGMENT.virtualWidth() : SEGLEN; - const unsigned HALLOWEEN_EYE_SPACE = MAX(2, strip.isMatrix ? SEGMENT.virtualWidth()>>4: SEGLEN>>5); + const unsigned maxWidth = strip.isMatrix ? SEG_W : SEGLEN; + const unsigned HALLOWEEN_EYE_SPACE = MAX(2, strip.isMatrix ? SEG_W>>4: SEGLEN>>5); const unsigned HALLOWEEN_EYE_WIDTH = HALLOWEEN_EYE_SPACE/2; unsigned eyeLength = (2*HALLOWEEN_EYE_WIDTH) + HALLOWEEN_EYE_SPACE; if (eyeLength >= maxWidth) return mode_static(); //bail if segment too short @@ -2751,7 +2752,7 @@ uint16_t mode_halloween_eyes() data.startPos = random16(0, maxWidth - eyeLength - 1); data.color = random8(); - if (strip.isMatrix) SEGMENT.offset = random16(SEGMENT.virtualHeight()-1); // a hack: reuse offset since it is not used in matrices + if (strip.isMatrix) SEGMENT.offset = random16(SEG_H-1); // a hack: reuse offset since it is not used in matrices duration = 128u + random16(SEGMENT.intensity*64u); data.duration = duration; data.state = eyeState::on; @@ -2869,7 +2870,7 @@ uint16_t mode_static_pattern() bool drawingLit = true; unsigned cnt = 0; - for (int i = 0; i < SEGLEN; i++) { + for (unsigned i = 0; i < SEGLEN; i++) { SEGMENT.setPixelColor(i, (drawingLit) ? SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0) : SEGCOLOR(1)); cnt++; if (cnt >= ((drawingLit) ? lit : unlit)) { @@ -2889,7 +2890,7 @@ uint16_t mode_tri_static_pattern() unsigned currSeg = 0; unsigned currSegCount = 0; - for (int i = 0; i < SEGLEN; i++) { + for (unsigned i = 0; i < SEGLEN; i++) { if ( currSeg % 3 == 0 ) { SEGMENT.setPixelColor(i, SEGCOLOR(0)); } else if( currSeg % 3 == 1) { @@ -3319,7 +3320,7 @@ uint16_t candle(bool multi) { if (multi && SEGLEN > 1) { //allocate segment data - unsigned dataSize = max(1, SEGLEN -1) *3; //max. 1365 pixels (ESP8266) + unsigned dataSize = max(1, (int)SEGLEN -1) *3; //max. 1365 pixels (ESP8266) if (!SEGENV.allocateData(dataSize)) return candle(false); //allocation failed } @@ -3378,7 +3379,7 @@ uint16_t candle(bool multi) SEGENV.data[d] = s; SEGENV.data[d+1] = s_target; SEGENV.data[d+2] = fadeStep; } else { - for (int j = 0; j < SEGLEN; j++) { + for (unsigned j = 0; j < SEGLEN; j++) { SEGMENT.setPixelColor(j, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(j, true, PALETTE_SOLID_WRAP, 0), s)); } @@ -3521,12 +3522,12 @@ uint16_t mode_starburst(void) { if (stars[j].fragment[i] > 0) { float loc = stars[j].fragment[i]; if (mirrored) loc -= (loc-stars[j].pos)*2; - int start = loc - particleSize; - int end = loc + particleSize; + unsigned start = loc - particleSize; + unsigned end = loc + particleSize; if (start < 0) start = 0; if (start == end) end++; if (end > SEGLEN) end = SEGLEN; - for (int p = start; p < end; p++) { + for (unsigned p = start; p < end; p++) { SEGMENT.setPixelColor(p, c); } } @@ -3546,8 +3547,8 @@ static const char _data_FX_MODE_STARBURST[] PROGMEM = "Fireworks Starburst@Chanc uint16_t mode_exploding_fireworks(void) { if (SEGLEN == 1) return mode_static(); - const int cols = SEGMENT.is2D() ? SEGMENT.virtualWidth() : 1; - const int rows = SEGMENT.is2D() ? SEGMENT.virtualHeight() : SEGMENT.virtualLength(); + const int cols = SEGMENT.is2D() ? SEG_W : 1; + const int rows = SEGMENT.is2D() ? SEG_H : SEGLEN; //allocate segment data unsigned maxData = FAIR_DATA_PER_SEG; //ESP8266: 256 ESP32: 640 @@ -3698,7 +3699,7 @@ uint16_t mode_drip(void) unsigned numDrops = 1 + (SEGMENT.intensity >> 6); // 255>>6 = 3 float gravity = -0.0005f - (SEGMENT.speed/50000.0f); - gravity *= max(1, SEGLEN-1); + gravity *= max(1, (int)SEGLEN-1); int sourcedrop = 12; for (unsigned j=0;j= SEGLEN occasionally + unsigned pos = constrain(unsigned(drops[j].pos) +i, 0, SEGLEN-1); //this is BAD, returns a pos >= SEGLEN occasionally SEGMENT.setPixelColor(indexToVStrip(pos, stripNr), color_blend(BLACK,SEGCOLOR(0),drops[j].col/i)); //spread pixel with fade while falling } @@ -3821,8 +3822,8 @@ uint16_t mode_tetrix(void) { if (drop->pos > drop->stack) { // fall until top of stack drop->pos -= drop->speed; // may add gravity as: speed += gravity if (int(drop->pos) < int(drop->stack)) drop->pos = drop->stack; - for (int i = int(drop->pos); i < SEGLEN; i++) { - uint32_t col = ipos)+drop->brick ? SEGMENT.color_from_palette(drop->col, false, false, 0) : SEGCOLOR(1); + for (unsigned i = unsigned(drop->pos); i < SEGLEN; i++) { + uint32_t col = i < unsigned(drop->pos)+drop->brick ? SEGMENT.color_from_palette(drop->col, false, false, 0) : SEGCOLOR(1); SEGMENT.setPixelColor(indexToVStrip(i, stripNr), col); } } else { // we hit bottom @@ -3836,7 +3837,7 @@ uint16_t mode_tetrix(void) { drop->brick = 0; // reset brick size (no more growing) if (drop->step > strip.now) { // allow fading of virtual strip - for (int i = 0; i < SEGLEN; i++) SEGMENT.blendPixelColor(indexToVStrip(i, stripNr), SEGCOLOR(1), 25); // 10% blend + for (unsigned i = 0; i < SEGLEN; i++) SEGMENT.blendPixelColor(indexToVStrip(i, stripNr), SEGCOLOR(1), 25); // 10% blend } else { drop->stack = 0; // reset brick stack size drop->step = 0; // proceed with next brick @@ -3893,7 +3894,7 @@ uint16_t mode_percent(void) { if (SEGMENT.speed == 255) size = 255; if (percent <= 100) { - for (int i = 0; i < SEGLEN; i++) { + for (unsigned i = 0; i < SEGLEN; i++) { if (i < SEGENV.aux1) { if (SEGMENT.check1) SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(map(percent,0,100,0,255), false, false, 0)); @@ -3905,7 +3906,7 @@ uint16_t mode_percent(void) { } } } else { - for (int i = 0; i < SEGLEN; i++) { + for (unsigned i = 0; i < SEGLEN; i++) { if (i < (SEGLEN - SEGENV.aux1)) { SEGMENT.setPixelColor(i, SEGCOLOR(1)); } @@ -3955,7 +3956,7 @@ uint16_t mode_heartbeat(void) { SEGENV.step = strip.now; } - for (int i = 0; i < SEGLEN; i++) { + for (unsigned i = 0; i < SEGLEN; i++) { SEGMENT.setPixelColor(i, color_blend(SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0), SEGCOLOR(1), 255 - (SEGENV.aux1 >> 8))); } @@ -4049,7 +4050,7 @@ uint16_t mode_pacifica() unsigned basethreshold = beatsin8( 9, 55, 65); unsigned wave = beat8( 7 ); - for (int i = 0; i < SEGLEN; i++) { + for (unsigned i = 0; i < SEGLEN; i++) { CRGB c = CRGB(2, 6, 10); // Render each of four layers, with different scales and speeds, that vary over time c += pacifica_one_layer(i, pacifica_palette_1, sCIStart1, beatsin16(3, 11 * 256, 14 * 256), beatsin8(10, 70, 130), 0-beat16(301)); @@ -4112,7 +4113,7 @@ uint16_t mode_sunrise() { if (SEGMENT.speed > 60) stage = 0xFFFF - stage; //sunset } - for (int i = 0; i <= SEGLEN/2; i++) + for (unsigned i = 0; i <= SEGLEN/2; i++) { //default palette is Fire unsigned wave = triwave16((i * stage) / SEGLEN); @@ -4145,7 +4146,7 @@ static uint16_t phased_base(uint8_t moder) { // We're making si unsigned index = strip.now/64; // Set color rotation speed *phase += SEGMENT.speed/32.0; // You can change the speed of the wave. AKA SPEED (was .4) - for (int i = 0; i < SEGLEN; i++) { + for (unsigned i = 0; i < SEGLEN; i++) { if (moder == 1) modVal = (inoise8(i*10 + i*10) /16); // Let's randomize our mod length with some Perlin noise. unsigned val = (i+1) * allfreq; // This sets the frequency of the waves. The +1 makes sure that led 0 is used. if (modVal == 0) modVal = 1; @@ -4177,7 +4178,7 @@ uint16_t mode_twinkleup(void) { // A very short twinkle routine unsigned prevSeed = random16_get_seed(); // save seed so we can restore it at the end of the function random16_set_seed(535); // The randomizer needs to be re-set each time through the loop in order for the same 'random' numbers to be the same each time through. - for (int i = 0; i < SEGLEN; i++) { + for (unsigned i = 0; i < SEGLEN; i++) { unsigned ranstart = random8(); // The starting value (aka brightness) for each pixel. Must be consistent each time through the loop for this to work. unsigned pixBri = sin8(ranstart + 16 * strip.now/(256-SEGMENT.speed)); if (random8() > SEGMENT.intensity) pixBri = 0; @@ -4214,7 +4215,7 @@ uint16_t mode_noisepal(void) { // Slow noise if (SEGMENT.palette > 0) palettes[0] = SEGPALETTE; - for (int i = 0; i < SEGLEN; i++) { + for (unsigned i = 0; i < SEGLEN; i++) { unsigned index = inoise8(i*scale, SEGENV.aux0+i*scale); // Get a value from the noise function. I'm using both x and y axis. SEGMENT.setPixelColor(i, ColorFromPalette(palettes[0], index, 255, LINEARBLEND)); // Use my own palette. } @@ -4237,7 +4238,7 @@ uint16_t mode_sinewave(void) { // Adjustable sinewave. By Andrew Tul SEGENV.step += SEGMENT.speed/16; // Speed of animation. unsigned freq = SEGMENT.intensity/4;//SEGMENT.fft2/8; // Frequency of the signal. - for (int i = 0; i < SEGLEN; i++) { // For each of the LED's in the strand, set a brightness based on a wave as follows: + for (unsigned i = 0; i < SEGLEN; i++) { // For each of the LED's in the strand, set a brightness based on a wave as follows: int pixBri = cubicwave8((i*freq)+SEGENV.step);//qsuba(cubicwave8((i*freq)+SEGENV.step), (255-SEGMENT.intensity)); // qsub sets a minimum value called thiscutoff. If < thiscutoff, then bright = 0. Otherwise, bright = 128 (as defined in qsub).. //setPixCol(i, i*colorIndex/255, pixBri); SEGMENT.setPixelColor(i, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(i*colorIndex/255, false, PALETTE_SOLID_WRAP, 0), pixBri)); @@ -4368,7 +4369,7 @@ uint16_t mode_dancing_shadows(void) spotlights[i].lastUpdateTime = time; } - respawn = (spotlights[i].speed > 0.0 && spotlights[i].position > (SEGLEN + 2)) + respawn = (spotlights[i].speed > 0.0 && spotlights[i].position > (int)(SEGLEN + 2)) || (spotlights[i].speed < 0.0 && spotlights[i].position < -(spotlights[i].width + 2)); } @@ -4398,7 +4399,7 @@ uint16_t mode_dancing_shadows(void) int start = spotlights[i].position; if (spotlights[i].width <= 1) { - if (start >= 0 && start < SEGLEN) { + if (start >= 0 && start < (int)SEGLEN) { SEGMENT.blendPixelColor(start, color, 128); } } else { @@ -4468,7 +4469,7 @@ uint16_t mode_washing_machine(void) { SEGENV.step += (speed * 2048) / (512 - SEGMENT.speed); - for (int i = 0; i < SEGLEN; i++) { + for (unsigned i = 0; i < SEGLEN; i++) { uint8_t col = sin8(((SEGMENT.intensity / 25 + 1) * 255 * i / SEGLEN) + (SEGENV.step >> 7)); SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(col, false, PALETTE_SOLID_WRAP, 3)); } @@ -4618,7 +4619,7 @@ uint16_t mode_tv_simulator(void) { } // set strip color - for (i = 0; i < SEGLEN; i++) { + for (i = 0; i < (int)SEGLEN; i++) { SEGMENT.setPixelColor(i, r >> 8, g >> 8, b >> 8); // Quantize to 8-bit } @@ -4777,7 +4778,7 @@ uint16_t mode_aurora(void) { if (SEGCOLOR(1)) backlight++; if (SEGCOLOR(2)) backlight++; //Loop through LEDs to determine color - for (int i = 0; i < SEGLEN; i++) { + for (unsigned i = 0; i < SEGLEN; i++) { CRGB mixedRgb = CRGB(backlight, backlight, backlight); //For each LED we must check each wave if it is "active" at this position. @@ -4824,7 +4825,7 @@ static const char _data_FX_MODE_PERLINMOVE[] PROGMEM = "Perlin Move@!,# of pixel // Uses beatsin8() + phase shifting. By: Andrew Tuline uint16_t mode_wavesins(void) { - for (int i = 0; i < SEGLEN; i++) { + for (unsigned i = 0; i < SEGLEN; i++) { uint8_t bri = sin8(strip.now/4 + i * SEGMENT.intensity); uint8_t index = beatsin8(SEGMENT.speed, SEGMENT.custom1, SEGMENT.custom1+SEGMENT.custom2, 0, i * (SEGMENT.custom3<<3)); // custom3 is reduced resolution slider //SEGMENT.setPixelColor(i, ColorFromPalette(SEGPALETTE, index, bri, LINEARBLEND)); @@ -4846,8 +4847,8 @@ uint16_t mode_FlowStripe(void) { uint8_t hue = strip.now / (SEGMENT.speed+1); uint32_t t = strip.now / (SEGMENT.intensity/8+1); - for (int i = 0; i < SEGLEN; i++) { - int c = (abs(i - hl) / hl) * 127; + for (unsigned i = 0; i < SEGLEN; i++) { + int c = (abs((int)i - hl) / hl) * 127; c = sin8(c); c = sin8(c / 2 + t); byte b = sin8(c + t/8); @@ -4869,8 +4870,8 @@ static const char _data_FX_MODE_FLOWSTRIPE[] PROGMEM = "Flow Stripe@Hue speed,Ef uint16_t mode_2DBlackHole(void) { // By: Stepko https://editor.soulmatelights.com/gallery/1012 , Modified by: Andrew Tuline if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + const int cols = SEG_W; + const int rows = SEG_H; int x, y; SEGMENT.fadeToBlackBy(16 + (SEGMENT.speed>>3)); // create fading trails @@ -4903,8 +4904,8 @@ static const char _data_FX_MODE_2DBLACKHOLE[] PROGMEM = "Black Hole@Fade rate,Ou uint16_t mode_2DColoredBursts() { // By: ldirko https://editor.soulmatelights.com/gallery/819-colored-bursts , modified by: Andrew Tuline if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + const int cols = SEG_W; + const int rows = SEG_H; if (SEGENV.call == 0) { SEGENV.aux0 = 0; // start with red hue @@ -4955,8 +4956,8 @@ static const char _data_FX_MODE_2DCOLOREDBURSTS[] PROGMEM = "Colored Bursts@Spee uint16_t mode_2Ddna(void) { // dna originally by by ldirko at https://pastebin.com/pCkkkzcs. Updated by Preyy. WLED conversion by Andrew Tuline. if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + const int cols = SEG_W; + const int rows = SEG_H; SEGMENT.fadeToBlackBy(64); for (int i = 0; i < cols; i++) { @@ -4976,8 +4977,8 @@ static const char _data_FX_MODE_2DDNA[] PROGMEM = "DNA@Scroll speed,Blur;;!;2"; uint16_t mode_2DDNASpiral() { // By: ldirko https://editor.soulmatelights.com/gallery/512-dna-spiral-variation , modified by: Andrew Tuline if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + const int cols = SEG_W; + const int rows = SEG_H; if (SEGENV.call == 0) { SEGMENT.fill(BLACK); @@ -5021,8 +5022,8 @@ static const char _data_FX_MODE_2DDNASPIRAL[] PROGMEM = "DNA Spiral@Scroll speed uint16_t mode_2DDrift() { // By: Stepko https://editor.soulmatelights.com/gallery/884-drift , Modified by: Andrew Tuline if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + const int cols = SEG_W; + const int rows = SEG_H; const int colsCenter = (cols>>1) + (cols%2); const int rowsCenter = (rows>>1) + (rows%2); @@ -5051,8 +5052,8 @@ static const char _data_FX_MODE_2DDRIFT[] PROGMEM = "Drift@Rotation speed,Blur a uint16_t mode_2Dfirenoise(void) { // firenoise2d. By Andrew Tuline. Yet another short routine. if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + const int cols = SEG_W; + const int rows = SEG_H; if (SEGENV.call == 0) { SEGMENT.fill(BLACK); @@ -5085,8 +5086,8 @@ static const char _data_FX_MODE_2DFIRENOISE[] PROGMEM = "Firenoise@X scale,Y sca uint16_t mode_2DFrizzles(void) { // By: Stepko https://editor.soulmatelights.com/gallery/640-color-frizzles , Modified by: Andrew Tuline if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + const int cols = SEG_W; + const int rows = SEG_H; SEGMENT.fadeToBlackBy(16); for (size_t i = 8; i > 0; i--) { @@ -5112,8 +5113,8 @@ typedef struct ColorCount { 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 if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + const int cols = SEG_W; + const int rows = SEG_H; const unsigned dataSize = sizeof(CRGB) * SEGMENT.length(); // using width*height prevents reallocation if mirroring is enabled const int crcBufferLen = 2; //(SEGMENT.width() + SEGMENT.height())*71/100; // roughly sqrt(2)/2 for better repetition detection (Ewowi) @@ -5218,8 +5219,8 @@ static const char _data_FX_MODE_2DGAMEOFLIFE[] PROGMEM = "Game Of Life@!;!,!;!;2 uint16_t mode_2DHiphotic() { // By: ldirko https://editor.soulmatelights.com/gallery/810 , Modified by: Andrew Tuline if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + const int cols = SEG_W; + const int rows = SEG_H; const uint32_t a = strip.now / ((SEGMENT.custom3>>1)+1); for (int x = 0; x < cols; x++) { @@ -5250,8 +5251,8 @@ typedef struct Julia { uint16_t mode_2DJulia(void) { // An animated Julia set by Andrew Tuline. if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + const int cols = SEG_W; + const int rows = SEG_H; if (!SEGENV.allocateData(sizeof(julia))) return mode_static(); Julia* julias = reinterpret_cast(SEGENV.data); @@ -5356,8 +5357,8 @@ static const char _data_FX_MODE_2DJULIA[] PROGMEM = "Julia@,Max iterations per p uint16_t mode_2DLissajous(void) { // By: Andrew Tuline if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + const int cols = SEG_W; + const int rows = SEG_H; SEGMENT.fadeToBlackBy(SEGMENT.intensity); uint_fast16_t phase = (strip.now * (1 + SEGENV.custom3)) /32; // allow user to control rotation speed @@ -5384,8 +5385,8 @@ static const char _data_FX_MODE_2DLISSAJOUS[] PROGMEM = "Lissajous@X frequency,F uint16_t mode_2Dmatrix(void) { // Matrix2D. By Jeremy Williams. Adapted by Andrew Tuline & improved by merkisoft and ewowi, and softhack007. if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + const int cols = SEG_W; + const int rows = SEG_H; unsigned dataSize = (SEGMENT.length()+7) >> 3; //1 bit per LED for trails if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed @@ -5454,8 +5455,8 @@ static const char _data_FX_MODE_2DMATRIX[] PROGMEM = "Matrix@!,Spawning rate,Tra uint16_t mode_2Dmetaballs(void) { // Metaballs by Stefan Petrick. Cannot have one of the dimensions be 2 or less. Adapted by Andrew Tuline. if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + const int cols = SEG_W; + const int rows = SEG_H; float speed = 0.25f * (1+(SEGMENT.speed>>6)); @@ -5513,8 +5514,8 @@ static const char _data_FX_MODE_2DMETABALLS[] PROGMEM = "Metaballs@!;;!;2"; uint16_t mode_2Dnoise(void) { // By Andrew Tuline if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + const int cols = SEG_W; + const int rows = SEG_H; const unsigned scale = SEGMENT.intensity+2; @@ -5536,8 +5537,8 @@ static const char _data_FX_MODE_2DNOISE[] PROGMEM = "Noise2D@!,Scale;;!;2"; uint16_t mode_2DPlasmaball(void) { // By: Stepko https://editor.soulmatelights.com/gallery/659-plasm-ball , Modified by: Andrew Tuline if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + const int cols = SEG_W; + const int rows = SEG_H; SEGMENT.fadeToBlackBy(SEGMENT.custom1>>2); uint_fast32_t t = (strip.now * 8) / (256 - SEGMENT.speed); // optimized to avoid float @@ -5576,8 +5577,8 @@ static const char _data_FX_MODE_2DPLASMABALL[] PROGMEM = "Plasma Ball@Speed,,Fad uint16_t mode_2DPolarLights(void) { // By: Kostyantyn Matviyevskyy https://editor.soulmatelights.com/gallery/762-polar-lights , Modified by: Andrew Tuline if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + const int cols = SEG_W; + const int rows = SEG_H; CRGBPalette16 auroraPalette = {0x000000, 0x003300, 0x006600, 0x009900, 0x00cc00, 0x00ff00, 0x33ff00, 0x66ff00, 0x99ff00, 0xccff00, 0xffff00, 0xffcc00, 0xff9900, 0xff6600, 0xff3300, 0xff0000}; @@ -5627,8 +5628,8 @@ static const char _data_FX_MODE_2DPOLARLIGHTS[] PROGMEM = "Polar Lights@!,Scale; uint16_t mode_2DPulser(void) { // By: ldirko https://editor.soulmatelights.com/gallery/878-pulse-test , modifed by: Andrew Tuline if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + const int cols = SEG_W; + const int rows = SEG_H; SEGMENT.fadeToBlackBy(8 - (SEGMENT.intensity>>5)); uint32_t a = strip.now / (18 - SEGMENT.speed / 16); @@ -5649,8 +5650,8 @@ static const char _data_FX_MODE_2DPULSER[] PROGMEM = "Pulser@!,Blur;;!;2"; uint16_t mode_2DSindots(void) { // By: ldirko https://editor.soulmatelights.com/gallery/597-sin-dots , modified by: Andrew Tuline if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + const int cols = SEG_W; + const int rows = SEG_H; if (SEGENV.call == 0) { SEGMENT.fill(BLACK); @@ -5680,8 +5681,8 @@ uint16_t mode_2Dsquaredswirl(void) { // By: Mark Kriegsman. https://g // Modifed by: Andrew Tuline if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + const int cols = SEG_W; + const int rows = SEG_H; const uint8_t kBorderWidth = 2; @@ -5711,8 +5712,8 @@ static const char _data_FX_MODE_2DSQUAREDSWIRL[] PROGMEM = "Squared Swirl@,,,,Bl uint16_t mode_2DSunradiation(void) { // By: ldirko https://editor.soulmatelights.com/gallery/599-sun-radiation , modified by: Andrew Tuline if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + const int cols = SEG_W; + const int rows = SEG_H; if (!SEGENV.allocateData(sizeof(byte)*(cols+2)*(rows+2))) return mode_static(); //allocation failed byte *bump = reinterpret_cast(SEGENV.data); @@ -5761,8 +5762,8 @@ static const char _data_FX_MODE_2DSUNRADIATION[] PROGMEM = "Sun Radiation@Varian uint16_t mode_2Dtartan(void) { // By: Elliott Kember https://editor.soulmatelights.com/gallery/3-tartan , Modified by: Andrew Tuline if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + const int cols = SEG_W; + const int rows = SEG_H; if (SEGENV.call == 0) { SEGMENT.fill(BLACK); @@ -5800,8 +5801,8 @@ static const char _data_FX_MODE_2DTARTAN[] PROGMEM = "Tartan@X scale,Y scale,,,S uint16_t mode_2Dspaceships(void) { //// Space ships by stepko (c)05.02.21 [https://editor.soulmatelights.com/gallery/639-space-ships], adapted by Blaz Kristan (AKA blazoncek) if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + const int cols = SEG_W; + const int rows = SEG_H; uint32_t tb = strip.now >> 12; // every ~4s if (tb > SEGENV.step) { @@ -5843,8 +5844,8 @@ static const char _data_FX_MODE_2DSPACESHIPS[] PROGMEM = "Spaceships@!,Blur;;!;2 uint16_t mode_2Dcrazybees(void) { if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + const int cols = SEG_W; + const int rows = SEG_H; byte n = MIN(MAX_BEES, (rows * cols) / 256 + 1); @@ -5916,8 +5917,8 @@ static const char _data_FX_MODE_2DCRAZYBEES[] PROGMEM = "Crazy Bees@!,Blur;;;2"; uint16_t mode_2Dghostrider(void) { if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + const int cols = SEG_W; + const int rows = SEG_H; typedef struct Lighter { int16_t gPosX; @@ -6006,8 +6007,8 @@ static const char _data_FX_MODE_2DGHOSTRIDER[] PROGMEM = "Ghost Rider@Fade rate, uint16_t mode_2Dfloatingblobs(void) { if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + const int cols = SEG_W; + const int rows = SEG_H; typedef struct Blob { float x[MAX_BLOBS], y[MAX_BLOBS]; @@ -6104,8 +6105,8 @@ static const char _data_FX_MODE_2DBLOBS[] PROGMEM = "Blobs@!,# blobs,Blur,Trail; uint16_t mode_2Dscrollingtext(void) { if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + const int cols = SEG_W; + const int rows = SEG_H; unsigned letterWidth, rotLW; unsigned letterHeight, rotLH; @@ -6205,8 +6206,8 @@ static const char _data_FX_MODE_2DSCROLLTEXT[] PROGMEM = "Scrolling Text@!,Y Off uint16_t mode_2Ddriftrose(void) { if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + const int cols = SEG_W; + const int rows = SEG_H; const float CX = (cols-cols%2)/2.f - .5f; const float CY = (rows-rows%2)/2.f - .5f; @@ -6232,8 +6233,8 @@ static const char _data_FX_MODE_2DDRIFTROSE[] PROGMEM = "Drift Rose@Fade,Blur;;; uint16_t mode_2Dplasmarotozoom() { if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + const int cols = SEG_W; + const int rows = SEG_H; unsigned dataSize = SEGMENT.length() + sizeof(float); if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed @@ -6401,8 +6402,8 @@ static const char _data_FX_MODE_RIPPLEPEAK[] PROGMEM = "Ripple Peak@Fade rate,Ma uint16_t mode_2DSwirl(void) { if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + const int cols = SEG_W; + const int rows = SEG_H; if (SEGENV.call == 0) { SEGMENT.fill(BLACK); @@ -6440,8 +6441,8 @@ static const char _data_FX_MODE_2DSWIRL[] PROGMEM = "Swirl@!,Sensitivity,Blur;,B uint16_t mode_2DWaverly(void) { if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + const int cols = SEG_W; + const int rows = SEG_H; um_data_t *um_data = getAudioData(); float volumeSmth = *(float*) um_data->u_data[0]; @@ -6652,7 +6653,7 @@ uint16_t mode_matripix(void) { // Matripix. By Andrew Tuline. SEGENV.aux0 = secondHand; int pixBri = volumeRaw * SEGMENT.intensity / 64; - for (int i = 0; i < SEGLEN-1; i++) SEGMENT.setPixelColor(i, SEGMENT.getPixelColor(i+1)); // shift left + for (unsigned i = 0; i < SEGLEN-1; i++) SEGMENT.setPixelColor(i, SEGMENT.getPixelColor(i+1)); // shift left SEGMENT.setPixelColor(SEGLEN-1, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(strip.now, false, PALETTE_SOLID_WRAP, 0), pixBri)); } @@ -6677,10 +6678,10 @@ uint16_t mode_midnoise(void) { // Midnoise. By Andrew Tuline. float tmpSound2 = volumeSmth * (float)SEGMENT.intensity / 256.0; // Too sensitive. tmpSound2 *= (float)SEGMENT.intensity / 128.0; // Reduce sensitivity/length. - int maxLen = mapf(tmpSound2, 0, 127, 0, SEGLEN/2); + unsigned maxLen = mapf(tmpSound2, 0, 127, 0, SEGLEN/2); if (maxLen >SEGLEN/2) maxLen = SEGLEN/2; - for (int i=(SEGLEN/2-maxLen); i<(SEGLEN/2+maxLen); i++) { + for (unsigned i=(SEGLEN/2-maxLen); i<(SEGLEN/2+maxLen); i++) { uint8_t index = inoise8(i*volumeSmth+SEGENV.aux0, SEGENV.aux1+i*volumeSmth); // Get a value from the noise function. I'm using both x and y axis. SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(index, false, PALETTE_SOLID_WRAP, 0)); } @@ -6708,7 +6709,7 @@ uint16_t mode_noisefire(void) { // Noisefire. By Andrew Tuline. if (SEGENV.call == 0) SEGMENT.fill(BLACK); - for (int i = 0; i < SEGLEN; i++) { + for (unsigned i = 0; i < SEGLEN; i++) { unsigned index = inoise8(i*SEGMENT.speed/64,strip.now*SEGMENT.speed/64*SEGLEN/255); // X location is constant, but we move along the Y at the rate of millis(). By Andrew Tuline. index = (255 - i*256/SEGLEN) * index/(256-SEGMENT.intensity); // Now we need to scale index so that it gets blacker as we get close to one of the ends. // This is a simple y=mx+b equation that's been scaled. index/128 is another scaling. @@ -6735,11 +6736,11 @@ uint16_t mode_noisemeter(void) { // Noisemeter. By Andrew Tuline. SEGMENT.fade_out(fadeRate); float tmpSound2 = volumeRaw * 2.0 * (float)SEGMENT.intensity / 255.0; - int maxLen = mapf(tmpSound2, 0, 255, 0, SEGLEN); // map to pixels availeable in current segment // Still a bit too sensitive. - if (maxLen <0) maxLen = 0; - if (maxLen >SEGLEN) maxLen = SEGLEN; + unsigned maxLen = mapf(tmpSound2, 0, 255, 0, SEGLEN); // map to pixels availeable in current segment // Still a bit too sensitive. + if (maxLen < 0) maxLen = 0; + if (maxLen > SEGLEN) maxLen = SEGLEN; - for (int i=0; i SEGLEN/2; i--) SEGMENT.setPixelColor(i, SEGMENT.getPixelColor(i-1)); //move to the left - for (int i = 0; i < SEGLEN/2; i++) SEGMENT.setPixelColor(i, SEGMENT.getPixelColor(i+1)); // move to the right + for (unsigned i = SEGLEN - 1; i > SEGLEN/2; i--) SEGMENT.setPixelColor(i, SEGMENT.getPixelColor(i-1)); //move to the left + for (unsigned i = 0; i < SEGLEN/2; i++) SEGMENT.setPixelColor(i, SEGMENT.getPixelColor(i+1)); // move to the right } return FRAMETIME; @@ -6803,7 +6804,7 @@ uint16_t mode_plasmoid(void) { // Plasmoid. By Andrew Tuline. plasmoip->thisphase += beatsin8(6,-4,4); // You can change direction and speed individually. plasmoip->thatphase += beatsin8(7,-4,4); // Two phase values to make a complex pattern. By Andrew Tuline. - for (int i = 0; i < SEGLEN; i++) { // For each of the LED's in the strand, set a brightness based on a wave as follows. + for (unsigned i = 0; i < SEGLEN; i++) { // For each of the LED's in the strand, set a brightness based on a wave as follows. // updated, similar to "plasma" effect - softhack007 uint8_t thisbright = cubicwave8(((i*(1 + (3*SEGMENT.speed/32)))+plasmoip->thisphase) & 0xFF)/2; thisbright += cos8(((i*(97 +(5*SEGMENT.speed/32)))+plasmoip->thatphase) & 0xFF)/2; // Let's munge the brightness a bit and animate it all with the phases. @@ -6943,7 +6944,7 @@ uint16_t mode_blurz(void) { // Blurz. By Andrew Tuline. SEGENV.step += FRAMETIME; if (SEGENV.step > SPEED_FORMULA_L) { unsigned segLoc = random16(SEGLEN); - SEGMENT.setPixelColor(segLoc, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(2*fftResult[SEGENV.aux0%16]*240/max(1, SEGLEN-1), false, PALETTE_SOLID_WRAP, 0), 2*fftResult[SEGENV.aux0%16])); + SEGMENT.setPixelColor(segLoc, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(2*fftResult[SEGENV.aux0%16]*240/max(1, (int)SEGLEN-1), false, PALETTE_SOLID_WRAP, 0), 2*fftResult[SEGENV.aux0%16])); ++(SEGENV.aux0) %= 16; // make sure it doesn't cross 16 SEGENV.step = 1; @@ -7006,7 +7007,7 @@ uint16_t mode_freqmap(void) { // Map FFT_MajorPeak to SEGLEN. int locn = (log10f((float)FFT_MajorPeak) - 1.78f) * (float)SEGLEN/(MAX_FREQ_LOG10 - 1.78f); // log10 frequency range is from 1.78 to 3.71. Let's scale to SEGLEN. if (locn < 1) locn = 0; // avoid underflow - if (locn >=SEGLEN) locn = SEGLEN-1; + if (locn >= (int)SEGLEN) locn = SEGLEN-1; unsigned pixCol = (log10f(FFT_MajorPeak) - 1.78f) * 255.0f/(MAX_FREQ_LOG10 - 1.78f); // Scale log10 of frequency values to the 255 colour index. if (FFT_MajorPeak < 61.0f) pixCol = 0; // handle underflow @@ -7159,8 +7160,8 @@ uint16_t mode_freqwave(void) { // Freqwave. By Andreas Pleschun // shift the pixels one pixel outwards // if SEGLEN equals 1 these loops won't execute - for (int i = SEGLEN - 1; i > SEGLEN/2; i--) SEGMENT.setPixelColor(i, SEGMENT.getPixelColor(i-1)); //move to the left - for (int i = 0; i < SEGLEN/2; i++) SEGMENT.setPixelColor(i, SEGMENT.getPixelColor(i+1)); // move to the right + for (unsigned i = SEGLEN - 1; i > SEGLEN/2; i--) SEGMENT.setPixelColor(i, SEGMENT.getPixelColor(i-1)); //move to the left + for (unsigned i = 0; i < SEGLEN/2; i++) SEGMENT.setPixelColor(i, SEGMENT.getPixelColor(i+1)); // move to the right } return FRAMETIME; @@ -7314,7 +7315,7 @@ uint16_t mode_waterfall(void) { // Waterfall. By: Andrew Tulin 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 + for (unsigned i = 0; i < SEGLEN-1; i++) SEGMENT.setPixelColor(i, SEGMENT.getPixelColor(i+1)); // shift left } return FRAMETIME; @@ -7330,8 +7331,8 @@ uint16_t mode_2DGEQ(void) { // By Will Tatam. Code reduction by Ewoud Wijma. if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up const int NUM_BANDS = map(SEGMENT.custom1, 0, 255, 1, 16); - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + const int cols = SEG_W; + const int rows = SEG_H; if (!SEGENV.allocateData(cols*sizeof(uint16_t))) return mode_static(); //allocation failed uint16_t *previousBarHeight = reinterpret_cast(SEGENV.data); //array of previous bar heights per frequency band @@ -7383,8 +7384,8 @@ static const char _data_FX_MODE_2DGEQ[] PROGMEM = "GEQ@Fade speed,Ripple decay,# uint16_t mode_2DFunkyPlank(void) { // Written by ??? Adapted by Will Tatam. if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + const int cols = SEG_W; + const int rows = SEG_H; int NUMB_BANDS = map(SEGMENT.custom1, 0, 255, 1, 16); int barWidth = (cols / NUMB_BANDS); @@ -7471,8 +7472,8 @@ static uint8_t akemi[] PROGMEM = { uint16_t mode_2DAkemi(void) { if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + const int cols = SEG_W; + const int rows = SEG_H; unsigned counter = (strip.now * ((SEGMENT.speed >> 2) +2)) & 0xFFFF; counter = counter >> 8; @@ -7539,8 +7540,8 @@ static const char _data_FX_MODE_2DAKEMI[] PROGMEM = "Akemi@Color speed,Dance;Hea uint16_t mode_2Ddistortionwaves() { if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + const int cols = SEG_W; + const int rows = SEG_H; uint8_t speed = SEGMENT.speed/32; uint8_t scale = SEGMENT.intensity/32; @@ -7594,8 +7595,8 @@ static const char _data_FX_MODE_2DDISTORTIONWAVES[] PROGMEM = "Distortion Waves@ uint16_t mode_2Dsoap() { if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + const int cols = SEG_W; + const int rows = SEG_H; const size_t dataSize = SEGMENT.width() * SEGMENT.height() * sizeof(uint8_t); // prevent reallocation if mirrored or grouped if (!SEGENV.allocateData(dataSize + sizeof(uint32_t)*3)) return mode_static(); //allocation failed @@ -7706,8 +7707,8 @@ static const char _data_FX_MODE_2DSOAP[] PROGMEM = "Soap@!,Smoothness;;!;2"; uint16_t mode_2Doctopus() { if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + const int cols = SEG_W; + const int rows = SEG_H; const uint8_t mapp = 180 / MAX(cols,rows); typedef struct { @@ -7761,8 +7762,8 @@ static const char _data_FX_MODE_2DOCTOPUS[] PROGMEM = "Octopus@!,,Offset X,Offse uint16_t mode_2Dwavingcell() { if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up - const int cols = SEGMENT.virtualWidth(); - const int rows = SEGMENT.virtualHeight(); + const int cols = SEG_W; + const int rows = SEG_H; uint32_t t = strip.now/(257-SEGMENT.speed); uint8_t aX = SEGMENT.custom1/16 + 9; diff --git a/wled00/FX.h b/wled00/FX.h index 825c722e7..98082c8d8 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -90,11 +90,11 @@ extern byte realtimeMode; // used in getMappedPixelIndex() #define NUM_COLORS 3 /* number of colors per segment */ #define SEGMENT strip._segments[strip.getCurrSegmentId()] #define SEGENV strip._segments[strip.getCurrSegmentId()] -//#define SEGCOLOR(x) strip._segments[strip.getCurrSegmentId()].currentColor(x, strip._segments[strip.getCurrSegmentId()].colors[x]) -//#define SEGLEN strip._segments[strip.getCurrSegmentId()].virtualLength() -#define SEGCOLOR(x) strip.segColor(x) /* saves us a few kbytes of code */ +#define SEGCOLOR(x) Segment::getCurrentColor(x) #define SEGPALETTE Segment::getCurrentPalette() -#define SEGLEN strip._virtualSegmentLength /* saves us a few kbytes of code */ +#define SEGLEN Segment::vLength() +#define SEG_W Segment::vWidth() +#define SEG_H Segment::vHeight() #define SPEED_FORMULA_L (5U + (50U*(255U - SEGMENT.speed))/SEGLEN) // some common colors @@ -421,7 +421,9 @@ typedef struct Segment { uint16_t _dataLen; static uint16_t _usedSegmentData; - // perhaps this should be per segment, not static + static unsigned _vLength; // 1D dimension used for current effect + static unsigned _vWidth, _vHeight; // 2D dimensions used for current effect + static uint32_t _currentColors[NUM_COLORS]; // colors used for current effect static CRGBPalette16 _currentPalette; // palette used for current effect (includes transition, used in color_from_palette()) static CRGBPalette16 _randomPalette; // actual random palette static CRGBPalette16 _newRandomPalette; // target random palette @@ -534,14 +536,20 @@ typedef struct Segment { inline uint16_t groupLength() const { return grouping + spacing; } inline uint8_t getLightCapabilities() const { return _capabilities; } - inline static uint16_t getUsedSegmentData() { return _usedSegmentData; } - inline static void addUsedSegmentData(int len) { _usedSegmentData += len; } + inline static uint16_t getUsedSegmentData() { return Segment::_usedSegmentData; } + inline static void addUsedSegmentData(int len) { Segment::_usedSegmentData += len; } #ifndef WLED_DISABLE_MODE_BLEND - inline static void modeBlend(bool blend) { _modeBlend = blend; } + inline static void modeBlend(bool blend) { _modeBlend = blend; } #endif - static void handleRandomPalette(); + inline static unsigned vLength() { return Segment::_vLength; } + inline static unsigned vWidth() { return Segment::_vWidth; } + inline static unsigned vHeight() { return Segment::_vHeight; } + inline static uint32_t getCurrentColor(unsigned i) { return Segment::_currentColors[i]; } // { return i < 3 ? Segment::_currentColors[i] : 0; } inline static const CRGBPalette16 &getCurrentPalette() { return Segment::_currentPalette; } + static void handleRandomPalette(); + + void beginDraw(); // set up parameters for current effect void setUp(uint16_t i1, uint16_t i2, uint8_t grp=1, uint8_t spc=0, uint16_t ofs=UINT16_MAX, uint16_t i1Y=0, uint16_t i2Y=1); bool setColor(uint8_t slot, uint32_t c); //returns true if changed void setCCT(uint16_t k); @@ -578,14 +586,13 @@ typedef struct Segment { uint8_t currentMode() const; // currently active effect/mode (while in transition) [[gnu::hot]] uint32_t currentColor(uint8_t slot) const; // currently active segment color (blended while in transition) CRGBPalette16 &loadPalette(CRGBPalette16 &tgt, uint8_t pal); - void setCurrentPalette(); // 1D strip [[gnu::hot]] uint16_t virtualLength() const; - [[gnu::hot]] void setPixelColor(int n, uint32_t c); // set relative pixel within segment with color - inline void setPixelColor(unsigned n, uint32_t c) { setPixelColor(int(n), c); } - inline void setPixelColor(int n, byte r, byte g, byte b, byte w = 0) { setPixelColor(n, RGBW32(r,g,b,w)); } - inline void setPixelColor(int n, CRGB c) { setPixelColor(n, RGBW32(c.r,c.g,c.b,0)); } + [[gnu::hot]] void setPixelColor(int n, uint32_t c, bool unScaled = true); // set relative pixel within segment with color + inline void setPixelColor(unsigned n, uint32_t c, bool unScaled = true) { setPixelColor(int(n), c, unScaled); } + inline void setPixelColor(int n, byte r, byte g, byte b, byte w = 0) { setPixelColor(n, RGBW32(r,g,b,w)); } + inline void setPixelColor(int n, CRGB c) { setPixelColor(n, RGBW32(c.r,c.g,c.b,0)); } #ifdef WLED_USE_AA_PIXELS void setPixelColor(float i, uint32_t c, bool aa = true); 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); } @@ -622,8 +629,8 @@ typedef struct Segment { uint16_t nrOfVStrips() const; // returns number of virtual vertical strips in 2D matrix (used to expand 1D effects into 2D) #ifndef WLED_DISABLE_2D [[gnu::hot]] uint16_t XY(int x, int y); // support function to get relative index within segment - [[gnu::hot]] 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); } + [[gnu::hot]] void setPixelColorXY(int x, int y, uint32_t c, bool unScaled = true); // set relative pixel within segment with color + inline void setPixelColorXY(unsigned x, unsigned y, uint32_t c, bool unScaled = true) { setPixelColorXY(int(x), int(y), c, unScaled); } 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)); } @@ -642,8 +649,8 @@ typedef struct Segment { inline void fadePixelColorXY(uint16_t x, uint16_t y, uint8_t fade) { setPixelColorXY(x, y, color_fade(getPixelColorXY(x,y), fade, true)); } void box_blur(unsigned r = 1U, bool smear = false); // 2D box blur void blur2D(uint8_t blur_amount, bool smear = false); - void blurRow(uint32_t row, fract8 blur_amount, bool smear = false); - void blurCol(uint32_t col, fract8 blur_amount, bool smear = false); + void blurRow(int row, fract8 blur_amount, bool smear = false); + void blurCol(int col, fract8 blur_amount, bool smear = false); 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); @@ -660,9 +667,9 @@ typedef struct Segment { inline void blur2d(fract8 blur_amount) { blur(blur_amount); } inline void fill_solid(CRGB c) { fill(RGBW32(c.r,c.g,c.b,0)); } #else - inline uint16_t XY(uint16_t x, uint16_t y) { return x; } - inline void setPixelColorXY(int x, int y, uint32_t c) { setPixelColor(x, c); } - inline void setPixelColorXY(unsigned x, unsigned y, uint32_t c) { setPixelColor(int(x), c); } + inline uint16_t XY(int x, int y) { return x; } + inline void setPixelColorXY(int x, int y, uint32_t c, bool unScaled = true) { setPixelColor(x, c, unScaled); } + inline void setPixelColorXY(unsigned x, unsigned y, uint32_t c, bool unScaled = true) { setPixelColor(int(x), c, unScaled); } 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)); } @@ -680,8 +687,8 @@ typedef struct Segment { inline void fadePixelColorXY(uint16_t x, uint16_t y, uint8_t fade) { fadePixelColor(x, fade); } inline void box_blur(unsigned i, bool vertical, fract8 blur_amount) {} inline void blur2D(uint8_t blur_amount, bool smear = false) {} - inline void blurRow(uint32_t row, fract8 blur_amount, bool smear = false) {} - inline void blurCol(uint32_t col, fract8 blur_amount, bool smear = false) {} + inline void blurRow(int row, fract8 blur_amount, bool smear = false) {} + inline void blurCol(int col, fract8 blur_amount, bool smear = false) {} 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) {} @@ -727,9 +734,6 @@ class WS2812FX { // 96 bytes autoSegments(false), correctWB(false), cctFromRgb(false), - // semi-private (just obscured) used in effect functions through macros - _colors_t{0,0,0}, - _virtualSegmentLength(0), // true private variables _suspend(false), _length(DEFAULT_LED_COUNT), @@ -829,7 +833,7 @@ class WS2812FX { // 96 bytes addEffect(uint8_t id, mode_ptr mode_fn, const char *mode_name); // add effect to the list; defined in FX.cpp; inline uint8_t getBrightness() const { return _brightness; } // returns current strip brightness - inline uint8_t getMaxSegments() const { return MAX_NUM_SEGMENTS; } // returns maximum number of supported segments (fixed value) + inline constexpr unsigned getMaxSegments() { return MAX_NUM_SEGMENTS; } // returns maximum number of supported segments (fixed value) inline uint8_t getSegmentsNum() const { return _segments.size(); } // returns currently present segments inline uint8_t getCurrSegmentId() const { return _segment_index; } // returns current segment index (only valid while strip.isServicing()) inline uint8_t getMainSegmentId() const { return _mainSegment; } // returns main segment index @@ -855,7 +859,6 @@ class WS2812FX { // 96 bytes uint32_t getPixelColor(unsigned) const; inline uint32_t getLastShow() const { return _lastShow; } // returns millis() timestamp of last strip.show() call - inline uint32_t segColor(uint8_t i) const { return _colors_t[i]; } // returns currently valid color (for slot i) AKA SEGCOLOR(); may be blended between two colors while in transition const char * getModeData(uint8_t id = 0) const { return (id && id<_modeCount) ? _modeData[id] : PSTR("Solid"); } @@ -922,11 +925,6 @@ class WS2812FX { // 96 bytes bool cctFromRgb : 1; }; - // using public variables to reduce code size increase due to inline function getSegment() (with bounds checking) - // and color transitions - uint32_t _colors_t[3]; // color used for effect (includes transition) - uint16_t _virtualSegmentLength; - std::vector _segments; friend class Segment; diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index 41fd67319..5f61b0094 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -163,23 +163,24 @@ void WS2812FX::setUpMatrix() { // XY(x,y) - gets pixel index within current segment (often used to reference leds[] array element) uint16_t IRAM_ATTR_YN Segment::XY(int x, int y) { - unsigned width = virtualWidth(); // segment width in logical pixels (can be 0 if segment is inactive) - unsigned height = virtualHeight(); // segment height in logical pixels (is always >= 1) - return isActive() ? (x%width) + (y%height) * width : 0; + const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive) + const int vH = vHeight(); // segment height in logical pixels (is always >= 1) + return isActive() ? (x%vW) + (y%vH) * vW : 0; } -void IRAM_ATTR_YN Segment::setPixelColorXY(int x, int y, uint32_t col) +void IRAM_ATTR_YN Segment::setPixelColorXY(int x, int y, uint32_t col, bool unScaled) { if (!isActive()) return; // not active - if (x >= virtualWidth() || y >= virtualHeight() || x<0 || y<0) return; // if pixel would fall out of virtual segment just exit + + const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive) + const int vH = vHeight(); // segment height in logical pixels (is always >= 1) + if (x >= vW|| y >= vH || x<0 || y<0) return; // if pixel would fall out of virtual segment just exit - uint8_t _bri_t = currentBri(); - if (_bri_t < 255) { - col = color_fade(col, _bri_t); - } + // if color is unscaled + if (unScaled) col = color_fade(col, currentBri()); - if (reverse ) x = virtualWidth() - x - 1; - if (reverse_y) y = virtualHeight() - y - 1; + if (reverse ) x = vW - x - 1; + if (reverse_y) y = vH - y - 1; if (transpose) { std::swap(x,y); } // swap X & Y if segment transposed x *= groupLength(); // expand to physical pixels y *= groupLength(); // expand to physical pixels @@ -221,11 +222,8 @@ void Segment::setPixelColorXY(float x, float y, uint32_t col, bool aa) if (!isActive()) return; // not active if (x<0.0f || x>1.0f || y<0.0f || y>1.0f) return; // not normalized - const unsigned cols = virtualWidth(); - const unsigned rows = virtualHeight(); - - float fX = x * (cols-1); - float fY = y * (rows-1); + float fX = x * (vWidth()-1); + float fY = y * (vHeight()-1); if (aa) { unsigned xL = roundf(fX-0.49f); unsigned xR = roundf(fX+0.49f); @@ -263,9 +261,11 @@ void Segment::setPixelColorXY(float x, float y, uint32_t col, bool aa) // returns RGBW values of pixel uint32_t IRAM_ATTR_YN Segment::getPixelColorXY(int x, int y) const { if (!isActive()) return 0; // not active - if (x >= virtualWidth() || y >= virtualHeight() || x<0 || y<0) return 0; // if pixel would fall out of virtual segment just exit - if (reverse ) x = virtualWidth() - x - 1; - if (reverse_y) y = virtualHeight() - y - 1; + int vW = vWidth(); + int vH = vHeight(); + if (x >= vW || y >= vH || x<0 || y<0) return 0; // if pixel would fall out of virtual segment just exit + if (reverse ) x = vW - x - 1; + if (reverse_y) y = vH - y - 1; if (transpose) { std::swap(x,y); } // swap X & Y if segment transposed x *= groupLength(); // expand to physical pixels y *= groupLength(); // expand to physical pixels @@ -274,10 +274,10 @@ uint32_t IRAM_ATTR_YN Segment::getPixelColorXY(int x, int y) const { } // blurRow: perform a blur on a row of a rectangular matrix -void Segment::blurRow(uint32_t row, fract8 blur_amount, bool smear){ +void Segment::blurRow(int row, fract8 blur_amount, bool smear){ if (!isActive() || blur_amount == 0) return; // not active - const unsigned cols = virtualWidth(); - const unsigned rows = virtualHeight(); + const int cols = vWidth(); + const int rows = vHeight(); if (row >= rows) return; // blur one row @@ -287,7 +287,7 @@ void Segment::blurRow(uint32_t row, fract8 blur_amount, bool smear){ uint32_t lastnew; uint32_t last; uint32_t curnew = BLACK; - for (unsigned x = 0; x < cols; x++) { + for (int x = 0; x < cols; x++) { uint32_t cur = getPixelColorXY(x, row); uint32_t part = color_fade(cur, seep); curnew = color_fade(cur, keep); @@ -306,10 +306,10 @@ void Segment::blurRow(uint32_t row, fract8 blur_amount, bool smear){ } // blurCol: perform a blur on a column of a rectangular matrix -void Segment::blurCol(uint32_t col, fract8 blur_amount, bool smear) { +void Segment::blurCol(int col, fract8 blur_amount, bool smear) { if (!isActive() || blur_amount == 0) return; // not active - const unsigned cols = virtualWidth(); - const unsigned rows = virtualHeight(); + const int cols = vWidth(); + const int rows = vHeight(); if (col >= cols) return; // blur one column @@ -319,7 +319,7 @@ void Segment::blurCol(uint32_t col, fract8 blur_amount, bool smear) { uint32_t lastnew; uint32_t last; uint32_t curnew = BLACK; - for (unsigned y = 0; y < rows; y++) { + for (int y = 0; y < rows; y++) { uint32_t cur = getPixelColorXY(col, y); uint32_t part = color_fade(cur, seep); curnew = color_fade(cur, keep); @@ -339,8 +339,8 @@ void Segment::blurCol(uint32_t col, fract8 blur_amount, bool smear) { void Segment::blur2D(uint8_t blur_amount, bool smear) { if (!isActive() || blur_amount == 0) return; // not active - const unsigned cols = virtualWidth(); - const unsigned rows = virtualHeight(); + const unsigned cols = vWidth(); + const unsigned rows = vHeight(); const uint8_t keep = smear ? 255 : 255 - blur_amount; const uint8_t seep = blur_amount >> (1 + smear); @@ -391,8 +391,8 @@ void Segment::box_blur(unsigned radius, bool smear) { if (!isActive() || radius == 0) return; // not active if (radius > 3) radius = 3; const unsigned d = (1 + 2*radius) * (1 + 2*radius); // averaging divisor - const unsigned cols = virtualWidth(); - const unsigned rows = virtualHeight(); + const unsigned cols = vWidth(); + const unsigned rows = vHeight(); uint16_t *tmpRSum = new uint16_t[cols*rows]; uint16_t *tmpGSum = new uint16_t[cols*rows]; uint16_t *tmpBSum = new uint16_t[cols*rows]; @@ -461,37 +461,37 @@ void Segment::box_blur(unsigned radius, bool smear) { void Segment::moveX(int8_t delta, bool wrap) { if (!isActive()) return; // not active - const int cols = virtualWidth(); - const int rows = virtualHeight(); - if (!delta || abs(delta) >= cols) return; - uint32_t newPxCol[cols]; - for (int y = 0; y < rows; y++) { + const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive) + const int vH = vHeight(); // segment height in logical pixels (is always >= 1) + if (!delta || abs(delta) >= vW) return; + uint32_t newPxCol[vW]; + for (int y = 0; y < vH; y++) { if (delta > 0) { - for (int x = 0; x < cols-delta; x++) newPxCol[x] = getPixelColorXY((x + delta), y); - for (int x = cols-delta; x < cols; x++) newPxCol[x] = getPixelColorXY(wrap ? (x + delta) - cols : x, y); + for (int x = 0; x < vW-delta; x++) newPxCol[x] = getPixelColorXY((x + delta), y); + for (int x = vW-delta; x < vW; x++) newPxCol[x] = getPixelColorXY(wrap ? (x + delta) - vW : x, y); } else { - for (int x = cols-1; x >= -delta; x--) newPxCol[x] = getPixelColorXY((x + delta), y); - for (int x = -delta-1; x >= 0; x--) newPxCol[x] = getPixelColorXY(wrap ? (x + delta) + cols : x, y); + for (int x = vW-1; x >= -delta; x--) newPxCol[x] = getPixelColorXY((x + delta), y); + for (int x = -delta-1; x >= 0; x--) newPxCol[x] = getPixelColorXY(wrap ? (x + delta) + vW : x, y); } - for (int x = 0; x < cols; x++) setPixelColorXY(x, y, newPxCol[x]); + for (int x = 0; x < vW; x++) setPixelColorXY(x, y, newPxCol[x]); } } void Segment::moveY(int8_t delta, bool wrap) { if (!isActive()) return; // not active - const int cols = virtualWidth(); - const int rows = virtualHeight(); - if (!delta || abs(delta) >= rows) return; - uint32_t newPxCol[rows]; - for (int x = 0; x < cols; x++) { + const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive) + const int vH = vHeight(); // segment height in logical pixels (is always >= 1) + if (!delta || abs(delta) >= vH) return; + uint32_t newPxCol[vH]; + for (int x = 0; x < vW; x++) { if (delta > 0) { - for (int y = 0; y < rows-delta; y++) newPxCol[y] = getPixelColorXY(x, (y + delta)); - for (int y = rows-delta; y < rows; y++) newPxCol[y] = getPixelColorXY(x, wrap ? (y + delta) - rows : y); + for (int y = 0; y < vH-delta; y++) newPxCol[y] = getPixelColorXY(x, (y + delta)); + for (int y = vH-delta; y < vH; y++) newPxCol[y] = getPixelColorXY(x, wrap ? (y + delta) - vH : y); } else { - for (int y = rows-1; y >= -delta; y--) newPxCol[y] = getPixelColorXY(x, (y + delta)); - for (int y = -delta-1; y >= 0; y--) newPxCol[y] = getPixelColorXY(x, wrap ? (y + delta) + rows : y); + for (int y = vH-1; y >= -delta; y--) newPxCol[y] = getPixelColorXY(x, (y + delta)); + for (int y = -delta-1; y >= 0; y--) newPxCol[y] = getPixelColorXY(x, wrap ? (y + delta) + vH : y); } - for (int y = 0; y < rows; y++) setPixelColorXY(x, y, newPxCol[y]); + for (int y = 0; y < vH; y++) setPixelColorXY(x, y, newPxCol[y]); } } @@ -545,18 +545,20 @@ void Segment::drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t col, x++; } } else { + // pre-scale color for all pixels + col = color_fade(col, currentBri()); // 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); + setPixelColorXY(cx+x, cy+y, col, false); + setPixelColorXY(cx-x, cy+y, col, false); + setPixelColorXY(cx+x, cy-y, col, false); + setPixelColorXY(cx-x, cy-y, col, false); + setPixelColorXY(cx+y, cy+x, col, false); + setPixelColorXY(cx-y, cy+x, col, false); + setPixelColorXY(cx+y, cy-x, col, false); + setPixelColorXY(cx-y, cy-x, col, false); x++; if (d > 0) { y--; @@ -571,17 +573,19 @@ void Segment::drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t col, // by stepko, taken from https://editor.soulmatelights.com/gallery/573-blobs void Segment::fillCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t col, bool soft) { if (!isActive() || radius == 0) return; // not active + const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive) + const int vH = vHeight(); // segment height in logical pixels (is always >= 1) // draw soft bounding circle if (soft) drawCircle(cx, cy, radius, col, soft); + // pre-scale color for all pixels + col = color_fade(col, currentBri()); // 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 && - int(cx)+x>=0 && int(cy)+y>=0 && - int(cx)+x= 0 && int(cy)+y >= 0 && + int(cx)+x < vW && int(cy)+y < vH) + setPixelColorXY(cx + x, cy + y, col, false); } } } @@ -589,9 +593,9 @@ void Segment::fillCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t col, //line function void Segment::drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint32_t c, bool soft) { if (!isActive()) return; // not active - const int cols = virtualWidth(); - const int rows = virtualHeight(); - if (x0 >= cols || x1 >= cols || y0 >= rows || y1 >= rows) return; + const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive) + const int vH = vHeight(); // segment height in logical pixels (is always >= 1) + if (x0 >= vW || x1 >= vW || y0 >= vH || y1 >= vH) return; const int dx = abs(x1-x0), sx = x0dy ? dx : -dy)/2; // error direction for (;;) { - setPixelColorXY(x0, y0, c); + setPixelColorXY(x0, y0, c, false); if (x0==x1 && y0==y1) break; int e2 = err; if (e2 >-dx) { err -= dy; x0 += sx; } @@ -653,8 +659,6 @@ void Segment::drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, if (!isActive()) return; // not active if (chr < 32 || chr > 126) return; // only ASCII 32-126 supported chr -= 32; // align with font table entries - const int cols = virtualWidth(); - const int rows = virtualHeight(); const int font = w*h; CRGB col = CRGB(color); @@ -681,7 +685,7 @@ void Segment::drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, case 1: x0 = x + i; y0 = y + j; break; // +90 deg default: x0 = x + (w-1) - j; y0 = y + i; break; // no rotation } - if (x0 < 0 || x0 >= cols || y0 < 0 || y0 >= rows) continue; // drawing off-screen + if (x0 < 0 || x0 >= (int)vWidth() || y0 < 0 || y0 >= (int)vHeight()) continue; // drawing off-screen if (((bits>>(j+(8-w))) & 0x01)) { // bit set setPixelColorXY(x0, y0, col); } diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index a98755425..a47a7edc9 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -80,15 +80,18 @@ static constexpr bool validatePinsAndTypes(const unsigned* types, unsigned numTy /////////////////////////////////////////////////////////////////////////////// // Segment class implementation /////////////////////////////////////////////////////////////////////////////// -uint16_t Segment::_usedSegmentData = 0U; // amount of RAM all segments use for their data[] -uint16_t Segment::maxWidth = DEFAULT_LED_COUNT; -uint16_t Segment::maxHeight = 1; - +uint16_t Segment::_usedSegmentData = 0U; // amount of RAM all segments use for their data[] +uint16_t Segment::maxWidth = DEFAULT_LED_COUNT; +uint16_t Segment::maxHeight = 1; +unsigned Segment::_vLength = 0; +unsigned Segment::_vWidth = 0; +unsigned Segment::_vHeight = 0; +uint32_t Segment::_currentColors[NUM_COLORS] = {0,0,0}; CRGBPalette16 Segment::_currentPalette = CRGBPalette16(CRGB::Black); CRGBPalette16 Segment::_randomPalette = generateRandomPalette(); // was CRGBPalette16(DEFAULT_COLOR); CRGBPalette16 Segment::_newRandomPalette = generateRandomPalette(); // was CRGBPalette16(DEFAULT_COLOR); uint16_t Segment::_lastPaletteChange = 0; // perhaps it should be per segment -uint16_t Segment::_lastPaletteBlend = 0; //in millis (lowest 16 bits only) +uint16_t Segment::_lastPaletteBlend = 0; // in millis (lowest 16 bits only) #ifndef WLED_DISABLE_MODE_BLEND bool Segment::_modeBlend = false; @@ -437,7 +440,21 @@ uint32_t IRAM_ATTR_YN Segment::currentColor(uint8_t slot) const { #endif } -void Segment::setCurrentPalette() { +// pre-calculate drawing parameters for faster access +void Segment::beginDraw() { + _vWidth = virtualWidth(); + _vHeight = virtualHeight(); + _vLength = virtualLength(); + // adjust gamma for effects + for (unsigned i = 0; i < NUM_COLORS; i++) { + #ifndef WLED_DISABLE_MODE_BLEND + uint32_t col = isInTransition() ? color_blend(_t->_segT._colorT[i], colors[i], progress(), true) : colors[i]; + #else + uint32_t col = isInTransition() ? color_blend(_t->_colorT[i], colors[i], progress(), true) : colors[i]; + #endif + _currentColors[i] = gamma32(col); + } + // load palette into _currentPalette loadPalette(_currentPalette, palette); unsigned prog = progress(); if (strip.paletteFade && prog < 0xFFFFU) { @@ -698,20 +715,21 @@ uint16_t IRAM_ATTR Segment::virtualLength() const { return vLength; } -void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col) +void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col, bool unScaled) { if (!isActive() || i < 0) return; // not active or invalid index #ifndef WLED_DISABLE_2D int vStrip = 0; #endif + int vL = vLength(); // if the 1D effect is using virtual strips "i" will have virtual strip id stored in upper 16 bits // in such case "i" will be > virtualLength() - if (i >= virtualLength()) { + if (i >= vL) { // check if this is a virtual strip #ifndef WLED_DISABLE_2D vStrip = i>>16; // hack to allow running on virtual strips (2D segment columns/rows) i &= 0xFFFF; //truncate vstrip index - if (i >= virtualLength()) return; // if pixel would still fall out of segment just exit + if (i >= vL) return; // if pixel would still fall out of segment just exit #else return; #endif @@ -719,22 +737,24 @@ void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col) #ifndef WLED_DISABLE_2D if (is2D()) { - int vH = virtualHeight(); // segment height in logical pixels - int vW = virtualWidth(); + const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive) + const int vH = vHeight(); // segment height in logical pixels (is always >= 1) + // pre-scale color for all pixels + col = color_fade(col, currentBri()); switch (map1D2D) { case M12_Pixels: // use all available pixels as a long strip - setPixelColorXY(i % vW, i / vW, col); + setPixelColorXY(i % vW, i / vW, col, false); break; 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); + if (vStrip > 0) setPixelColorXY(vStrip - 1, vH - i - 1, col, false); + else for (int x = 0; x < vW; x++) setPixelColorXY(x, vH - i - 1, col, false); break; case M12_pArc: // expand in circular fashion from center if (i == 0) - setPixelColorXY(0, 0, col); + setPixelColorXY(0, 0, col, false); else { float r = i; float step = HALF_PI / (2.8284f * r + 4); // we only need (PI/4)/(r/sqrt(2)+1) steps @@ -742,8 +762,8 @@ void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col) int x = roundf(sin_t(rad) * r); int y = roundf(cos_t(rad) * r); // exploit symmetry - setPixelColorXY(x, y, col); - setPixelColorXY(y, x, col); + setPixelColorXY(x, y, col, false); + setPixelColorXY(y, x, col, false); } // Bresenham’s Algorithm (may not fill every pixel) //int d = 3 - (2*i); @@ -762,8 +782,8 @@ void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col) } break; case M12_pCorner: - for (int x = 0; x <= i; x++) setPixelColorXY(x, i, col); - for (int y = 0; y < i; y++) setPixelColorXY(i, y, col); + for (int x = 0; x <= i; x++) setPixelColorXY(x, i, col, false); + for (int y = 0; y < i; y++) setPixelColorXY(i, y, col, false); break; case M12_sPinwheel: { // i = angle --> 0 - 296 (Big), 0 - 192 (Medium), 0 - 72 (Small) @@ -802,7 +822,7 @@ void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col) int x = posx / Fixed_Scale; int y = posy / Fixed_Scale; // set pixel - if (x != lastX || y != lastY) setPixelColorXY(x, y, col); // only paint if pixel position is different + if (x != lastX || y != lastY) setPixelColorXY(x, y, col, false); // only paint if pixel position is different lastX = x; lastY = y; // advance to next position @@ -813,12 +833,12 @@ void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col) } } return; - } else if (Segment::maxHeight!=1 && (width()==1 || height()==1)) { + } else if (Segment::maxHeight != 1 && (width() == 1 || height() == 1)) { if (start < Segment::maxWidth*Segment::maxHeight) { // we have a vertical or horizontal 1D segment (WARNING: virtual...() may be transposed) int x = 0, y = 0; - if (virtualHeight()>1) y = i; - if (virtualWidth() >1) x = i; + if (vHeight() > 1) y = i; + if (vWidth() > 1) x = i; setPixelColorXY(x, y, col); return; } @@ -826,10 +846,8 @@ void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col) #endif unsigned len = length(); - uint8_t _bri_t = currentBri(); - if (_bri_t < 255) { - col = color_fade(col, _bri_t); - } + // if color is unscaled + if (unScaled) col = color_fade(col, currentBri()); // expand pixel (taking into account start, grouping, spacing [and offset]) i = i * groupLength(); @@ -907,8 +925,8 @@ uint32_t IRAM_ATTR_YN Segment::getPixelColor(int i) const #ifndef WLED_DISABLE_2D if (is2D()) { - int vH = virtualHeight(); // segment height in logical pixels - int vW = virtualWidth(); + const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive) + const int vH = vHeight(); // segment height in logical pixels (is always >= 1) switch (map1D2D) { case M12_Pixels: return getPixelColorXY(i % vW, i / vW); @@ -961,7 +979,7 @@ uint32_t IRAM_ATTR_YN Segment::getPixelColor(int i) const } #endif - if (reverse) i = virtualLength() - i - 1; + if (reverse) i = vLength() - i - 1; i *= groupLength(); i += start; // offset/phase @@ -1050,11 +1068,13 @@ void Segment::refreshLightCapabilities() { */ void Segment::fill(uint32_t c) { if (!isActive()) return; // not active - const int cols = is2D() ? virtualWidth() : virtualLength(); - const int rows = virtualHeight(); // will be 1 for 1D + const int cols = is2D() ? vWidth() : vLength(); + const int rows = vHeight(); // will be 1 for 1D + // pre-scale color for all pixels + c = color_fade(c, currentBri()); for (int y = 0; y < rows; y++) for (int x = 0; x < cols; x++) { - if (is2D()) setPixelColorXY(x, y, c); - else setPixelColor(x, c); + if (is2D()) setPixelColorXY(x, y, c, false); + else setPixelColor(x, c, false); } } @@ -1063,8 +1083,8 @@ void Segment::fill(uint32_t c) { */ void Segment::fade_out(uint8_t rate) { if (!isActive()) return; // not active - const int cols = is2D() ? virtualWidth() : virtualLength(); - const int rows = virtualHeight(); // will be 1 for 1D + const int cols = is2D() ? vWidth() : vLength(); + const int rows = vHeight(); // will be 1 for 1D rate = (255-rate) >> 1; float mappedRate = 1.0f / (float(rate) + 1.1f); @@ -1102,8 +1122,8 @@ void Segment::fade_out(uint8_t rate) { // fades all pixels to black using nscale8() void Segment::fadeToBlackBy(uint8_t fadeBy) { if (!isActive() || fadeBy == 0) return; // optimization - no scaling to apply - const int cols = is2D() ? virtualWidth() : virtualLength(); - const int rows = virtualHeight(); // will be 1 for 1D + const int cols = is2D() ? vWidth() : vLength(); + const int rows = vHeight(); // will be 1 for 1D for (int y = 0; y < rows; y++) for (int x = 0; x < cols; x++) { if (is2D()) setPixelColorXY(x, y, color_fade(getPixelColorXY(x,y), 255-fadeBy)); @@ -1126,7 +1146,7 @@ void Segment::blur(uint8_t blur_amount, bool smear) { #endif uint8_t keep = smear ? 255 : 255 - blur_amount; uint8_t seep = blur_amount >> (1 + smear); - unsigned vlength = virtualLength(); + unsigned vlength = vLength(); uint32_t carryover = BLACK; uint32_t lastnew; uint32_t last; @@ -1140,8 +1160,7 @@ void Segment::blur(uint8_t blur_amount, bool smear) { uint32_t prev = color_add(lastnew, part); // optimization: only set pixel if color has changed if (last != prev) setPixelColor(i - 1, prev); - } else // first pixel - setPixelColor(i, curnew); + } else setPixelColor(i, curnew); // first pixel lastnew = curnew; last = cur; // save original value for comparison on next iteration carryover = part; @@ -1156,7 +1175,7 @@ void Segment::blur(uint8_t blur_amount, bool smear) { */ uint32_t Segment::color_wheel(uint8_t pos) const { if (palette) return color_from_palette(pos, false, true, 0); // perhaps "strip.paletteBlend < 2" should be better instead of "true" - uint8_t w = W(currentColor(0)); + uint8_t w = W(getCurrentColor(0)); pos = 255 - pos; if (pos < 85) { return RGBW32((255 - pos * 3), 0, (pos * 3), w); @@ -1179,20 +1198,19 @@ uint32_t Segment::color_wheel(uint8_t pos) const { * @returns Single color from palette */ uint32_t Segment::color_from_palette(uint16_t i, bool mapping, bool wrap, uint8_t mcol, uint8_t pbri) const { - - uint32_t color = currentColor(mcol); + uint32_t color = getCurrentColor(mcol < NUM_COLORS ? mcol : 0); // default palette or no RGB support on segment if ((palette == 0 && mcol < NUM_COLORS) || !_isRGB) { - color = gamma32(color); - return (pbri == 255) ? color : color_fade(color, pbri, true); + return color_fade(color, pbri, true); } + const int vL = vLength(); unsigned paletteIndex = i; - if (mapping && virtualLength() > 1) paletteIndex = (i*255)/(virtualLength() -1); + if (mapping && vL > 1) paletteIndex = (i*255)/(vL -1); // paletteBlend: 0 - wrap when moving, 1 - always wrap, 2 - never wrap, 3 - none (undefined) if (!wrap && strip.paletteBlend != 3) paletteIndex = scale8(paletteIndex, 240); //cut off blend at palette "end" CRGBW palcol = ColorFromPalette(_currentPalette, paletteIndex, pbri, (strip.paletteBlend == 3)? NOBLEND:LINEARBLEND); // NOTE: paletteBlend should be global - palcol.w = gamma8(W(color)); + palcol.w = W(color); return palcol.color32; } @@ -1359,11 +1377,6 @@ void WS2812FX::service() { if (!seg.freeze) { //only run effect function if not frozen int oldCCT = BusManager::getSegmentCCT(); // store original CCT value (actually it is not Segment based) - _virtualSegmentLength = seg.virtualLength(); //SEGLEN - _colors_t[0] = gamma32(seg.currentColor(0)); - _colors_t[1] = gamma32(seg.currentColor(1)); - _colors_t[2] = gamma32(seg.currentColor(2)); - seg.setCurrentPalette(); // load actual palette // when correctWB is true we need to correct/adjust RGB value according to desired CCT value, but it will also affect actual WW/CW ratio // when cctFromRgb is true we implicitly calculate WW and CW from RGB values if (cctFromRgb) BusManager::setSegmentCCT(-1); @@ -1375,13 +1388,14 @@ void WS2812FX::service() { // overwritten by later effect. To enable seamless blending for every effect, additional LED buffer // would need to be allocated for each effect and then blended together for each pixel. [[maybe_unused]] uint8_t tmpMode = seg.currentMode(); // this will return old mode while in transition + seg.beginDraw(); // set up parameters for get/setPixelColor() delay = (*_mode[seg.mode])(); // run new/current mode #ifndef WLED_DISABLE_MODE_BLEND if (modeBlending && seg.mode != tmpMode) { Segment::tmpsegd_t _tmpSegData; Segment::modeBlend(true); // set semaphore seg.swapSegenv(_tmpSegData); // temporarily store new mode state (and swap it with transitional state) - _virtualSegmentLength = seg.virtualLength(); // update SEGLEN (mapping may have changed) + seg.beginDraw(); // set up parameters for get/setPixelColor() unsigned d2 = (*_mode[tmpMode])(); // run old mode seg.restoreSegenv(_tmpSegData); // restore mode state (will also update transitional state) delay = MIN(delay,d2); // use shortest delay @@ -1397,7 +1411,6 @@ void WS2812FX::service() { } _segment_index++; } - _virtualSegmentLength = 0; _isServicing = false; _triggered = false; diff --git a/wled00/udp.cpp b/wled00/udp.cpp index 60774d701..a615cefcb 100644 --- a/wled00/udp.cpp +++ b/wled00/udp.cpp @@ -416,18 +416,18 @@ void realtimeLock(uint32_t timeoutMs, byte md) start = mainseg.start; stop = mainseg.stop; mainseg.freeze = true; + // if WLED was off and using main segment only, freeze non-main segments so they stay off + if (bri == 0) { + for (size_t s = 0; s < strip.getSegmentsNum(); s++) { + strip.getSegment(s).freeze = true; + } + } } else { start = 0; stop = strip.getLengthTotal(); } // clear strip/segment for (size_t i = start; i < stop; i++) strip.setPixelColor(i,BLACK); - // if WLED was off and using main segment only, freeze non-main segments so they stay off - if (useMainSegmentOnly && bri == 0) { - for (size_t s=0; s < strip.getSegmentsNum(); s++) { - strip.getSegment(s).freeze = true; - } - } } // if strip is off (bri==0) and not already in RTM if (briT == 0 && !realtimeMode && !realtimeOverride) { @@ -510,12 +510,10 @@ void handleNotifications() rgbUdp.read(lbuf, packetSize); realtimeLock(realtimeTimeoutMs, REALTIME_MODE_HYPERION); if (realtimeOverride && !(realtimeMode && useMainSegmentOnly)) return; - unsigned id = 0; unsigned totalLen = strip.getLengthTotal(); - for (size_t i = 0; i < packetSize -2; i += 3) - { + if (useMainSegmentOnly) strip.getMainSegment().beginDraw(); // set up parameters for get/setPixelColor() + for (size_t i = 0, id = 0; i < packetSize -2 && id < totalLen; i += 3, id++) { setRealtimePixel(id, lbuf[i], lbuf[i+1], lbuf[i+2], 0); - id++; if (id >= totalLen) break; } if (!(realtimeMode && useMainSegmentOnly)) strip.show(); return; @@ -595,17 +593,11 @@ void handleNotifications() unsigned id = (tpmPayloadFrameSize/3)*(packetNum-1); //start LED unsigned totalLen = strip.getLengthTotal(); - for (size_t i = 6; i < tpmPayloadFrameSize + 4U; i += 3) - { - if (id < totalLen) - { - setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], 0); - id++; - } - else break; + if (useMainSegmentOnly) strip.getMainSegment().beginDraw(); // set up parameters for get/setPixelColor() + for (size_t i = 6; i < tpmPayloadFrameSize + 4U && id < totalLen; i += 3, id++) { + setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], 0); } - if (tpmPacketCount == numPackets) //reset packet count and show if all packets were received - { + if (tpmPacketCount == numPackets) { //reset packet count and show if all packets were received tpmPacketCount = 0; strip.show(); } @@ -629,6 +621,7 @@ void handleNotifications() if (realtimeOverride && !(realtimeMode && useMainSegmentOnly)) return; unsigned totalLen = strip.getLengthTotal(); + if (useMainSegmentOnly) strip.getMainSegment().beginDraw(); // set up parameters for get/setPixelColor() if (udpIn[0] == 1 && packetSize > 5) //warls { for (size_t i = 2; i < packetSize -3; i += 4) @@ -637,39 +630,29 @@ void handleNotifications() } } else if (udpIn[0] == 2 && packetSize > 4) //drgb { - unsigned id = 0; - for (size_t i = 2; i < packetSize -2; i += 3) + for (size_t i = 2, id = 0; i < packetSize -2 && id < totalLen; i += 3, id++) { setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], 0); - - id++; if (id >= totalLen) break; } } else if (udpIn[0] == 3 && packetSize > 6) //drgbw { - unsigned id = 0; - for (size_t i = 2; i < packetSize -3; i += 4) + for (size_t i = 2, id = 0; i < packetSize -3 && id < totalLen; i += 4, id++) { setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], udpIn[i+3]); - - id++; if (id >= totalLen) break; } } else if (udpIn[0] == 4 && packetSize > 7) //dnrgb { unsigned id = ((udpIn[3] << 0) & 0xFF) + ((udpIn[2] << 8) & 0xFF00); - for (size_t i = 4; i < packetSize -2; i += 3) + for (size_t i = 4; i < packetSize -2 && id < totalLen; i += 3, id++) { - if (id >= totalLen) break; setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], 0); - id++; } } else if (udpIn[0] == 5 && packetSize > 8) //dnrgbw { unsigned id = ((udpIn[3] << 0) & 0xFF) + ((udpIn[2] << 8) & 0xFF00); - for (size_t i = 4; i < packetSize -2; i += 4) + for (size_t i = 4; i < packetSize -2 && id < totalLen; i += 4, id++) { - if (id >= totalLen) break; setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], udpIn[i+3]); - id++; } } strip.show(); @@ -705,8 +688,7 @@ void setRealtimePixel(uint16_t i, byte r, byte g, byte b, byte w) w = gamma8(w); } if (useMainSegmentOnly) { - Segment &seg = strip.getMainSegment(); - if (pix10) return; + char nS[32]; if (subPage == SUBPAGE_MENU) { @@ -259,8 +260,6 @@ void getSettingsJS(byte subPage, Print& settingsScript) if (subPage == SUBPAGE_LEDS) { - char nS[32]; - appendGPIOinfo(settingsScript); settingsScript.print(SET_F("d.ledTypes=")); settingsScript.print(BusManager::getLEDTypesJSONString().c_str()); settingsScript.print(";"); @@ -399,7 +398,6 @@ void getSettingsJS(byte subPage, Print& settingsScript) if (subPage == SUBPAGE_SYNC) { - [[maybe_unused]] char nS[32]; printSetFormValue(settingsScript,PSTR("UP"),udpPort); printSetFormValue(settingsScript,PSTR("U2"),udpPort2); #ifndef WLED_DISABLE_ESPNOW From 9114867578477e4cf5fa652769ea0690503c5f10 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Sat, 28 Sep 2024 18:48:43 +0200 Subject: [PATCH 046/287] Fix compiler error --- wled00/FX.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/FX.h b/wled00/FX.h index 98082c8d8..b4aaf3c48 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -833,7 +833,7 @@ class WS2812FX { // 96 bytes addEffect(uint8_t id, mode_ptr mode_fn, const char *mode_name); // add effect to the list; defined in FX.cpp; inline uint8_t getBrightness() const { return _brightness; } // returns current strip brightness - inline constexpr unsigned getMaxSegments() { return MAX_NUM_SEGMENTS; } // returns maximum number of supported segments (fixed value) + inline static constexpr unsigned getMaxSegments() { return MAX_NUM_SEGMENTS; } // returns maximum number of supported segments (fixed value) inline uint8_t getSegmentsNum() const { return _segments.size(); } // returns currently present segments inline uint8_t getCurrSegmentId() const { return _segment_index; } // returns current segment index (only valid while strip.isServicing()) inline uint8_t getMainSegmentId() const { return _mainSegment; } // returns main segment index From cc87b32206e602adc1a3c34f659ca5498fdd269a Mon Sep 17 00:00:00 2001 From: Will Miles Date: Sat, 28 Sep 2024 10:02:05 -0400 Subject: [PATCH 047/287] Support PWM phase shifts on ESP8266 Use the phase-locked soft PWM from the Arduino core to implement the same PWM phase management as ESP32s are using. The soft PWM code is vendored in, as it was previously, to add the NMI workaround from #4035. Completes #4034 --- .../src/core_esp8266_waveform_phase.cpp | 478 ++++++++++++ .../src/core_esp8266_waveform_pwm.cpp | 717 ------------------ wled00/bus_manager.cpp | 46 +- 3 files changed, 507 insertions(+), 734 deletions(-) create mode 100644 lib/ESP8266PWM/src/core_esp8266_waveform_phase.cpp delete mode 100644 lib/ESP8266PWM/src/core_esp8266_waveform_pwm.cpp diff --git a/lib/ESP8266PWM/src/core_esp8266_waveform_phase.cpp b/lib/ESP8266PWM/src/core_esp8266_waveform_phase.cpp new file mode 100644 index 000000000..b846091bf --- /dev/null +++ b/lib/ESP8266PWM/src/core_esp8266_waveform_phase.cpp @@ -0,0 +1,478 @@ +/* esp8266_waveform imported from platform source code + Modified for WLED to work around a fault in the NMI handling, + which can result in the system locking up and hard WDT crashes. + + Imported from https://github.com/esp8266/Arduino/blob/7e0d20e2b9034994f573a236364e0aef17fd66de/cores/esp8266/core_esp8266_waveform_phase.cpp +*/ + + +/* + esp8266_waveform - General purpose waveform generation and control, + supporting outputs on all pins in parallel. + + Copyright (c) 2018 Earle F. Philhower, III. All rights reserved. + Copyright (c) 2020 Dirk O. Kaar. + + The core idea is to have a programmable waveform generator with a unique + high and low period (defined in microseconds or CPU clock cycles). TIMER1 is + set to 1-shot mode and is always loaded with the time until the next edge + of any live waveforms. + + Up to one waveform generator per pin supported. + + Each waveform generator is synchronized to the ESP clock cycle counter, not the + timer. This allows for removing interrupt jitter and delay as the counter + always increments once per 80MHz clock. Changes to a waveform are + contiguous and only take effect on the next waveform transition, + allowing for smooth transitions. + + This replaces older tone(), analogWrite(), and the Servo classes. + + Everywhere in the code where "ccy" or "ccys" is used, it means ESP.getCycleCount() + clock cycle time, or an interval measured in clock cycles, but not TIMER1 + cycles (which may be 2 CPU clock cycles @ 160MHz). + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "core_esp8266_waveform.h" +#include +#include "debug.h" +#include "ets_sys.h" +#include + + +// ----- @willmmiles begin patch ----- +// Linker magic +extern "C" void usePWMFixedNMI(void) {}; + +// NMI crash workaround +// Sometimes the NMI fails to return, stalling the CPU. When this happens, +// the next NMI gets a return address /inside the NMI handler function/. +// We work around this by caching the last NMI return address, and restoring +// the epc3 and eps3 registers to the previous values if the observed epc3 +// happens to be pointing to the _NMILevelVector function. +extern "C" void _NMILevelVector(); +extern "C" void _UserExceptionVector_1(); // the next function after _NMILevelVector +static inline IRAM_ATTR void nmiCrashWorkaround() { + static uintptr_t epc3_backup, eps3_backup; + + uintptr_t epc3, eps3; + __asm__ __volatile__("rsr %0,epc3; rsr %1,eps3":"=a"(epc3),"=a" (eps3)); + if ((epc3 < (uintptr_t) &_NMILevelVector) || (epc3 >= (uintptr_t) &_UserExceptionVector_1)) { + // Address is good; save backup + epc3_backup = epc3; + eps3_backup = eps3; + } else { + // Address is inside the NMI handler -- restore from backup + __asm__ __volatile__("wsr %0,epc3; wsr %1,eps3"::"a"(epc3_backup),"a"(eps3_backup)); + } +} +// ----- @willmmiles end patch ----- + + +// No-op calls to override the PWM implementation +extern "C" void _setPWMFreq_weak(uint32_t freq) { (void) freq; } +extern "C" IRAM_ATTR bool _stopPWM_weak(int pin) { (void) pin; return false; } +extern "C" bool _setPWM_weak(int pin, uint32_t val, uint32_t range) { (void) pin; (void) val; (void) range; return false; } + + +// Timer is 80MHz fixed. 160MHz CPU frequency need scaling. +constexpr bool ISCPUFREQ160MHZ = clockCyclesPerMicrosecond() == 160; +// Maximum delay between IRQs, Timer1, <= 2^23 / 80MHz +constexpr int32_t MAXIRQTICKSCCYS = microsecondsToClockCycles(10000); +// Maximum servicing time for any single IRQ +constexpr uint32_t ISRTIMEOUTCCYS = microsecondsToClockCycles(18); +// The latency between in-ISR rearming of the timer and the earliest firing +constexpr int32_t IRQLATENCYCCYS = microsecondsToClockCycles(2); +// The SDK and hardware take some time to actually get to our NMI code +constexpr int32_t DELTAIRQCCYS = ISCPUFREQ160MHZ ? + microsecondsToClockCycles(2) >> 1 : microsecondsToClockCycles(2); + +// for INFINITE, the NMI proceeds on the waveform without expiry deadline. +// for EXPIRES, the NMI expires the waveform automatically on the expiry ccy. +// for UPDATEEXPIRY, the NMI recomputes the exact expiry ccy and transitions to EXPIRES. +// for INIT, the NMI initializes nextPeriodCcy, and if expiryCcy != 0 includes UPDATEEXPIRY. +enum class WaveformMode : uint8_t {INFINITE = 0, EXPIRES = 1, UPDATEEXPIRY = 2, INIT = 3}; + +// Waveform generator can create tones, PWM, and servos +typedef struct { + uint32_t nextPeriodCcy; // ESP clock cycle when a period begins. If WaveformMode::INIT, temporarily holds positive phase offset ccy count + uint32_t endDutyCcy; // ESP clock cycle when going from duty to off + int32_t dutyCcys; // Set next off cycle at low->high to maintain phase + int32_t adjDutyCcys; // Temporary correction for next period + int32_t periodCcys; // Set next phase cycle at low->high to maintain phase + uint32_t expiryCcy; // For time-limited waveform, the CPU clock cycle when this waveform must stop. If WaveformMode::UPDATE, temporarily holds relative ccy count + WaveformMode mode; + int8_t alignPhase; // < 0 no phase alignment, otherwise starts waveform in relative phase offset to given pin + bool autoPwm; // perform PWM duty to idle cycle ratio correction under high load at the expense of precise timings +} Waveform; + +namespace { + + static struct { + Waveform pins[17]; // State of all possible pins + uint32_t states = 0; // Is the pin high or low, updated in NMI so no access outside the NMI code + uint32_t enabled = 0; // Is it actively running, updated in NMI so no access outside the NMI code + + // Enable lock-free by only allowing updates to waveform.states and waveform.enabled from IRQ service routine + int32_t toSetBits = 0; // Message to the NMI handler to start/modify exactly one waveform + int32_t toDisableBits = 0; // Message to the NMI handler to disable exactly one pin from waveform generation + + uint32_t(*timer1CB)() = nullptr; + + bool timer1Running = false; + + uint32_t nextEventCcy; + } waveform; + +} + +// Interrupt on/off control +static IRAM_ATTR void timer1Interrupt(); + +// Non-speed critical bits +#pragma GCC optimize ("Os") + +static void initTimer() { + timer1_disable(); + ETS_FRC_TIMER1_INTR_ATTACH(NULL, NULL); + ETS_FRC_TIMER1_NMI_INTR_ATTACH(timer1Interrupt); + timer1_enable(TIM_DIV1, TIM_EDGE, TIM_SINGLE); + waveform.timer1Running = true; + timer1_write(IRQLATENCYCCYS); // Cause an interrupt post-haste +} + +static void IRAM_ATTR deinitTimer() { + ETS_FRC_TIMER1_NMI_INTR_ATTACH(NULL); + timer1_disable(); + timer1_isr_init(); + waveform.timer1Running = false; +} + +extern "C" { + +// Set a callback. Pass in NULL to stop it +void setTimer1Callback_weak(uint32_t (*fn)()) { + waveform.timer1CB = fn; + std::atomic_thread_fence(std::memory_order_acq_rel); + if (!waveform.timer1Running && fn) { + initTimer(); + } else if (waveform.timer1Running && !fn && !waveform.enabled) { + deinitTimer(); + } +} + +// Start up a waveform on a pin, or change the current one. Will change to the new +// waveform smoothly on next low->high transition. For immediate change, stopWaveform() +// first, then it will immediately begin. +int startWaveformClockCycles_weak(uint8_t pin, uint32_t highCcys, uint32_t lowCcys, + uint32_t runTimeCcys, int8_t alignPhase, uint32_t phaseOffsetCcys, bool autoPwm) { + uint32_t periodCcys = highCcys + lowCcys; + if (periodCcys < MAXIRQTICKSCCYS) { + if (!highCcys) { + periodCcys = (MAXIRQTICKSCCYS / periodCcys) * periodCcys; + } + else if (!lowCcys) { + highCcys = periodCcys = (MAXIRQTICKSCCYS / periodCcys) * periodCcys; + } + } + // sanity checks, including mixed signed/unsigned arithmetic safety + if ((pin > 16) || isFlashInterfacePin(pin) || (alignPhase > 16) || + static_cast(periodCcys) <= 0 || + static_cast(highCcys) < 0 || static_cast(lowCcys) < 0) { + return false; + } + Waveform& wave = waveform.pins[pin]; + wave.dutyCcys = highCcys; + wave.adjDutyCcys = 0; + wave.periodCcys = periodCcys; + wave.autoPwm = autoPwm; + + std::atomic_thread_fence(std::memory_order_acquire); + const uint32_t pinBit = 1UL << pin; + if (!(waveform.enabled & pinBit)) { + // wave.nextPeriodCcy and wave.endDutyCcy are initialized by the ISR + wave.nextPeriodCcy = phaseOffsetCcys; + wave.expiryCcy = runTimeCcys; // in WaveformMode::INIT, temporarily hold relative cycle count + wave.mode = WaveformMode::INIT; + wave.alignPhase = (alignPhase < 0) ? -1 : alignPhase; + if (!wave.dutyCcys) { + // If initially at zero duty cycle, force GPIO off + if (pin == 16) { + GP16O = 0; + } + else { + GPOC = pinBit; + } + } + std::atomic_thread_fence(std::memory_order_release); + waveform.toSetBits = 1UL << pin; + std::atomic_thread_fence(std::memory_order_release); + if (!waveform.timer1Running) { + initTimer(); + } + else if (T1V > IRQLATENCYCCYS) { + // Must not interfere if Timer is due shortly + timer1_write(IRQLATENCYCCYS); + } + } + else { + wave.mode = WaveformMode::INFINITE; // turn off possible expiry to make update atomic from NMI + std::atomic_thread_fence(std::memory_order_release); + wave.expiryCcy = runTimeCcys; // in WaveformMode::UPDATEEXPIRY, temporarily hold relative cycle count + if (runTimeCcys) { + wave.mode = WaveformMode::UPDATEEXPIRY; + std::atomic_thread_fence(std::memory_order_release); + waveform.toSetBits = 1UL << pin; + } + } + std::atomic_thread_fence(std::memory_order_acq_rel); + while (waveform.toSetBits) { + esp_yield(); // Wait for waveform to update + std::atomic_thread_fence(std::memory_order_acquire); + } + return true; +} + +// Stops a waveform on a pin +IRAM_ATTR int stopWaveform_weak(uint8_t pin) { + // Can't possibly need to stop anything if there is no timer active + if (!waveform.timer1Running) { + return false; + } + // If user sends in a pin >16 but <32, this will always point to a 0 bit + // If they send >=32, then the shift will result in 0 and it will also return false + std::atomic_thread_fence(std::memory_order_acquire); + const uint32_t pinBit = 1UL << pin; + if (waveform.enabled & pinBit) { + waveform.toDisableBits = 1UL << pin; + std::atomic_thread_fence(std::memory_order_release); + // Must not interfere if Timer is due shortly + if (T1V > IRQLATENCYCCYS) { + timer1_write(IRQLATENCYCCYS); + } + while (waveform.toDisableBits) { + /* no-op */ // Can't delay() since stopWaveform may be called from an IRQ + std::atomic_thread_fence(std::memory_order_acquire); + } + } + if (!waveform.enabled && !waveform.timer1CB) { + deinitTimer(); + } + return true; +} + +}; + +// Speed critical bits +#pragma GCC optimize ("O2") + +// For dynamic CPU clock frequency switch in loop the scaling logic would have to be adapted. +// Using constexpr makes sure that the CPU clock frequency is compile-time fixed. +static inline IRAM_ATTR int32_t scaleCcys(const int32_t ccys, const bool isCPU2X) { + if (ISCPUFREQ160MHZ) { + return isCPU2X ? ccys : (ccys >> 1); + } + else { + return isCPU2X ? (ccys << 1) : ccys; + } +} + +static IRAM_ATTR void timer1Interrupt() { + // ----- @willmmiles begin patch ----- + nmiCrashWorkaround(); + // ----- @willmmiles end patch ----- + + const uint32_t isrStartCcy = ESP.getCycleCount(); + int32_t clockDrift = isrStartCcy - waveform.nextEventCcy; + const bool isCPU2X = CPU2X & 1; + if ((waveform.toSetBits && !(waveform.enabled & waveform.toSetBits)) || waveform.toDisableBits) { + // Handle enable/disable requests from main app. + waveform.enabled = (waveform.enabled & ~waveform.toDisableBits) | waveform.toSetBits; // Set the requested waveforms on/off + // Find the first GPIO being generated by checking GCC's find-first-set (returns 1 + the bit of the first 1 in an int32_t) + waveform.toDisableBits = 0; + } + + if (waveform.toSetBits) { + const int toSetPin = __builtin_ffs(waveform.toSetBits) - 1; + Waveform& wave = waveform.pins[toSetPin]; + switch (wave.mode) { + case WaveformMode::INIT: + waveform.states &= ~waveform.toSetBits; // Clear the state of any just started + if (wave.alignPhase >= 0 && waveform.enabled & (1UL << wave.alignPhase)) { + wave.nextPeriodCcy = waveform.pins[wave.alignPhase].nextPeriodCcy + wave.nextPeriodCcy; + } + else { + wave.nextPeriodCcy = waveform.nextEventCcy; + } + if (!wave.expiryCcy) { + wave.mode = WaveformMode::INFINITE; + break; + } + // fall through + case WaveformMode::UPDATEEXPIRY: + // in WaveformMode::UPDATEEXPIRY, expiryCcy temporarily holds relative CPU cycle count + wave.expiryCcy = wave.nextPeriodCcy + scaleCcys(wave.expiryCcy, isCPU2X); + wave.mode = WaveformMode::EXPIRES; + break; + default: + break; + } + waveform.toSetBits = 0; + } + + // Exit the loop if the next event, if any, is sufficiently distant. + const uint32_t isrTimeoutCcy = isrStartCcy + ISRTIMEOUTCCYS; + uint32_t busyPins = waveform.enabled; + waveform.nextEventCcy = isrStartCcy + MAXIRQTICKSCCYS; + + uint32_t now = ESP.getCycleCount(); + uint32_t isrNextEventCcy = now; + while (busyPins) { + if (static_cast(isrNextEventCcy - now) > IRQLATENCYCCYS) { + waveform.nextEventCcy = isrNextEventCcy; + break; + } + isrNextEventCcy = waveform.nextEventCcy; + uint32_t loopPins = busyPins; + while (loopPins) { + const int pin = __builtin_ffsl(loopPins) - 1; + const uint32_t pinBit = 1UL << pin; + loopPins ^= pinBit; + + Waveform& wave = waveform.pins[pin]; + + if (clockDrift) { + wave.endDutyCcy += clockDrift; + wave.nextPeriodCcy += clockDrift; + wave.expiryCcy += clockDrift; + } + + uint32_t waveNextEventCcy = (waveform.states & pinBit) ? wave.endDutyCcy : wave.nextPeriodCcy; + if (WaveformMode::EXPIRES == wave.mode && + static_cast(waveNextEventCcy - wave.expiryCcy) >= 0 && + static_cast(now - wave.expiryCcy) >= 0) { + // Disable any waveforms that are done + waveform.enabled ^= pinBit; + busyPins ^= pinBit; + } + else { + const int32_t overshootCcys = now - waveNextEventCcy; + if (overshootCcys >= 0) { + const int32_t periodCcys = scaleCcys(wave.periodCcys, isCPU2X); + if (waveform.states & pinBit) { + // active configuration and forward are 100% duty + if (wave.periodCcys == wave.dutyCcys) { + wave.nextPeriodCcy += periodCcys; + wave.endDutyCcy = wave.nextPeriodCcy; + } + else { + if (wave.autoPwm) { + wave.adjDutyCcys += overshootCcys; + } + waveform.states ^= pinBit; + if (16 == pin) { + GP16O = 0; + } + else { + GPOC = pinBit; + } + } + waveNextEventCcy = wave.nextPeriodCcy; + } + else { + wave.nextPeriodCcy += periodCcys; + if (!wave.dutyCcys) { + wave.endDutyCcy = wave.nextPeriodCcy; + } + else { + int32_t dutyCcys = scaleCcys(wave.dutyCcys, isCPU2X); + if (dutyCcys <= wave.adjDutyCcys) { + dutyCcys >>= 1; + wave.adjDutyCcys -= dutyCcys; + } + else if (wave.adjDutyCcys) { + dutyCcys -= wave.adjDutyCcys; + wave.adjDutyCcys = 0; + } + wave.endDutyCcy = now + dutyCcys; + if (static_cast(wave.endDutyCcy - wave.nextPeriodCcy) > 0) { + wave.endDutyCcy = wave.nextPeriodCcy; + } + waveform.states |= pinBit; + if (16 == pin) { + GP16O = 1; + } + else { + GPOS = pinBit; + } + } + waveNextEventCcy = wave.endDutyCcy; + } + + if (WaveformMode::EXPIRES == wave.mode && static_cast(waveNextEventCcy - wave.expiryCcy) > 0) { + waveNextEventCcy = wave.expiryCcy; + } + } + + if (static_cast(waveNextEventCcy - isrTimeoutCcy) >= 0) { + busyPins ^= pinBit; + if (static_cast(waveform.nextEventCcy - waveNextEventCcy) > 0) { + waveform.nextEventCcy = waveNextEventCcy; + } + } + else if (static_cast(isrNextEventCcy - waveNextEventCcy) > 0) { + isrNextEventCcy = waveNextEventCcy; + } + } + now = ESP.getCycleCount(); + } + clockDrift = 0; + } + + int32_t callbackCcys = 0; + if (waveform.timer1CB) { + callbackCcys = scaleCcys(waveform.timer1CB(), isCPU2X); + } + now = ESP.getCycleCount(); + int32_t nextEventCcys = waveform.nextEventCcy - now; + // Account for unknown duration of timer1CB(). + if (waveform.timer1CB && nextEventCcys > callbackCcys) { + waveform.nextEventCcy = now + callbackCcys; + nextEventCcys = callbackCcys; + } + + // Timer is 80MHz fixed. 160MHz CPU frequency need scaling. + int32_t deltaIrqCcys = DELTAIRQCCYS; + int32_t irqLatencyCcys = IRQLATENCYCCYS; + if (isCPU2X) { + nextEventCcys >>= 1; + deltaIrqCcys >>= 1; + irqLatencyCcys >>= 1; + } + + // Firing timer too soon, the NMI occurs before ISR has returned. + if (nextEventCcys < irqLatencyCcys + deltaIrqCcys) { + waveform.nextEventCcy = now + IRQLATENCYCCYS + DELTAIRQCCYS; + nextEventCcys = irqLatencyCcys; + } + else { + nextEventCcys -= deltaIrqCcys; + } + + // Register access is fast and edge IRQ was configured before. + T1L = nextEventCcys; +} diff --git a/lib/ESP8266PWM/src/core_esp8266_waveform_pwm.cpp b/lib/ESP8266PWM/src/core_esp8266_waveform_pwm.cpp deleted file mode 100644 index 78c7160d9..000000000 --- a/lib/ESP8266PWM/src/core_esp8266_waveform_pwm.cpp +++ /dev/null @@ -1,717 +0,0 @@ -/* esp8266_waveform imported from platform source code - Modified for WLED to work around a fault in the NMI handling, - which can result in the system locking up and hard WDT crashes. - - Imported from https://github.com/esp8266/Arduino/blob/7e0d20e2b9034994f573a236364e0aef17fd66de/cores/esp8266/core_esp8266_waveform_pwm.cpp -*/ - -/* - esp8266_waveform - General purpose waveform generation and control, - supporting outputs on all pins in parallel. - - Copyright (c) 2018 Earle F. Philhower, III. All rights reserved. - - The core idea is to have a programmable waveform generator with a unique - high and low period (defined in microseconds or CPU clock cycles). TIMER1 - is set to 1-shot mode and is always loaded with the time until the next - edge of any live waveforms. - - Up to one waveform generator per pin supported. - - Each waveform generator is synchronized to the ESP clock cycle counter, not - the timer. This allows for removing interrupt jitter and delay as the - counter always increments once per 80MHz clock. Changes to a waveform are - contiguous and only take effect on the next waveform transition, - allowing for smooth transitions. - - This replaces older tone(), analogWrite(), and the Servo classes. - - Everywhere in the code where "cycles" is used, it means ESP.getCycleCount() - clock cycle count, or an interval measured in CPU clock cycles, but not - TIMER1 cycles (which may be 2 CPU clock cycles @ 160MHz). - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ - - -#include -#include -#include "ets_sys.h" -#include "core_esp8266_waveform.h" -#include "user_interface.h" - -extern "C" { - -// Linker magic -void usePWMFixedNMI() {}; - -// Maximum delay between IRQs -#define MAXIRQUS (10000) - -// Waveform generator can create tones, PWM, and servos -typedef struct { - uint32_t nextServiceCycle; // ESP cycle timer when a transition required - uint32_t expiryCycle; // For time-limited waveform, the cycle when this waveform must stop - uint32_t timeHighCycles; // Actual running waveform period (adjusted using desiredCycles) - uint32_t timeLowCycles; // - uint32_t desiredHighCycles; // Ideal waveform period to drive the error signal - uint32_t desiredLowCycles; // - uint32_t lastEdge; // Cycle when this generator last changed -} Waveform; - -class WVFState { -public: - Waveform waveform[17]; // State of all possible pins - uint32_t waveformState = 0; // Is the pin high or low, updated in NMI so no access outside the NMI code - uint32_t waveformEnabled = 0; // Is it actively running, updated in NMI so no access outside the NMI code - - // Enable lock-free by only allowing updates to waveformState and waveformEnabled from IRQ service routine - uint32_t waveformToEnable = 0; // Message to the NMI handler to start a waveform on a inactive pin - uint32_t waveformToDisable = 0; // Message to the NMI handler to disable a pin from waveform generation - - uint32_t waveformToChange = 0; // Mask of pin to change. One bit set in main app, cleared when effected in the NMI - uint32_t waveformNewHigh = 0; - uint32_t waveformNewLow = 0; - - uint32_t (*timer1CB)() = NULL; - - // Optimize the NMI inner loop by keeping track of the min and max GPIO that we - // are generating. In the common case (1 PWM) these may be the same pin and - // we can avoid looking at the other pins. - uint16_t startPin = 0; - uint16_t endPin = 0; -}; -static WVFState wvfState; - - -// Ensure everything is read/written to RAM -#define MEMBARRIER() { __asm__ volatile("" ::: "memory"); } - -// Non-speed critical bits -#pragma GCC optimize ("Os") - -// Interrupt on/off control -static IRAM_ATTR void timer1Interrupt(); -static bool timerRunning = false; - -static __attribute__((noinline)) void initTimer() { - if (!timerRunning) { - timer1_disable(); - ETS_FRC_TIMER1_INTR_ATTACH(NULL, NULL); - ETS_FRC_TIMER1_NMI_INTR_ATTACH(timer1Interrupt); - timer1_enable(TIM_DIV1, TIM_EDGE, TIM_SINGLE); - timerRunning = true; - timer1_write(microsecondsToClockCycles(10)); - } -} - -static IRAM_ATTR void forceTimerInterrupt() { - if (T1L > microsecondsToClockCycles(10)) { - T1L = microsecondsToClockCycles(10); - } -} - -// PWM implementation using special purpose state machine -// -// Keep an ordered list of pins with the delta in cycles between each -// element, with a terminal entry making up the remainder of the PWM -// period. With this method sum(all deltas) == PWM period clock cycles. -// -// At t=0 set all pins high and set the timeout for the 1st edge. -// On interrupt, if we're at the last element reset to t=0 state -// Otherwise, clear that pin down and set delay for next element -// and so forth. - -constexpr int maxPWMs = 8; - -// PWM machine state -typedef struct PWMState { - uint32_t mask; // Bitmask of active pins - uint32_t cnt; // How many entries - uint32_t idx; // Where the state machine is along the list - uint8_t pin[maxPWMs + 1]; - uint32_t delta[maxPWMs + 1]; - uint32_t nextServiceCycle; // Clock cycle for next step - struct PWMState *pwmUpdate; // Set by main code, cleared by ISR -} PWMState; - -static PWMState pwmState; -static uint32_t _pwmFreq = 1000; -static uint32_t _pwmPeriod = microsecondsToClockCycles(1000000UL) / _pwmFreq; - - -// If there are no more scheduled activities, shut down Timer 1. -// Otherwise, do nothing. -static IRAM_ATTR void disableIdleTimer() { - if (timerRunning && !wvfState.waveformEnabled && !pwmState.cnt && !wvfState.timer1CB) { - ETS_FRC_TIMER1_NMI_INTR_ATTACH(NULL); - timer1_disable(); - timer1_isr_init(); - timerRunning = false; - } -} - -// Notify the NMI that a new PWM state is available through the mailbox. -// Wait for mailbox to be emptied (either busy or delay() as needed) -static IRAM_ATTR void _notifyPWM(PWMState *p, bool idle) { - p->pwmUpdate = nullptr; - pwmState.pwmUpdate = p; - MEMBARRIER(); - forceTimerInterrupt(); - while (pwmState.pwmUpdate) { - if (idle) { - esp_yield(); - } - MEMBARRIER(); - } -} - -static void _addPWMtoList(PWMState &p, int pin, uint32_t val, uint32_t range); - - -// Called when analogWriteFreq() changed to update the PWM total period -//extern void _setPWMFreq_weak(uint32_t freq) __attribute__((weak)); -void _setPWMFreq_weak(uint32_t freq) { - _pwmFreq = freq; - - // Convert frequency into clock cycles - uint32_t cc = microsecondsToClockCycles(1000000UL) / freq; - - // Simple static adjustment to bring period closer to requested due to overhead - // Empirically determined as a constant PWM delay and a function of the number of PWMs -#if F_CPU == 80000000 - cc -= ((microsecondsToClockCycles(pwmState.cnt) * 13) >> 4) + 110; -#else - cc -= ((microsecondsToClockCycles(pwmState.cnt) * 10) >> 4) + 75; -#endif - - if (cc == _pwmPeriod) { - return; // No change - } - - _pwmPeriod = cc; - - if (pwmState.cnt) { - PWMState p; // The working copy since we can't edit the one in use - p.mask = 0; - p.cnt = 0; - for (uint32_t i = 0; i < pwmState.cnt; i++) { - auto pin = pwmState.pin[i]; - _addPWMtoList(p, pin, wvfState.waveform[pin].desiredHighCycles, wvfState.waveform[pin].desiredLowCycles); - } - // Update and wait for mailbox to be emptied - initTimer(); - _notifyPWM(&p, true); - disableIdleTimer(); - } -} -/* -static void _setPWMFreq_bound(uint32_t freq) __attribute__((weakref("_setPWMFreq_weak"))); -void _setPWMFreq(uint32_t freq) { - _setPWMFreq_bound(freq); -} -*/ - -// Helper routine to remove an entry from the state machine -// and clean up any marked-off entries -static void _cleanAndRemovePWM(PWMState *p, int pin) { - uint32_t leftover = 0; - uint32_t in, out; - for (in = 0, out = 0; in < p->cnt; in++) { - if ((p->pin[in] != pin) && (p->mask & (1<pin[in]))) { - p->pin[out] = p->pin[in]; - p->delta[out] = p->delta[in] + leftover; - leftover = 0; - out++; - } else { - leftover += p->delta[in]; - p->mask &= ~(1<pin[in]); - } - } - p->cnt = out; - // Final pin is never used: p->pin[out] = 0xff; - p->delta[out] = p->delta[in] + leftover; -} - - -// Disable PWM on a specific pin (i.e. when a digitalWrite or analogWrite(0%/100%)) -//extern bool _stopPWM_weak(uint8_t pin) __attribute__((weak)); -IRAM_ATTR bool _stopPWM_weak(uint8_t pin) { - if (!((1<= _pwmPeriod) { - cc = _pwmPeriod - 1; - } - - if (p.cnt == 0) { - // Starting up from scratch, special case 1st element and PWM period - p.pin[0] = pin; - p.delta[0] = cc; - // Final pin is never used: p.pin[1] = 0xff; - p.delta[1] = _pwmPeriod - cc; - } else { - uint32_t ttl = 0; - uint32_t i; - // Skip along until we're at the spot to insert - for (i=0; (i <= p.cnt) && (ttl + p.delta[i] < cc); i++) { - ttl += p.delta[i]; - } - // Shift everything out by one to make space for new edge - for (int32_t j = p.cnt; j >= (int)i; j--) { - p.pin[j + 1] = p.pin[j]; - p.delta[j + 1] = p.delta[j]; - } - int off = cc - ttl; // The delta from the last edge to the one we're inserting - p.pin[i] = pin; - p.delta[i] = off; // Add the delta to this new pin - p.delta[i + 1] -= off; // And subtract it from the follower to keep sum(deltas) constant - } - p.cnt++; - p.mask |= 1<= maxPWMs) { - return false; // No space left - } - - // Sanity check for all-on/off - uint32_t cc = (_pwmPeriod * val) / range; - if ((cc == 0) || (cc >= _pwmPeriod)) { - digitalWrite(pin, cc ? HIGH : LOW); - return true; - } - - _addPWMtoList(p, pin, val, range); - - // Set mailbox and wait for ISR to copy it over - initTimer(); - _notifyPWM(&p, true); - disableIdleTimer(); - - // Potentially recalculate the PWM period if we've added another pin - _setPWMFreq(_pwmFreq); - - return true; -} -/* -static bool _setPWM_bound(int pin, uint32_t val, uint32_t range) __attribute__((weakref("_setPWM_weak"))); -bool _setPWM(int pin, uint32_t val, uint32_t range) { - return _setPWM_bound(pin, val, range); -} -*/ - -// Start up a waveform on a pin, or change the current one. Will change to the new -// waveform smoothly on next low->high transition. For immediate change, stopWaveform() -// first, then it will immediately begin. -//extern int startWaveformClockCycles_weak(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles, int8_t alignPhase, uint32_t phaseOffsetUS, bool autoPwm) __attribute__((weak)); -int startWaveformClockCycles_weak(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles, - int8_t alignPhase, uint32_t phaseOffsetUS, bool autoPwm) { - (void) alignPhase; - (void) phaseOffsetUS; - (void) autoPwm; - - if ((pin > 16) || isFlashInterfacePin(pin) || (timeHighCycles == 0)) { - return false; - } - Waveform *wave = &wvfState.waveform[pin]; - wave->expiryCycle = runTimeCycles ? ESP.getCycleCount() + runTimeCycles : 0; - if (runTimeCycles && !wave->expiryCycle) { - wave->expiryCycle = 1; // expiryCycle==0 means no timeout, so avoid setting it - } - - _stopPWM(pin); // Make sure there's no PWM live here - - uint32_t mask = 1<timeHighCycles = timeHighCycles; - wave->desiredHighCycles = timeHighCycles; - wave->timeLowCycles = timeLowCycles; - wave->desiredLowCycles = timeLowCycles; - wave->lastEdge = 0; - wave->nextServiceCycle = ESP.getCycleCount() + microsecondsToClockCycles(1); - wvfState.waveformToEnable |= mask; - MEMBARRIER(); - initTimer(); - forceTimerInterrupt(); - while (wvfState.waveformToEnable) { - esp_yield(); // Wait for waveform to update - MEMBARRIER(); - } - } - - return true; -} -/* -static int startWaveformClockCycles_bound(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles, int8_t alignPhase, uint32_t phaseOffsetUS, bool autoPwm) __attribute__((weakref("startWaveformClockCycles_weak"))); -int startWaveformClockCycles(uint8_t pin, uint32_t timeHighCycles, uint32_t timeLowCycles, uint32_t runTimeCycles, int8_t alignPhase, uint32_t phaseOffsetUS, bool autoPwm) { - return startWaveformClockCycles_bound(pin, timeHighCycles, timeLowCycles, runTimeCycles, alignPhase, phaseOffsetUS, autoPwm); -} - - -// This version falls-thru to the proper startWaveformClockCycles call and is invariant across waveform generators -int startWaveform(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS, uint32_t runTimeUS, - int8_t alignPhase, uint32_t phaseOffsetUS, bool autoPwm) { - return startWaveformClockCycles_bound(pin, - microsecondsToClockCycles(timeHighUS), microsecondsToClockCycles(timeLowUS), - microsecondsToClockCycles(runTimeUS), alignPhase, microsecondsToClockCycles(phaseOffsetUS), autoPwm); -} -*/ - -// Set a callback. Pass in NULL to stop it -//extern void setTimer1Callback_weak(uint32_t (*fn)()) __attribute__((weak)); -void setTimer1Callback_weak(uint32_t (*fn)()) { - wvfState.timer1CB = fn; - if (fn) { - initTimer(); - forceTimerInterrupt(); - } - disableIdleTimer(); -} -/* -static void setTimer1Callback_bound(uint32_t (*fn)()) __attribute__((weakref("setTimer1Callback_weak"))); -void setTimer1Callback(uint32_t (*fn)()) { - setTimer1Callback_bound(fn); -} -*/ - -// Stops a waveform on a pin -//extern int stopWaveform_weak(uint8_t pin) __attribute__((weak)); -IRAM_ATTR int stopWaveform_weak(uint8_t pin) { - // Can't possibly need to stop anything if there is no timer active - if (!timerRunning) { - return false; - } - // If user sends in a pin >16 but <32, this will always point to a 0 bit - // If they send >=32, then the shift will result in 0 and it will also return false - uint32_t mask = 1<= (uintptr_t) &_UserExceptionVector_1)) { - // Address is good; save backup - epc3_backup = epc3; - eps3_backup = eps3; - } else { - // Address is inside the NMI handler -- restore from backup - __asm__ __volatile__("wsr %0,epc3; wsr %1,eps3"::"a"(epc3_backup),"a"(eps3_backup)); - } -} -// ----- @willmmiles end patch ----- - - -// The SDK and hardware take some time to actually get to our NMI code, so -// decrement the next IRQ's timer value by a bit so we can actually catch the -// real CPU cycle counter we want for the waveforms. - -// The SDK also sometimes is running at a different speed the the Arduino core -// so the ESP cycle counter is actually running at a variable speed. -// adjust(x) takes care of adjusting a delta clock cycle amount accordingly. -#if F_CPU == 80000000 - #define DELTAIRQ (microsecondsToClockCycles(9)/4) - #define adjust(x) ((x) << (turbo ? 1 : 0)) -#else - #define DELTAIRQ (microsecondsToClockCycles(9)/8) - #define adjust(x) ((x) >> 0) -#endif - -// When the time to the next edge is greater than this, RTI and set another IRQ to minimize CPU usage -#define MINIRQTIME microsecondsToClockCycles(6) - -static IRAM_ATTR void timer1Interrupt() { - // ----- @willmmiles begin patch ----- - nmiCrashWorkaround(); - // ----- @willmmiles end patch ----- - - // Flag if the core is at 160 MHz, for use by adjust() - bool turbo = (*(uint32_t*)0x3FF00014) & 1 ? true : false; - - uint32_t nextEventCycle = GetCycleCountIRQ() + microsecondsToClockCycles(MAXIRQUS); - uint32_t timeoutCycle = GetCycleCountIRQ() + microsecondsToClockCycles(14); - - if (wvfState.waveformToEnable || wvfState.waveformToDisable) { - // Handle enable/disable requests from main app - wvfState.waveformEnabled = (wvfState.waveformEnabled & ~wvfState.waveformToDisable) | wvfState.waveformToEnable; // Set the requested waveforms on/off - wvfState.waveformState &= ~wvfState.waveformToEnable; // And clear the state of any just started - wvfState.waveformToEnable = 0; - wvfState.waveformToDisable = 0; - // No mem barrier. Globals must be written to RAM on ISR exit. - // Find the first GPIO being generated by checking GCC's find-first-set (returns 1 + the bit of the first 1 in an int32_t) - wvfState.startPin = __builtin_ffs(wvfState.waveformEnabled) - 1; - // Find the last bit by subtracting off GCC's count-leading-zeros (no offset in this one) - wvfState.endPin = 32 - __builtin_clz(wvfState.waveformEnabled); - } else if (!pwmState.cnt && pwmState.pwmUpdate) { - // Start up the PWM generator by copying from the mailbox - pwmState.cnt = 1; - pwmState.idx = 1; // Ensure copy this cycle, cause it to start at t=0 - pwmState.nextServiceCycle = GetCycleCountIRQ(); // Do it this loop! - // No need for mem barrier here. Global must be written by IRQ exit - } - - bool done = false; - if (wvfState.waveformEnabled || pwmState.cnt) { - do { - nextEventCycle = GetCycleCountIRQ() + microsecondsToClockCycles(MAXIRQUS); - - // PWM state machine implementation - if (pwmState.cnt) { - int32_t cyclesToGo; - do { - cyclesToGo = pwmState.nextServiceCycle - GetCycleCountIRQ(); - if (cyclesToGo < 0) { - if (pwmState.idx == pwmState.cnt) { // Start of pulses, possibly copy new - if (pwmState.pwmUpdate) { - // Do the memory copy from temp to global and clear mailbox - pwmState = *(PWMState*)pwmState.pwmUpdate; - } - GPOS = pwmState.mask; // Set all active pins high - if (pwmState.mask & (1<<16)) { - GP16O = 1; - } - pwmState.idx = 0; - } else { - do { - // Drop the pin at this edge - if (pwmState.mask & (1<expiryCycle) { - int32_t expiryToGo = wave->expiryCycle - now; - if (expiryToGo < 0) { - // Done, remove! - if (i == 16) { - GP16O = 0; - } - GPOC = mask; - wvfState.waveformEnabled &= ~mask; - continue; - } - } - - // Check for toggles - int32_t cyclesToGo = wave->nextServiceCycle - now; - if (cyclesToGo < 0) { - uint32_t nextEdgeCycles; - uint32_t desired = 0; - uint32_t *timeToUpdate; - wvfState.waveformState ^= mask; - if (wvfState.waveformState & mask) { - if (i == 16) { - GP16O = 1; - } - GPOS = mask; - - if (wvfState.waveformToChange & mask) { - // Copy over next full-cycle timings - wave->timeHighCycles = wvfState.waveformNewHigh; - wave->desiredHighCycles = wvfState.waveformNewHigh; - wave->timeLowCycles = wvfState.waveformNewLow; - wave->desiredLowCycles = wvfState.waveformNewLow; - wave->lastEdge = 0; - wvfState.waveformToChange = 0; - } - if (wave->lastEdge) { - desired = wave->desiredLowCycles; - timeToUpdate = &wave->timeLowCycles; - } - nextEdgeCycles = wave->timeHighCycles; - } else { - if (i == 16) { - GP16O = 0; - } - GPOC = mask; - desired = wave->desiredHighCycles; - timeToUpdate = &wave->timeHighCycles; - nextEdgeCycles = wave->timeLowCycles; - } - if (desired) { - desired = adjust(desired); - int32_t err = desired - (now - wave->lastEdge); - if (abs(err) < desired) { // If we've lost > the entire phase, ignore this error signal - err /= 2; - *timeToUpdate += err; - } - } - nextEdgeCycles = adjust(nextEdgeCycles); - wave->nextServiceCycle = now + nextEdgeCycles; - wave->lastEdge = now; - } - nextEventCycle = earliest(nextEventCycle, wave->nextServiceCycle); - } - - // Exit the loop if we've hit the fixed runtime limit or the next event is known to be after that timeout would occur - uint32_t now = GetCycleCountIRQ(); - int32_t cycleDeltaNextEvent = nextEventCycle - now; - int32_t cyclesLeftTimeout = timeoutCycle - now; - done = (cycleDeltaNextEvent > MINIRQTIME) || (cyclesLeftTimeout < 0); - } while (!done); - } // if (wvfState.waveformEnabled) - - if (wvfState.timer1CB) { - nextEventCycle = earliest(nextEventCycle, GetCycleCountIRQ() + wvfState.timer1CB()); - } - - int32_t nextEventCycles = nextEventCycle - GetCycleCountIRQ(); - - if (nextEventCycles < MINIRQTIME) { - nextEventCycles = MINIRQTIME; - } - nextEventCycles -= DELTAIRQ; - - // Do it here instead of global function to save time and because we know it's edge-IRQ - T1L = nextEventCycles >> (turbo ? 1 : 0); -} - -}; diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index 5b948b9c4..5dcb7f948 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -16,6 +16,9 @@ #define LEDC_MUTEX_UNLOCK() #endif #endif +#ifdef ESP8266 +#include "core_esp8266_waveform.h" +#endif #include "const.h" #include "pin_manager.h" #include "bus_wrapper.h" @@ -466,10 +469,7 @@ BusPwm::BusPwm(BusConfig &bc) for (unsigned i = 0; i < numPins; i++) pins[i] = {(int8_t)bc.pins[i], true}; if (!PinManager::allocateMultiplePins(pins, numPins, PinOwner::BusPwm)) return; -#ifdef ESP8266 - analogWriteRange((1<<_depth)-1); - analogWriteFreq(_frequency); -#else +#ifdef ARDUINO_ARCH_ESP32 // for 2 pin PWM CCT strip pinManager will make sure both LEDC channels are in the same speed group and sharing the same timer _ledcStart = PinManager::allocateLedc(numPins); if (_ledcStart == 255) { //no more free LEDC channels @@ -560,12 +560,23 @@ uint32_t BusPwm::getPixelColor(uint16_t pix) const { void BusPwm::show() { if (!_valid) return; + const unsigned numPins = getPins(); +#ifdef ESP8266 + const unsigned analogPeriod = F_CPU / _frequency; + const unsigned maxBri = analogPeriod; // compute to clock cycle accuracy + constexpr bool dithering = false; + constexpr unsigned bitShift = 7; // 2^7 clocks for dead time +#else // if _needsRefresh is true (UI hack) we are using dithering (credit @dedehai & @zalatnaicsongor) // https://github.com/Aircoookie/WLED/pull/4115 and https://github.com/zalatnaicsongor/WLED/pull/1) const bool dithering = _needsRefresh; // avoid working with bitfield - const unsigned numPins = getPins(); const unsigned maxBri = (1<<_depth); // possible values: 16384 (14), 8192 (13), 4096 (12), 2048 (11), 1024 (10), 512 (9) and 256 (8) - [[maybe_unused]] const unsigned bitShift = dithering * 4; // if dithering, _depth is 12 bit but LEDC channel is set to 8 bit (using 4 fractional bits) + const unsigned bitShift = dithering * 4; // if dithering, _depth is 12 bit but LEDC channel is set to 8 bit (using 4 fractional bits) +#endif + // add dead time between signals (when using dithering, two full 8bit pulses are required) + // this is needed for 2CH, but also adds some slack for ESP8266 which has less precise + // PWM timing. + const int deadTime = (1+dithering) << bitShift; // use CIE brightness formula (cubic) to fit (or approximate linearity of) human eye perceived brightness // the formula is based on 12 bit resolution as there is no need for greater precision @@ -591,19 +602,19 @@ void BusPwm::show() { // also mandatory that both channels use the same timer (pinManager takes care of that). for (unsigned i = 0; i < numPins; i++) { unsigned duty = (_data[i] * pwmBri) / 255; - #ifdef ESP8266 - if (_reversed) duty = maxBri - duty; - analogWrite(_pins[i], duty); - #else - int deadTime = 0; + if (_type == TYPE_ANALOG_2CH && Bus::getCCTBlend() == 0) { - // add dead time between signals (when using dithering, two full 8bit pulses are required) - deadTime = (1+dithering) << bitShift; // we only need to take care of shortening the signal at (almost) full brightness otherwise pulses may overlap - if (_bri >= 254 && duty >= maxBri / 2 && duty < maxBri) duty -= deadTime << 1; // shorten duty of larger signal except if full on - if (_reversed) deadTime = -deadTime; // need to invert dead time to make phaseshift go the opposite way so low signals dont overlap + if (_bri >= 254 && duty >= maxBri / 2 && duty < maxBri) { + duty -= deadTime << 1; // shorten duty of larger signal except if full on + } } if (_reversed) duty = maxBri - duty; + + #ifdef ESP8266 + stopWaveform(_pins[i]); + startWaveformClockCycles(_pins[i], duty, analogPeriod - duty, 0, i ? _pins[0] : -1, hPoint, false); + #else unsigned channel = _ledcStart + i; unsigned gr = channel/8; // high/low speed group unsigned ch = channel%8; // group channel @@ -612,9 +623,10 @@ void BusPwm::show() { LEDC.channel_group[gr].channel[ch].duty.duty = duty << ((!dithering)*4); // lowest 4 bits are used for dithering, shift by 4 bits if not using dithering LEDC.channel_group[gr].channel[ch].hpoint.hpoint = hPoint >> bitShift; // hPoint is at _depth resolution (needs shifting if dithering) ledc_update_duty((ledc_mode_t)gr, (ledc_channel_t)ch); - hPoint += duty + deadTime; // offset to cascade the signals - if (hPoint >= maxBri) hPoint = 0; // offset it out of bounds, reset #endif + + hPoint += duty + (_reversed ? -1 : 1) * deadTime; // offset to cascade the signals + if (hPoint >= maxBri) hPoint -= maxBri; // offset is out of bounds, reset } } From fe4b668107fc5a91a1d66743cefbce185c10b5d2 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Sat, 28 Sep 2024 23:12:03 -0400 Subject: [PATCH 048/287] Slightly reduce PWM jankiness --- .../src/core_esp8266_waveform_phase.cpp | 33 +++++++++++++++---- wled00/bus_manager.cpp | 2 +- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/lib/ESP8266PWM/src/core_esp8266_waveform_phase.cpp b/lib/ESP8266PWM/src/core_esp8266_waveform_phase.cpp index b846091bf..12a12066c 100644 --- a/lib/ESP8266PWM/src/core_esp8266_waveform_phase.cpp +++ b/lib/ESP8266PWM/src/core_esp8266_waveform_phase.cpp @@ -104,18 +104,20 @@ constexpr int32_t DELTAIRQCCYS = ISCPUFREQ160MHZ ? // for INFINITE, the NMI proceeds on the waveform without expiry deadline. // for EXPIRES, the NMI expires the waveform automatically on the expiry ccy. // for UPDATEEXPIRY, the NMI recomputes the exact expiry ccy and transitions to EXPIRES. +// for UPDATEPHASE, the NMI recomputes the target timings // for INIT, the NMI initializes nextPeriodCcy, and if expiryCcy != 0 includes UPDATEEXPIRY. -enum class WaveformMode : uint8_t {INFINITE = 0, EXPIRES = 1, UPDATEEXPIRY = 2, INIT = 3}; +enum class WaveformMode : uint8_t {INFINITE = 0, EXPIRES = 1, UPDATEEXPIRY = 2, UPDATEPHASE = 3, INIT = 4}; // Waveform generator can create tones, PWM, and servos typedef struct { - uint32_t nextPeriodCcy; // ESP clock cycle when a period begins. If WaveformMode::INIT, temporarily holds positive phase offset ccy count + uint32_t nextPeriodCcy; // ESP clock cycle when a period begins. uint32_t endDutyCcy; // ESP clock cycle when going from duty to off int32_t dutyCcys; // Set next off cycle at low->high to maintain phase int32_t adjDutyCcys; // Temporary correction for next period int32_t periodCcys; // Set next phase cycle at low->high to maintain phase uint32_t expiryCcy; // For time-limited waveform, the CPU clock cycle when this waveform must stop. If WaveformMode::UPDATE, temporarily holds relative ccy count WaveformMode mode; + uint32_t phaseCcy; // positive phase offset ccy count int8_t alignPhase; // < 0 no phase alignment, otherwise starts waveform in relative phase offset to given pin bool autoPwm; // perform PWM duty to idle cycle ratio correction under high load at the expense of precise timings } Waveform; @@ -200,15 +202,15 @@ int startWaveformClockCycles_weak(uint8_t pin, uint32_t highCcys, uint32_t lowCc wave.adjDutyCcys = 0; wave.periodCcys = periodCcys; wave.autoPwm = autoPwm; + wave.alignPhase = (alignPhase < 0) ? -1 : alignPhase; + wave.phaseCcy = phaseOffsetCcys; std::atomic_thread_fence(std::memory_order_acquire); const uint32_t pinBit = 1UL << pin; if (!(waveform.enabled & pinBit)) { // wave.nextPeriodCcy and wave.endDutyCcy are initialized by the ISR - wave.nextPeriodCcy = phaseOffsetCcys; wave.expiryCcy = runTimeCcys; // in WaveformMode::INIT, temporarily hold relative cycle count wave.mode = WaveformMode::INIT; - wave.alignPhase = (alignPhase < 0) ? -1 : alignPhase; if (!wave.dutyCcys) { // If initially at zero duty cycle, force GPIO off if (pin == 16) { @@ -232,11 +234,16 @@ int startWaveformClockCycles_weak(uint8_t pin, uint32_t highCcys, uint32_t lowCc else { wave.mode = WaveformMode::INFINITE; // turn off possible expiry to make update atomic from NMI std::atomic_thread_fence(std::memory_order_release); - wave.expiryCcy = runTimeCcys; // in WaveformMode::UPDATEEXPIRY, temporarily hold relative cycle count if (runTimeCcys) { + wave.expiryCcy = runTimeCcys; // in WaveformMode::UPDATEEXPIRY, temporarily hold relative cycle count wave.mode = WaveformMode::UPDATEEXPIRY; std::atomic_thread_fence(std::memory_order_release); waveform.toSetBits = 1UL << pin; + } else if (alignPhase) { + // @willmmiles new feature + wave.mode = WaveformMode::UPDATEPHASE; // recalculate start + std::atomic_thread_fence(std::memory_order_release); + waveform.toSetBits = 1UL << pin; } } std::atomic_thread_fence(std::memory_order_acq_rel); @@ -292,12 +299,13 @@ static inline IRAM_ATTR int32_t scaleCcys(const int32_t ccys, const bool isCPU2X } static IRAM_ATTR void timer1Interrupt() { + const uint32_t isrStartCcy = ESP.getCycleCount(); + int32_t clockDrift = isrStartCcy - waveform.nextEventCcy; + // ----- @willmmiles begin patch ----- nmiCrashWorkaround(); // ----- @willmmiles end patch ----- - const uint32_t isrStartCcy = ESP.getCycleCount(); - int32_t clockDrift = isrStartCcy - waveform.nextEventCcy; const bool isCPU2X = CPU2X & 1; if ((waveform.toSetBits && !(waveform.enabled & waveform.toSetBits)) || waveform.toDisableBits) { // Handle enable/disable requests from main app. @@ -328,6 +336,15 @@ static IRAM_ATTR void timer1Interrupt() { wave.expiryCcy = wave.nextPeriodCcy + scaleCcys(wave.expiryCcy, isCPU2X); wave.mode = WaveformMode::EXPIRES; break; + // @willmmiles new feature + case WaveformMode::UPDATEPHASE: + // in WaveformMode::UPDATEPHASE, we recalculate the targets without adjusting the state + if (wave.alignPhase >= 0 && waveform.enabled & (1UL << wave.alignPhase)) { + auto& align_wave = waveform.pins[wave.alignPhase]; + // Go back one cycle + wave.nextPeriodCcy = align_wave.nextPeriodCcy - scaleCcys(align_wave.periodCcys, isCPU2X) + scaleCcys(wave.phaseCcy, isCPU2X); + wave.endDutyCcy = wave.nextPeriodCcy + scaleCcys(wave.dutyCcys, isCPU2X); + } default: break; } @@ -355,11 +372,13 @@ static IRAM_ATTR void timer1Interrupt() { Waveform& wave = waveform.pins[pin]; +/* @willmmiles - wtf? We don't want to accumulate drift if (clockDrift) { wave.endDutyCcy += clockDrift; wave.nextPeriodCcy += clockDrift; wave.expiryCcy += clockDrift; } +*/ uint32_t waveNextEventCcy = (waveform.states & pinBit) ? wave.endDutyCcy : wave.nextPeriodCcy; if (WaveformMode::EXPIRES == wave.mode && diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index 5dcb7f948..e631190d4 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -612,7 +612,7 @@ void BusPwm::show() { if (_reversed) duty = maxBri - duty; #ifdef ESP8266 - stopWaveform(_pins[i]); + //stopWaveform(_pins[i]); // can cause the waveform to miss a cycle. instead we risk crossovers. startWaveformClockCycles(_pins[i], duty, analogPeriod - duty, 0, i ? _pins[0] : -1, hPoint, false); #else unsigned channel = _ledcStart + i; From 3c7f83407b64f21fa20262189c215fdd9852a972 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Sat, 28 Sep 2024 23:15:20 -0400 Subject: [PATCH 049/287] Save a little RAM --- .../src/core_esp8266_waveform_phase.cpp | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/lib/ESP8266PWM/src/core_esp8266_waveform_phase.cpp b/lib/ESP8266PWM/src/core_esp8266_waveform_phase.cpp index 12a12066c..60b9b0eb5 100644 --- a/lib/ESP8266PWM/src/core_esp8266_waveform_phase.cpp +++ b/lib/ESP8266PWM/src/core_esp8266_waveform_phase.cpp @@ -117,8 +117,6 @@ typedef struct { int32_t periodCcys; // Set next phase cycle at low->high to maintain phase uint32_t expiryCcy; // For time-limited waveform, the CPU clock cycle when this waveform must stop. If WaveformMode::UPDATE, temporarily holds relative ccy count WaveformMode mode; - uint32_t phaseCcy; // positive phase offset ccy count - int8_t alignPhase; // < 0 no phase alignment, otherwise starts waveform in relative phase offset to given pin bool autoPwm; // perform PWM duty to idle cycle ratio correction under high load at the expense of precise timings } Waveform; @@ -133,6 +131,11 @@ namespace { int32_t toSetBits = 0; // Message to the NMI handler to start/modify exactly one waveform int32_t toDisableBits = 0; // Message to the NMI handler to disable exactly one pin from waveform generation + // toSetBits temporaries + // cheaper than packing them in every Waveform, since we permit only one use at a time + uint32_t phaseCcy; // positive phase offset ccy count + int8_t alignPhase; // < 0 no phase alignment, otherwise starts waveform in relative phase offset to given pin + uint32_t(*timer1CB)() = nullptr; bool timer1Running = false; @@ -202,8 +205,8 @@ int startWaveformClockCycles_weak(uint8_t pin, uint32_t highCcys, uint32_t lowCc wave.adjDutyCcys = 0; wave.periodCcys = periodCcys; wave.autoPwm = autoPwm; - wave.alignPhase = (alignPhase < 0) ? -1 : alignPhase; - wave.phaseCcy = phaseOffsetCcys; + waveform.alignPhase = (alignPhase < 0) ? -1 : alignPhase; + waveform.phaseCcy = phaseOffsetCcys; std::atomic_thread_fence(std::memory_order_acquire); const uint32_t pinBit = 1UL << pin; @@ -320,8 +323,8 @@ static IRAM_ATTR void timer1Interrupt() { switch (wave.mode) { case WaveformMode::INIT: waveform.states &= ~waveform.toSetBits; // Clear the state of any just started - if (wave.alignPhase >= 0 && waveform.enabled & (1UL << wave.alignPhase)) { - wave.nextPeriodCcy = waveform.pins[wave.alignPhase].nextPeriodCcy + wave.nextPeriodCcy; + if (waveform.alignPhase >= 0 && waveform.enabled & (1UL << waveform.alignPhase)) { + wave.nextPeriodCcy = waveform.pins[waveform.alignPhase].nextPeriodCcy + wave.nextPeriodCcy; } else { wave.nextPeriodCcy = waveform.nextEventCcy; @@ -339,10 +342,10 @@ static IRAM_ATTR void timer1Interrupt() { // @willmmiles new feature case WaveformMode::UPDATEPHASE: // in WaveformMode::UPDATEPHASE, we recalculate the targets without adjusting the state - if (wave.alignPhase >= 0 && waveform.enabled & (1UL << wave.alignPhase)) { - auto& align_wave = waveform.pins[wave.alignPhase]; + if (waveform.alignPhase >= 0 && waveform.enabled & (1UL << waveform.alignPhase)) { + auto& align_wave = waveform.pins[waveform.alignPhase]; // Go back one cycle - wave.nextPeriodCcy = align_wave.nextPeriodCcy - scaleCcys(align_wave.periodCcys, isCPU2X) + scaleCcys(wave.phaseCcy, isCPU2X); + wave.nextPeriodCcy = align_wave.nextPeriodCcy - scaleCcys(align_wave.periodCcys, isCPU2X) + scaleCcys(waveform.phaseCcy, isCPU2X); wave.endDutyCcy = wave.nextPeriodCcy + scaleCcys(wave.dutyCcys, isCPU2X); } default: From ffbc8c5f709461f96c557e5dc9162c8f3e5c6004 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Sun, 29 Sep 2024 13:55:00 +0200 Subject: [PATCH 050/287] Reverting addition of `bool unScale`, added new improvements and fixes - Added pre-calculation for segment brightness: stored in _segBri. The impact on FPS is not huge but measurable (~1-2FPS in my test conditions) - Removed `bool unScaled` from `setPixelColor()` function again (it has no/minimal impact on speed but huge impact on flash usage: +850 bytes) - Removed negative checking in `setPixelColorXY()` and replaced it with a local typecast to unsigned, saves a few instructions (tested and working) - Changed int8_t to int in `moveX()` and `moveY()` - Removed a few functions from IRAM as they are now not called for every pixel but only once per segment update - Removed a `virtualWidth()` call from `ripple_base()` - Bugfix in `mode_colortwinkle()` --- wled00/FX.cpp | 4 ++-- wled00/FX.h | 26 ++++++++++++------------- wled00/FX_2Dfcn.cpp | 40 ++++++++++++++++---------------------- wled00/FX_fcn.cpp | 47 ++++++++++++++++++++++----------------------- 4 files changed, 55 insertions(+), 62 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 2ec31014f..77167b02d 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -2294,7 +2294,7 @@ uint16_t mode_colortwinkle() { bool fadeUp = bitRead(SEGENV.data[index], bitNum); if (fadeUp) { - CRGBW incrementalColor = color_fade(col, fadeUpAmount, true); + CRGBW incrementalColor = color_fade(cur, fadeUpAmount, true); col = color_add(cur, incrementalColor); if (col.r == 255 || col.g == 255 || col.b == 255) { @@ -2528,7 +2528,7 @@ static uint16_t ripple_base() { } else {//randomly create new wave if (random16(IBN + 10000) <= (SEGMENT.intensity >> (SEGMENT.is2D()*3))) { ripples[i].state = 1; - ripples[i].pos = SEGMENT.is2D() ? ((random8(SEGENV.virtualWidth())<<8) | (random8(SEGENV.virtualHeight()))) : random16(SEGLEN); + ripples[i].pos = SEGMENT.is2D() ? ((random8(SEG_W)<<8) | (random8(SEG_H))) : random16(SEGLEN); ripples[i].color = random8(); //color } } diff --git a/wled00/FX.h b/wled00/FX.h index b4aaf3c48..8a452cfcb 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -420,8 +420,8 @@ typedef struct Segment { }; uint16_t _dataLen; static uint16_t _usedSegmentData; - - static unsigned _vLength; // 1D dimension used for current effect + static uint8_t _segBri; // Current brightness of segment + static unsigned _vLength; // 1D dimension used for current effect static unsigned _vWidth, _vHeight; // 2D dimensions used for current effect static uint32_t _currentColors[NUM_COLORS]; // colors used for current effect static CRGBPalette16 _currentPalette; // palette used for current effect (includes transition, used in color_from_palette()) @@ -546,7 +546,7 @@ typedef struct Segment { inline static unsigned vHeight() { return Segment::_vHeight; } inline static uint32_t getCurrentColor(unsigned i) { return Segment::_currentColors[i]; } // { return i < 3 ? Segment::_currentColors[i] : 0; } inline static const CRGBPalette16 &getCurrentPalette() { return Segment::_currentPalette; } - + inline static uint8_t getCurrentBrightness() { return Segment::_segBri; } static void handleRandomPalette(); void beginDraw(); // set up parameters for current effect @@ -589,8 +589,8 @@ typedef struct Segment { // 1D strip [[gnu::hot]] uint16_t virtualLength() const; - [[gnu::hot]] void setPixelColor(int n, uint32_t c, bool unScaled = true); // set relative pixel within segment with color - inline void setPixelColor(unsigned n, uint32_t c, bool unScaled = true) { setPixelColor(int(n), c, unScaled); } + [[gnu::hot]] void setPixelColor(int n, uint32_t c); // set relative pixel within segment with color + inline void setPixelColor(unsigned n, uint32_t c) { setPixelColor(int(n), c); } inline void setPixelColor(int n, byte r, byte g, byte b, byte w = 0) { setPixelColor(n, RGBW32(r,g,b,w)); } inline void setPixelColor(int n, CRGB c) { setPixelColor(n, RGBW32(c.r,c.g,c.b,0)); } #ifdef WLED_USE_AA_PIXELS @@ -629,8 +629,8 @@ typedef struct Segment { uint16_t nrOfVStrips() const; // returns number of virtual vertical strips in 2D matrix (used to expand 1D effects into 2D) #ifndef WLED_DISABLE_2D [[gnu::hot]] uint16_t XY(int x, int y); // support function to get relative index within segment - [[gnu::hot]] void setPixelColorXY(int x, int y, uint32_t c, bool unScaled = true); // set relative pixel within segment with color - inline void setPixelColorXY(unsigned x, unsigned y, uint32_t c, bool unScaled = true) { setPixelColorXY(int(x), int(y), c, unScaled); } + [[gnu::hot]] 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)); } 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)); } @@ -651,8 +651,8 @@ typedef struct Segment { void blur2D(uint8_t blur_amount, bool smear = false); void blurRow(int row, fract8 blur_amount, bool smear = false); void blurCol(int col, fract8 blur_amount, bool smear = false); - void moveX(int8_t delta, bool wrap = false); - void moveY(int8_t delta, bool wrap = false); + void moveX(int delta, bool wrap = false); + void moveY(int delta, bool wrap = false); void move(uint8_t dir, uint8_t delta, bool wrap = false); 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); } @@ -668,8 +668,8 @@ typedef struct Segment { inline void fill_solid(CRGB c) { fill(RGBW32(c.r,c.g,c.b,0)); } #else inline uint16_t XY(int x, int y) { return x; } - inline void setPixelColorXY(int x, int y, uint32_t c, bool unScaled = true) { setPixelColor(x, c, unScaled); } - inline void setPixelColorXY(unsigned x, unsigned y, uint32_t c, bool unScaled = true) { setPixelColor(int(x), c, unScaled); } + inline void setPixelColorXY(int x, int y, uint32_t c) { setPixelColor(x, c); } + 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)); } @@ -689,8 +689,8 @@ typedef struct Segment { inline void blur2D(uint8_t blur_amount, bool smear = false) {} inline void blurRow(int row, fract8 blur_amount, bool smear = false) {} inline void blurCol(int col, fract8 blur_amount, bool smear = false) {} - inline void moveX(int8_t delta, bool wrap = false) {} - inline void moveY(int8_t delta, bool wrap = false) {} + inline void moveX(int delta, bool wrap = false) {} + inline void moveY(int delta, bool wrap = false) {} inline void move(uint8_t dir, uint8_t delta, bool wrap = false) {} 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) {} diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index 5f61b0094..bee317597 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -168,16 +168,16 @@ uint16_t IRAM_ATTR_YN Segment::XY(int x, int y) return isActive() ? (x%vW) + (y%vH) * vW : 0; } -void IRAM_ATTR_YN Segment::setPixelColorXY(int x, int y, uint32_t col, bool unScaled) +void IRAM_ATTR_YN Segment::setPixelColorXY(int x, int y, uint32_t col) { if (!isActive()) return; // not active - + const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive) const int vH = vHeight(); // segment height in logical pixels (is always >= 1) - if (x >= vW|| y >= vH || x<0 || y<0) return; // if pixel would fall out of virtual segment just exit + if (unsigned(x) >= vW || unsigned(y) >= vH) return; // if pixel would fall out of virtual segment just exit - // if color is unscaled - if (unScaled) col = color_fade(col, currentBri()); + if(getCurrentBrightness() < 255) + col = color_fade(col, getCurrentBrightness()); // scale brightness if (reverse ) x = vW - x - 1; if (reverse_y) y = vH - y - 1; @@ -459,7 +459,7 @@ void Segment::box_blur(unsigned radius, bool smear) { delete[] tmpWSum; } -void Segment::moveX(int8_t delta, bool wrap) { +void Segment::moveX(int delta, bool wrap) { if (!isActive()) return; // not active const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive) const int vH = vHeight(); // segment height in logical pixels (is always >= 1) @@ -477,7 +477,7 @@ void Segment::moveX(int8_t delta, bool wrap) { } } -void Segment::moveY(int8_t delta, bool wrap) { +void Segment::moveY(int delta, bool wrap) { if (!isActive()) return; // not active const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive) const int vH = vHeight(); // segment height in logical pixels (is always >= 1) @@ -545,20 +545,18 @@ void Segment::drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t col, x++; } } else { - // pre-scale color for all pixels - col = color_fade(col, currentBri()); // Bresenham’s Algorithm int d = 3 - (2*radius); int y = radius, x = 0; while (y >= x) { - setPixelColorXY(cx+x, cy+y, col, false); - setPixelColorXY(cx-x, cy+y, col, false); - setPixelColorXY(cx+x, cy-y, col, false); - setPixelColorXY(cx-x, cy-y, col, false); - setPixelColorXY(cx+y, cy+x, col, false); - setPixelColorXY(cx-y, cy+x, col, false); - setPixelColorXY(cx+y, cy-x, col, false); - setPixelColorXY(cx-y, cy-x, col, false); + 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--; @@ -577,15 +575,13 @@ void Segment::fillCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t col, const int vH = vHeight(); // segment height in logical pixels (is always >= 1) // draw soft bounding circle if (soft) drawCircle(cx, cy, radius, col, soft); - // pre-scale color for all pixels - col = color_fade(col, currentBri()); // fill it for (int y = -radius; y <= radius; y++) { for (int x = -radius; x <= radius; x++) { if (x * x + y * y <= radius * radius && int(cx)+x >= 0 && int(cy)+y >= 0 && int(cx)+x < vW && int(cy)+y < vH) - setPixelColorXY(cx + x, cy + y, col, false); + setPixelColorXY(cx + x, cy + y, col); } } } @@ -633,12 +629,10 @@ void Segment::drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint3 if (steep) std::swap(x,y); // restore if steep } } else { - // pre-scale color for all pixels - c = color_fade(c, currentBri()); // Bresenham's algorithm int err = (dx>dy ? dx : -dy)/2; // error direction for (;;) { - setPixelColorXY(x0, y0, c, false); + setPixelColorXY(x0, y0, c); if (x0==x1 && y0==y1) break; int e2 = err; if (e2 >-dx) { err -= dy; x0 += sx; } diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index a47a7edc9..9edd7a282 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -86,6 +86,9 @@ uint16_t Segment::maxHeight = 1; unsigned Segment::_vLength = 0; unsigned Segment::_vWidth = 0; unsigned Segment::_vHeight = 0; +uint8_t Segment::_segBri = 0; +//uint8_t Segment::_currentBrightness2 = 0; +//uint8_t Segment::_currentBrightness3 = 0; uint32_t Segment::_currentColors[NUM_COLORS] = {0,0,0}; CRGBPalette16 Segment::_currentPalette = CRGBPalette16(CRGB::Black); CRGBPalette16 Segment::_randomPalette = generateRandomPalette(); // was CRGBPalette16(DEFAULT_COLOR); @@ -413,7 +416,7 @@ void Segment::restoreSegenv(tmpsegd_t &tmpSeg) { } #endif -uint8_t IRAM_ATTR Segment::currentBri(bool useCct) const { +uint8_t Segment::currentBri(bool useCct) const { unsigned prog = progress(); if (prog < 0xFFFFU) { unsigned curBri = (useCct ? cct : (on ? opacity : 0)) * prog; @@ -445,6 +448,7 @@ void Segment::beginDraw() { _vWidth = virtualWidth(); _vHeight = virtualHeight(); _vLength = virtualLength(); + _segBri = currentBri(); // adjust gamma for effects for (unsigned i = 0; i < NUM_COLORS; i++) { #ifndef WLED_DISABLE_MODE_BLEND @@ -624,21 +628,21 @@ void Segment::setPalette(uint8_t pal) { } // 2D matrix -uint16_t IRAM_ATTR Segment::virtualWidth() const { +uint16_t Segment::virtualWidth() const { unsigned groupLen = groupLength(); unsigned vWidth = ((transpose ? height() : width()) + groupLen - 1) / groupLen; if (mirror) vWidth = (vWidth + 1) /2; // divide by 2 if mirror, leave at least a single LED return vWidth; } -uint16_t IRAM_ATTR Segment::virtualHeight() const { +uint16_t Segment::virtualHeight() const { unsigned groupLen = groupLength(); unsigned vHeight = ((transpose ? width() : height()) + groupLen - 1) / groupLen; if (mirror_y) vHeight = (vHeight + 1) /2; // divide by 2 if mirror, leave at least a single LED return vHeight; } -uint16_t IRAM_ATTR_YN Segment::nrOfVStrips() const { +uint16_t Segment::nrOfVStrips() const { unsigned vLen = 1; #ifndef WLED_DISABLE_2D if (is2D() && map1D2D == M12_pBar) vLen = virtualWidth(); @@ -683,7 +687,7 @@ static int getPinwheelLength(int vW, int vH) { #endif // 1D strip -uint16_t IRAM_ATTR Segment::virtualLength() const { +uint16_t Segment::virtualLength() const { #ifndef WLED_DISABLE_2D if (is2D()) { unsigned vW = virtualWidth(); @@ -715,7 +719,7 @@ uint16_t IRAM_ATTR Segment::virtualLength() const { return vLength; } -void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col, bool unScaled) +void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col) { if (!isActive() || i < 0) return; // not active or invalid index #ifndef WLED_DISABLE_2D @@ -739,22 +743,20 @@ void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col, bool unScaled) if (is2D()) { const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive) const int vH = vHeight(); // segment height in logical pixels (is always >= 1) - // pre-scale color for all pixels - col = color_fade(col, currentBri()); switch (map1D2D) { case M12_Pixels: // use all available pixels as a long strip - setPixelColorXY(i % vW, i / vW, col, false); + setPixelColorXY(i % vW, i / vW, col); break; case M12_pBar: // expand 1D effect vertically or have it play on virtual strips - if (vStrip > 0) setPixelColorXY(vStrip - 1, vH - i - 1, col, false); - else for (int x = 0; x < vW; x++) setPixelColorXY(x, vH - i - 1, col, false); + if (vStrip > 0) setPixelColorXY(vStrip - 1, vH - i - 1, col); + else for (int x = 0; x < vW; x++) setPixelColorXY(x, vH - i - 1, col); break; case M12_pArc: // expand in circular fashion from center if (i == 0) - setPixelColorXY(0, 0, col, false); + setPixelColorXY(0, 0, col); else { float r = i; float step = HALF_PI / (2.8284f * r + 4); // we only need (PI/4)/(r/sqrt(2)+1) steps @@ -762,8 +764,8 @@ void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col, bool unScaled) int x = roundf(sin_t(rad) * r); int y = roundf(cos_t(rad) * r); // exploit symmetry - setPixelColorXY(x, y, col, false); - setPixelColorXY(y, x, col, false); + setPixelColorXY(x, y, col); + setPixelColorXY(y, x, col); } // Bresenham’s Algorithm (may not fill every pixel) //int d = 3 - (2*i); @@ -782,8 +784,8 @@ void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col, bool unScaled) } break; case M12_pCorner: - for (int x = 0; x <= i; x++) setPixelColorXY(x, i, col, false); - for (int y = 0; y < i; y++) setPixelColorXY(i, y, col, false); + for (int x = 0; x <= i; x++) setPixelColorXY(x, i, col); + for (int y = 0; y < i; y++) setPixelColorXY(i, y, col); break; case M12_sPinwheel: { // i = angle --> 0 - 296 (Big), 0 - 192 (Medium), 0 - 72 (Small) @@ -822,7 +824,7 @@ void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col, bool unScaled) int x = posx / Fixed_Scale; int y = posy / Fixed_Scale; // set pixel - if (x != lastX || y != lastY) setPixelColorXY(x, y, col, false); // only paint if pixel position is different + if (x != lastX || y != lastY) setPixelColorXY(x, y, col); // only paint if pixel position is different lastX = x; lastY = y; // advance to next position @@ -846,9 +848,8 @@ void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col, bool unScaled) #endif unsigned len = length(); - // if color is unscaled - if (unScaled) col = color_fade(col, currentBri()); - + if(getCurrentBrightness() < 255) //!!! test without this if + col = color_fade(col, getCurrentBrightness()); // scale brightness // expand pixel (taking into account start, grouping, spacing [and offset]) i = i * groupLength(); if (reverse) { // is segment reversed? @@ -1070,11 +1071,9 @@ void Segment::fill(uint32_t c) { if (!isActive()) return; // not active const int cols = is2D() ? vWidth() : vLength(); const int rows = vHeight(); // will be 1 for 1D - // pre-scale color for all pixels - c = color_fade(c, currentBri()); for (int y = 0; y < rows; y++) for (int x = 0; x < cols; x++) { - if (is2D()) setPixelColorXY(x, y, c, false); - else setPixelColor(x, c, false); + if (is2D()) setPixelColorXY(x, y, c); + else setPixelColor(x, c); } } From 336da25463ff13485da9bd2b1d825f1647368221 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Sun, 29 Sep 2024 14:14:07 +0200 Subject: [PATCH 051/287] Private global _colorScaled --- wled00/FX.h | 15 ++++++++------- wled00/FX_2Dfcn.cpp | 20 ++++++++++++++++++-- wled00/FX_fcn.cpp | 18 +++++++++++++----- 3 files changed, 39 insertions(+), 14 deletions(-) diff --git a/wled00/FX.h b/wled00/FX.h index 8a452cfcb..763dc2639 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -420,10 +420,11 @@ typedef struct Segment { }; uint16_t _dataLen; static uint16_t _usedSegmentData; - static uint8_t _segBri; // Current brightness of segment + static uint8_t _segBri; // brightness of segment for current effect static unsigned _vLength; // 1D dimension used for current effect static unsigned _vWidth, _vHeight; // 2D dimensions used for current effect static uint32_t _currentColors[NUM_COLORS]; // colors used for current effect + static bool _colorScaled; // color has been scaled prior to setPixelColor() call static CRGBPalette16 _currentPalette; // palette used for current effect (includes transition, used in color_from_palette()) static CRGBPalette16 _randomPalette; // actual random palette static CRGBPalette16 _newRandomPalette; // target random palette @@ -590,9 +591,9 @@ typedef struct Segment { // 1D strip [[gnu::hot]] uint16_t virtualLength() const; [[gnu::hot]] void setPixelColor(int n, uint32_t c); // set relative pixel within segment with color - inline void setPixelColor(unsigned n, uint32_t c) { setPixelColor(int(n), c); } - inline void setPixelColor(int n, byte r, byte g, byte b, byte w = 0) { setPixelColor(n, RGBW32(r,g,b,w)); } - inline void setPixelColor(int n, CRGB c) { setPixelColor(n, RGBW32(c.r,c.g,c.b,0)); } + inline void setPixelColor(unsigned n, uint32_t c) { setPixelColor(int(n), c); } + inline void setPixelColor(int n, byte r, byte g, byte b, byte w = 0) { setPixelColor(n, RGBW32(r,g,b,w)); } + inline void setPixelColor(int n, CRGB c) { setPixelColor(n, RGBW32(c.r,c.g,c.b,0)); } #ifdef WLED_USE_AA_PIXELS void setPixelColor(float i, uint32_t c, bool aa = true); 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); } @@ -630,7 +631,7 @@ typedef struct Segment { #ifndef WLED_DISABLE_2D [[gnu::hot]] uint16_t XY(int x, int y); // support function to get relative index within segment [[gnu::hot]] 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(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)); } @@ -668,8 +669,8 @@ typedef struct Segment { inline void fill_solid(CRGB c) { fill(RGBW32(c.r,c.g,c.b,0)); } #else inline uint16_t XY(int x, int y) { return x; } - inline void setPixelColorXY(int x, int y, uint32_t c) { setPixelColor(x, c); } - inline void setPixelColorXY(unsigned x, unsigned y, uint32_t c) { setPixelColor(int(x), c); } + inline void setPixelColorXY(int x, int y, uint32_t c) { setPixelColor(x, c); } + 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)); } diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index bee317597..8804cdbb6 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -176,8 +176,8 @@ void IRAM_ATTR_YN Segment::setPixelColorXY(int x, int y, uint32_t col) const int vH = vHeight(); // segment height in logical pixels (is always >= 1) if (unsigned(x) >= vW || unsigned(y) >= vH) return; // if pixel would fall out of virtual segment just exit - if(getCurrentBrightness() < 255) - col = color_fade(col, getCurrentBrightness()); // scale brightness + // if color is unscaled + if (!_colorScaled) col = color_fade(col, _segBri); if (reverse ) x = vW - x - 1; if (reverse_y) y = vH - y - 1; @@ -545,6 +545,9 @@ void Segment::drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t col, x++; } } else { + // pre-scale color for all pixels + col = color_fade(col, _segBri); + _colorScaled = true; // Bresenham’s Algorithm int d = 3 - (2*radius); int y = radius, x = 0; @@ -565,6 +568,7 @@ void Segment::drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t col, d += 4 * x + 6; } } + _colorScaled = false; } } @@ -575,6 +579,9 @@ void Segment::fillCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t col, const int vH = vHeight(); // segment height in logical pixels (is always >= 1) // draw soft bounding circle if (soft) drawCircle(cx, cy, radius, col, soft); + // pre-scale color for all pixels + col = color_fade(col, _segBri); + _colorScaled = true; // fill it for (int y = -radius; y <= radius; y++) { for (int x = -radius; x <= radius; x++) { @@ -584,6 +591,7 @@ void Segment::fillCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t col, setPixelColorXY(cx + x, cy + y, col); } } + _colorScaled = false; } //line function @@ -629,6 +637,9 @@ void Segment::drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint3 if (steep) std::swap(x,y); // restore if steep } } else { + // pre-scale color for all pixels + c = color_fade(c, _segBri); + _colorScaled = true; // Bresenham's algorithm int err = (dx>dy ? dx : -dy)/2; // error direction for (;;) { @@ -638,6 +649,7 @@ void Segment::drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint3 if (e2 >-dx) { err -= dy; x0 += sx; } if (e2 < dy) { err += dx; y0 += sy; } } + _colorScaled = false; } } @@ -670,6 +682,9 @@ void Segment::drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, default: return; } uint32_t col = ColorFromPaletteWLED(grad, (i+1)*255/h, 255, NOBLEND); + // pre-scale color for all pixels + col = color_fade(col, _segBri); + _colorScaled = true; for (int j = 0; j= 1) + // pre-scale color for all pixels + col = color_fade(col, _segBri); + _colorScaled = true; switch (map1D2D) { case M12_Pixels: // use all available pixels as a long strip @@ -834,6 +836,7 @@ void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col) break; } } + _colorScaled = false; return; } else if (Segment::maxHeight != 1 && (width() == 1 || height() == 1)) { if (start < Segment::maxWidth*Segment::maxHeight) { @@ -848,8 +851,9 @@ void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col) #endif unsigned len = length(); - if(getCurrentBrightness() < 255) //!!! test without this if - col = color_fade(col, getCurrentBrightness()); // scale brightness + // if color is unscaled + if (!_colorScaled) col = color_fade(col, _segBri); + // expand pixel (taking into account start, grouping, spacing [and offset]) i = i * groupLength(); if (reverse) { // is segment reversed? @@ -1071,10 +1075,14 @@ void Segment::fill(uint32_t c) { if (!isActive()) return; // not active const int cols = is2D() ? vWidth() : vLength(); const int rows = vHeight(); // will be 1 for 1D + // pre-scale color for all pixels + c = color_fade(c, _segBri); + _colorScaled = true; for (int y = 0; y < rows; y++) for (int x = 0; x < cols; x++) { if (is2D()) setPixelColorXY(x, y, c); else setPixelColor(x, c); } + _colorScaled = false; } /* From 0ae73296cffaff3089893734c2621174347de7ad Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Sun, 29 Sep 2024 15:19:37 +0200 Subject: [PATCH 052/287] Update comment --- wled00/FX_fcn.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 86f190ad5..232994e73 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -442,7 +442,7 @@ uint32_t IRAM_ATTR_YN Segment::currentColor(uint8_t slot) const { #endif } -// pre-calculate drawing parameters for faster access +// pre-calculate drawing parameters for faster access (based on the idea from @softhack007 from MM fork) void Segment::beginDraw() { _vWidth = virtualWidth(); _vHeight = virtualHeight(); From 59deebc961ec86f602504499ca45fda239a04bd6 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Sun, 29 Sep 2024 10:00:27 -0400 Subject: [PATCH 053/287] Improve PWM on ESP8266 - Better phase updates without dropping samples - Make second pin duty cycle always after first, even inverted --- .../src/core_esp8266_waveform_phase.cpp | 20 +++++++++++-------- wled00/bus_manager.cpp | 11 ++++++---- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/lib/ESP8266PWM/src/core_esp8266_waveform_phase.cpp b/lib/ESP8266PWM/src/core_esp8266_waveform_phase.cpp index 60b9b0eb5..b89ec8bc1 100644 --- a/lib/ESP8266PWM/src/core_esp8266_waveform_phase.cpp +++ b/lib/ESP8266PWM/src/core_esp8266_waveform_phase.cpp @@ -242,7 +242,7 @@ int startWaveformClockCycles_weak(uint8_t pin, uint32_t highCcys, uint32_t lowCc wave.mode = WaveformMode::UPDATEEXPIRY; std::atomic_thread_fence(std::memory_order_release); waveform.toSetBits = 1UL << pin; - } else if (alignPhase) { + } else if (alignPhase >= 0) { // @willmmiles new feature wave.mode = WaveformMode::UPDATEPHASE; // recalculate start std::atomic_thread_fence(std::memory_order_release); @@ -303,7 +303,7 @@ static inline IRAM_ATTR int32_t scaleCcys(const int32_t ccys, const bool isCPU2X static IRAM_ATTR void timer1Interrupt() { const uint32_t isrStartCcy = ESP.getCycleCount(); - int32_t clockDrift = isrStartCcy - waveform.nextEventCcy; + //int32_t clockDrift = isrStartCcy - waveform.nextEventCcy; // ----- @willmmiles begin patch ----- nmiCrashWorkaround(); @@ -341,12 +341,16 @@ static IRAM_ATTR void timer1Interrupt() { break; // @willmmiles new feature case WaveformMode::UPDATEPHASE: - // in WaveformMode::UPDATEPHASE, we recalculate the targets without adjusting the state + // in WaveformMode::UPDATEPHASE, we recalculate the targets if (waveform.alignPhase >= 0 && waveform.enabled & (1UL << waveform.alignPhase)) { - auto& align_wave = waveform.pins[waveform.alignPhase]; - // Go back one cycle - wave.nextPeriodCcy = align_wave.nextPeriodCcy - scaleCcys(align_wave.periodCcys, isCPU2X) + scaleCcys(waveform.phaseCcy, isCPU2X); - wave.endDutyCcy = wave.nextPeriodCcy + scaleCcys(wave.dutyCcys, isCPU2X); + // Compute phase shift to realign with target + auto& align_wave = waveform.pins[waveform.alignPhase]; + int32_t shift = static_cast(align_wave.nextPeriodCcy + scaleCcys(waveform.phaseCcy, isCPU2X) - wave.nextPeriodCcy); + const int32_t periodCcys = scaleCcys(wave.periodCcys, isCPU2X); + if (shift > periodCcys/2) shift -= periodCcys; + else if (shift <= -periodCcys/2) shift += periodCcys; + wave.nextPeriodCcy += shift; + wave.endDutyCcy += shift; } default: break; @@ -462,7 +466,7 @@ static IRAM_ATTR void timer1Interrupt() { } now = ESP.getCycleCount(); } - clockDrift = 0; + //clockDrift = 0; } int32_t callbackCcys = 0; diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index e631190d4..c3c8a2121 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -565,7 +565,7 @@ void BusPwm::show() { const unsigned analogPeriod = F_CPU / _frequency; const unsigned maxBri = analogPeriod; // compute to clock cycle accuracy constexpr bool dithering = false; - constexpr unsigned bitShift = 7; // 2^7 clocks for dead time + constexpr unsigned bitShift = 8; // 256 clocks for dead time, ~3us at 80MHz #else // if _needsRefresh is true (UI hack) we are using dithering (credit @dedehai & @zalatnaicsongor) // https://github.com/Aircoookie/WLED/pull/4115 and https://github.com/zalatnaicsongor/WLED/pull/1) @@ -609,8 +609,10 @@ void BusPwm::show() { duty -= deadTime << 1; // shorten duty of larger signal except if full on } } - if (_reversed) duty = maxBri - duty; - + if (_reversed) { + if (i) hPoint += duty; // align start at time zero + duty = maxBri - duty; + } #ifdef ESP8266 //stopWaveform(_pins[i]); // can cause the waveform to miss a cycle. instead we risk crossovers. startWaveformClockCycles(_pins[i], duty, analogPeriod - duty, 0, i ? _pins[0] : -1, hPoint, false); @@ -625,7 +627,8 @@ void BusPwm::show() { ledc_update_duty((ledc_mode_t)gr, (ledc_channel_t)ch); #endif - hPoint += duty + (_reversed ? -1 : 1) * deadTime; // offset to cascade the signals + if (!_reversed) hPoint += duty; + hPoint += deadTime; // offset to cascade the signals if (hPoint >= maxBri) hPoint -= maxBri; // offset is out of bounds, reset } } From cb8dae1ddbe750527ad5d4f162a6aa1793748ce3 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Sun, 29 Sep 2024 10:13:19 -0400 Subject: [PATCH 054/287] PWM: Revert always apply dead time --- wled00/bus_manager.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index c3c8a2121..cbcfa4b2b 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -573,11 +573,6 @@ void BusPwm::show() { const unsigned maxBri = (1<<_depth); // possible values: 16384 (14), 8192 (13), 4096 (12), 2048 (11), 1024 (10), 512 (9) and 256 (8) const unsigned bitShift = dithering * 4; // if dithering, _depth is 12 bit but LEDC channel is set to 8 bit (using 4 fractional bits) #endif - // add dead time between signals (when using dithering, two full 8bit pulses are required) - // this is needed for 2CH, but also adds some slack for ESP8266 which has less precise - // PWM timing. - const int deadTime = (1+dithering) << bitShift; - // use CIE brightness formula (cubic) to fit (or approximate linearity of) human eye perceived brightness // the formula is based on 12 bit resolution as there is no need for greater precision // see: https://en.wikipedia.org/wiki/Lightness @@ -601,9 +596,12 @@ void BusPwm::show() { // Phase shifting requires that LEDC timers are synchronised (see setup()). For PWM CCT (and H-bridge) it is // also mandatory that both channels use the same timer (pinManager takes care of that). for (unsigned i = 0; i < numPins; i++) { - unsigned duty = (_data[i] * pwmBri) / 255; + unsigned duty = (_data[i] * pwmBri) / 255; + unsigned deadTime = 0; if (_type == TYPE_ANALOG_2CH && Bus::getCCTBlend() == 0) { + // add dead time between signals (when using dithering, two full 8bit pulses are required) + deadTime = (1+dithering) << bitShift; // we only need to take care of shortening the signal at (almost) full brightness otherwise pulses may overlap if (_bri >= 254 && duty >= maxBri / 2 && duty < maxBri) { duty -= deadTime << 1; // shorten duty of larger signal except if full on From ee380c5377b1f73e2a8da2247bb77288b9670899 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Mon, 30 Sep 2024 16:35:40 +0200 Subject: [PATCH 055/287] Replace uint16_t with unsigned for segment data swap if statements in color_fade --- wled00/FX.h | 8 ++++---- wled00/FX_fcn.cpp | 4 ++-- wled00/colors.cpp | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/wled00/FX.h b/wled00/FX.h index 763dc2639..065daa86d 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -400,7 +400,7 @@ typedef struct Segment { uint32_t _stepT; uint32_t _callT; uint8_t *_dataT; - uint16_t _dataLenT; + unsigned _dataLenT; TemporarySegmentData() : _dataT(nullptr) // just in case... , _dataLenT(0) @@ -418,8 +418,8 @@ typedef struct Segment { uint8_t _reserved : 4; }; }; - uint16_t _dataLen; - static uint16_t _usedSegmentData; + unsigned _dataLen; + static unsigned _usedSegmentData; static uint8_t _segBri; // brightness of segment for current effect static unsigned _vLength; // 1D dimension used for current effect static unsigned _vWidth, _vHeight; // 2D dimensions used for current effect @@ -537,7 +537,7 @@ typedef struct Segment { inline uint16_t groupLength() const { return grouping + spacing; } inline uint8_t getLightCapabilities() const { return _capabilities; } - inline static uint16_t getUsedSegmentData() { return Segment::_usedSegmentData; } + inline static unsigned getUsedSegmentData() { return Segment::_usedSegmentData; } inline static void addUsedSegmentData(int len) { Segment::_usedSegmentData += len; } #ifndef WLED_DISABLE_MODE_BLEND inline static void modeBlend(bool blend) { _modeBlend = blend; } diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 232994e73..13e9e73bb 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -80,7 +80,7 @@ static constexpr bool validatePinsAndTypes(const unsigned* types, unsigned numTy /////////////////////////////////////////////////////////////////////////////// // Segment class implementation /////////////////////////////////////////////////////////////////////////////// -uint16_t Segment::_usedSegmentData = 0U; // amount of RAM all segments use for their data[] +unsigned Segment::_usedSegmentData = 0U; // amount of RAM all segments use for their data[] uint16_t Segment::maxWidth = DEFAULT_LED_COUNT; uint16_t Segment::maxHeight = 1; unsigned Segment::_vLength = 0; @@ -433,7 +433,7 @@ uint8_t Segment::currentMode() const { return mode; } -uint32_t IRAM_ATTR_YN Segment::currentColor(uint8_t slot) const { +uint32_t Segment::currentColor(uint8_t slot) const { if (slot >= NUM_COLORS) slot = 0; #ifndef WLED_DISABLE_MODE_BLEND return isInTransition() ? color_blend(_t->_segT._colorT[slot], colors[slot], progress(), true) : colors[slot]; diff --git a/wled00/colors.cpp b/wled00/colors.cpp index 1c4844371..c059ea9db 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -77,8 +77,8 @@ uint32_t color_add(uint32_t c1, uint32_t c2, bool preserveCR) uint32_t color_fade(uint32_t c1, uint8_t amount, bool video) { - if (c1 == BLACK || amount == 0) return BLACK; if (amount == 255) return c1; + if (c1 == BLACK || amount == 0) return BLACK; uint32_t scaledcolor; // color order is: W R G B from MSB to LSB uint32_t scale = amount; // 32bit for faster calculation uint32_t addRemains = 0; From ba3a61f6236f316e3b03e3cf3bd92c79b0a1ce8f Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Wed, 2 Oct 2024 20:14:25 +0200 Subject: [PATCH 056/287] Reduced code size by: - removing WS2812FX::setMode() - removing WS2812FX::setColor() - removing floating point in transition - color handling modification in set.cpp - replaced uint8_t with unsigned in function parameters - inlined WS2812FX::isUpdating() - (MAY BE BREAKING) alexa & smartnest update --- usermods/smartnest/usermod_smartnest.h | 2 +- wled00/FX.h | 29 +++++++--------- wled00/FX_2Dfcn.cpp | 2 +- wled00/FX_fcn.cpp | 46 +++----------------------- wled00/alexa.cpp | 4 +-- wled00/bus_manager.h | 1 + wled00/led.cpp | 21 ++++++------ wled00/set.cpp | 45 ++++++++++++------------- wled00/udp.cpp | 6 ++-- wled00/wled.cpp | 10 +++--- wled00/wled.h | 1 - 11 files changed, 61 insertions(+), 106 deletions(-) diff --git a/usermods/smartnest/usermod_smartnest.h b/usermods/smartnest/usermod_smartnest.h index 92d524c88..9d21ef2e7 100644 --- a/usermods/smartnest/usermod_smartnest.h +++ b/usermods/smartnest/usermod_smartnest.h @@ -49,7 +49,7 @@ private: void setColor(int r, int g, int b) { - strip.setColor(0, r, g, b); + strip.getMainSegment().setColor(0, RGBW32(r, g, b, 0)); stateUpdated(CALL_MODE_DIRECT_CHANGE); char msg[18] {}; sprintf(msg, "rgb(%d,%d,%d)", r, g, b); diff --git a/wled00/FX.h b/wled00/FX.h index 065daa86d..14c268162 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -30,6 +30,7 @@ #include #include "const.h" +#include "bus_manager.h" #define FASTLED_INTERNAL //remove annoying pragma messages #define USE_GET_MILLISECOND_TIMER @@ -654,7 +655,7 @@ typedef struct Segment { void blurCol(int col, fract8 blur_amount, bool smear = false); void moveX(int delta, bool wrap = false); void moveY(int delta, bool wrap = false); - void move(uint8_t dir, uint8_t delta, bool wrap = false); + void move(unsigned dir, unsigned delta, bool wrap = false); 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); @@ -781,25 +782,22 @@ class WS2812FX { // 96 bytes #endif finalizeInit(), // initialises strip components service(), // executes effect functions when due and calls strip.show() - setMode(uint8_t segid, uint8_t m), // sets effect/mode for given segment (high level API) - setColor(uint8_t slot, uint32_t c), // sets color (in slot) for given segment (high level API) setCCT(uint16_t k), // sets global CCT (either in relative 0-255 value or in K) setBrightness(uint8_t b, bool direct = false), // sets strip brightness setRange(uint16_t i, uint16_t i2, uint32_t col), // used for clock overlay purgeSegments(), // removes inactive segments from RAM (may incure penalty and memory fragmentation but reduces vector footprint) setSegment(uint8_t n, uint16_t start, uint16_t stop, uint8_t grouping = 1, uint8_t spacing = 0, uint16_t offset = UINT16_MAX, uint16_t startY=0, uint16_t stopY=1), - setMainSegmentId(uint8_t n), + setMainSegmentId(unsigned n = 0), resetSegments(), // marks all segments for reset makeAutoSegments(bool forceReset = false), // will create segments based on configured outputs fixInvalidSegments(), // fixes incorrect segment configuration setPixelColor(unsigned n, uint32_t c), // paints absolute strip pixel with index n and color c show(), // initiates LED output - setTargetFps(uint8_t fps), + setTargetFps(unsigned fps), setupEffectData(); // add default effects to the list; defined in FX.cpp inline void restartRuntime() { for (Segment &seg : _segments) seg.markForReset(); } inline void setTransitionMode(bool t) { for (Segment &seg : _segments) seg.startTransition(t ? _transitionDur : 0); } - inline void setColor(uint8_t slot, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0) { setColor(slot, RGBW32(r,g,b,w)); } inline void setPixelColor(unsigned n, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0) { setPixelColor(n, RGBW32(r,g,b,w)); } inline void setPixelColor(unsigned n, CRGB c) { setPixelColor(n, c.red, c.green, c.blue); } inline void fill(uint32_t c) { for (unsigned i = 0; i < getLengthTotal(); i++) setPixelColor(i, c); } // fill whole strip with color (inline) @@ -815,9 +813,9 @@ class WS2812FX { // 96 bytes checkSegmentAlignment(), hasRGBWBus() const, hasCCTBus() const, - isUpdating() const, // return true if the strip is being sent pixel updates - deserializeMap(uint8_t n=0); + deserializeMap(unsigned n = 0); + inline bool isUpdating() const { return !BusManager::canAllShow(); } // return true if the strip is being sent pixel updates inline bool isServicing() const { return _isServicing; } // returns true if strip.service() is executing inline bool hasWhiteChannel() const { return _hasWhiteChannel; } // returns true if strip contains separate white chanel inline bool isOffRefreshRequired() const { return _isOffRefreshRequired; } // returns true if strip requires regular updates (i.e. TM1814 chipset) @@ -844,9 +842,9 @@ class WS2812FX { // 96 bytes uint16_t getLengthPhysical() const, - getLengthTotal() const, // will include virtual/nonexistent pixels in matrix - getFps() const; + getLengthTotal() const; // will include virtual/nonexistent pixels in matrix + inline uint16_t getFps() const { return (millis() - _lastShow > 2000) ? 0 : _cumulativeFps +1; } // Returns the refresh rate of the LED strip inline uint16_t getFrameTime() const { return _frametime; } // returns amount of time a frame should take (in ms) inline uint16_t getMinShowDelay() const { return MIN_SHOW_DELAY; } // returns minimum amount of time strip.service() can be delayed (constant) inline uint16_t getLength() const { return _length; } // returns actual amount of LEDs on a strip (2D matrix may have less LEDs than W*H) @@ -859,15 +857,12 @@ class WS2812FX { // 96 bytes uint32_t now, timebase; uint32_t getPixelColor(unsigned) const; - inline uint32_t getLastShow() const { return _lastShow; } // returns millis() timestamp of last strip.show() call + inline uint32_t getLastShow() const { return _lastShow; } // returns millis() timestamp of last strip.show() call - const char * - getModeData(uint8_t id = 0) const { return (id && id<_modeCount) ? _modeData[id] : PSTR("Solid"); } + const char *getModeData(unsigned id = 0) const { return (id && id < _modeCount) ? _modeData[id] : PSTR("Solid"); } + inline const char **getModeDataSrc() { return &(_modeData[0]); } // vectors use arrays for underlying data - const char ** - getModeDataSrc() { return &(_modeData[0]); } // vectors use arrays for underlying data - - Segment& getSegment(uint8_t id); + Segment& getSegment(unsigned id); inline Segment& getFirstSelectedSeg() { return _segments[getFirstSelectedSegId()]; } // returns reference to first segment that is "selected" inline Segment& getMainSegment() { return _segments[getMainSegmentId()]; } // returns reference to main segment inline Segment* getSegments() { return &(_segments[0]); } // returns pointer to segment vector structure (warning: use carefully) diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index 8804cdbb6..9b39b4c8f 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -499,7 +499,7 @@ void Segment::moveY(int delta, bool wrap) { // @param dir direction: 0=left, 1=left-up, 2=up, 3=right-up, 4=right, 5=right-down, 6=down, 7=left-down // @param delta number of pixels to move // @param wrap around -void Segment::move(uint8_t dir, uint8_t delta, bool wrap) { +void Segment::move(unsigned dir, unsigned delta, bool wrap) { if (delta==0) return; switch (dir) { case 0: moveX( delta, wrap); break; diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 13e9e73bb..7927269ed 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1464,49 +1464,11 @@ void WS2812FX::show() { _lastShow = showNow; } -/** - * Returns a true value if any of the strips are still being updated. - * On some hardware (ESP32), strip updates are done asynchronously. - */ -bool WS2812FX::isUpdating() const { - return !BusManager::canAllShow(); -} - -/** - * Returns the refresh rate of the LED strip. Useful for finding out whether a given setup is fast enough. - * Only updates on show() or is set to 0 fps if last show is more than 2 secs ago, so accuracy varies - */ -uint16_t WS2812FX::getFps() const { - if (millis() - _lastShow > 2000) return 0; - return _cumulativeFps +1; -} - -void WS2812FX::setTargetFps(uint8_t fps) { +void WS2812FX::setTargetFps(unsigned fps) { if (fps > 0 && fps <= 120) _targetFps = fps; _frametime = 1000 / _targetFps; } -void WS2812FX::setMode(uint8_t segid, uint8_t m) { - if (segid >= _segments.size()) return; - - if (m >= getModeCount()) m = getModeCount() - 1; - - if (_segments[segid].mode != m) { - _segments[segid].setMode(m); // do not load defaults - } -} - -//applies to all active and selected segments -void WS2812FX::setColor(uint8_t slot, uint32_t c) { - if (slot >= NUM_COLORS) return; - - for (segment &seg : _segments) { - if (seg.isActive() && seg.isSelected()) { - seg.setColor(slot, c); - } - } -} - void WS2812FX::setCCT(uint16_t k) { for (segment &seg : _segments) { if (seg.isActive() && seg.isSelected()) { @@ -1553,7 +1515,7 @@ uint8_t WS2812FX::getFirstSelectedSegId() const { return getMainSegmentId(); } -void WS2812FX::setMainSegmentId(uint8_t n) { +void WS2812FX::setMainSegmentId(unsigned n) { _mainSegment = 0; if (n < _segments.size()) { _mainSegment = n; @@ -1629,7 +1591,7 @@ void WS2812FX::purgeSegments() { } } -Segment& WS2812FX::getSegment(uint8_t id) { +Segment& WS2812FX::getSegment(unsigned id) { return _segments[id >= _segments.size() ? getMainSegmentId() : id]; // vectors } @@ -1844,7 +1806,7 @@ void WS2812FX::loadCustomPalettes() { } //load custom mapping table from JSON file (called from finalizeInit() or deserializeState()) -bool WS2812FX::deserializeMap(uint8_t n) { +bool WS2812FX::deserializeMap(unsigned n) { // 2D support creates its own ledmap (on the fly) if a ledmap.json exists it will overwrite built one. char fileName[32]; diff --git a/wled00/alexa.cpp b/wled00/alexa.cpp index b108f294b..81b9ec346 100644 --- a/wled00/alexa.cpp +++ b/wled00/alexa.cpp @@ -126,10 +126,10 @@ void onAlexaChange(EspalexaDevice* dev) } else { colorKtoRGB(k, rgbw); } - strip.setColor(0, RGBW32(rgbw[0], rgbw[1], rgbw[2], rgbw[3])); + strip.getMainSegment().setColor(0, RGBW32(rgbw[0], rgbw[1], rgbw[2], rgbw[3])); } else { uint32_t color = dev->getRGB(); - strip.setColor(0, color); + strip.getMainSegment().setColor(0, color); } stateUpdated(CALL_MODE_ALEXA); } diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h index 1b324d713..e25a06849 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -6,6 +6,7 @@ */ #include "const.h" +#include "pin_manager.h" #include //colors.cpp diff --git a/wled00/led.cpp b/wled00/led.cpp index 9de0495b4..5439fbb68 100644 --- a/wled00/led.cpp +++ b/wled00/led.cpp @@ -80,6 +80,7 @@ byte scaledBri(byte in) void applyBri() { if (!realtimeMode || !arlsForceMaxBri) { + //DEBUG_PRINTF_P(PSTR("Applying strip brightness: %d (%d,%d)\n"), (int)briT, (int)bri, (int)briOld); strip.setBrightness(scaledBri(briT)); } } @@ -144,7 +145,6 @@ void stateUpdated(byte callMode) { if (transitionActive) { briOld = briT; - tperLast = 0; } else strip.setTransitionMode(true); // force all segments to transition mode transitionActive = true; @@ -184,22 +184,21 @@ void handleTransitions() updateInterfaces(interfaceUpdateCallMode); if (transitionActive && strip.getTransition() > 0) { - float tper = (millis() - transitionStartTime)/(float)strip.getTransition(); - if (tper >= 1.0f) { + int ti = millis() - transitionStartTime; + int tr = strip.getTransition(); + if (ti/tr) { strip.setTransitionMode(false); // stop all transitions // restore (global) transition time if not called from UDP notifier or single/temporary transition from JSON (also playlist) if (jsonTransitionOnce) strip.setTransition(transitionDelay); transitionActive = false; jsonTransitionOnce = false; - tperLast = 0; applyFinalBri(); return; } - if (tper - tperLast < 0.004f) return; // less than 1 bit change (1/255) - tperLast = tper; - briT = briOld + ((bri - briOld) * tper); - - applyBri(); + byte briTO = briT; + int deltaBri = (int)bri - (int)briOld; + briT = briOld + (deltaBri * ti / tr); + if (briTO != briT) applyBri(); } } @@ -234,8 +233,8 @@ void handleNightlight() colNlT[1] = effectSpeed; colNlT[2] = effectPalette; - strip.setMode(strip.getFirstSelectedSegId(), FX_MODE_STATIC); // make sure seg runtime is reset if it was in sunrise mode - effectCurrent = FX_MODE_SUNRISE; + strip.getFirstSelectedSeg().setMode(FX_MODE_STATIC); // make sure seg runtime is reset if it was in sunrise mode + effectCurrent = FX_MODE_SUNRISE; // colorUpdated() will take care of assigning that to all selected segments effectSpeed = nightlightDelayMins; effectPalette = 0; if (effectSpeed > 60) effectSpeed = 60; //currently limited to 60 minutes diff --git a/wled00/set.cpp b/wled00/set.cpp index 9b7aa92a5..cf3a07dd0 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -837,8 +837,9 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply) } // temporary values, write directly to segments, globals are updated by setValuesFromFirstSelectedSeg() - uint32_t col0 = selseg.colors[0]; - uint32_t col1 = selseg.colors[1]; + uint32_t col0 = selseg.colors[0]; + uint32_t col1 = selseg.colors[1]; + uint32_t col2 = selseg.colors[2]; byte colIn[4] = {R(col0), G(col0), B(col0), W(col0)}; byte colInSec[4] = {R(col1), G(col1), B(col1), W(col1)}; byte effectIn = selseg.mode; @@ -919,7 +920,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply) //set brightness updateVal(req.c_str(), "&A=", &bri); - bool col0Changed = false, col1Changed = false; + bool col0Changed = false, col1Changed = false, col2Changed = false; //set colors col0Changed |= updateVal(req.c_str(), "&R=", &colIn[0]); col0Changed |= updateVal(req.c_str(), "&G=", &colIn[1]); @@ -976,7 +977,6 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply) } //set color from HEX or 32bit DEC - byte tmpCol[4]; pos = req.indexOf(F("CL=")); if (pos > 0) { colorFromDecOrHexString(colIn, (char*)req.substring(pos + 3).c_str()); @@ -989,10 +989,11 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply) } pos = req.indexOf(F("C3=")); if (pos > 0) { + byte tmpCol[4]; colorFromDecOrHexString(tmpCol, (char*)req.substring(pos + 3).c_str()); - uint32_t col2 = RGBW32(tmpCol[0], tmpCol[1], tmpCol[2], tmpCol[3]); + col2 = RGBW32(tmpCol[0], tmpCol[1], tmpCol[2], tmpCol[3]); selseg.setColor(2, col2); // defined above (SS= or main) - if (!singleSegment) strip.setColor(2, col2); // will set color to all active & selected segments + col2Changed = true; } //set to random hue SR=0->1st SR=1->2nd @@ -1003,29 +1004,22 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply) col0Changed |= (!sec); col1Changed |= sec; } - //swap 2nd & 1st - pos = req.indexOf(F("SC")); - if (pos > 0) { - byte temp; - for (unsigned i=0; i<4; i++) { - temp = colIn[i]; - colIn[i] = colInSec[i]; - colInSec[i] = temp; - } - col0Changed = col1Changed = true; - } - // apply colors to selected segment, and all selected segments if applicable if (col0Changed) { - uint32_t colIn0 = RGBW32(colIn[0], colIn[1], colIn[2], colIn[3]); - selseg.setColor(0, colIn0); - if (!singleSegment) strip.setColor(0, colIn0); // will set color to all active & selected segments + col0 = RGBW32(colIn[0], colIn[1], colIn[2], colIn[3]); + selseg.setColor(0, col0); } if (col1Changed) { - uint32_t colIn1 = RGBW32(colInSec[0], colInSec[1], colInSec[2], colInSec[3]); - selseg.setColor(1, colIn1); - if (!singleSegment) strip.setColor(1, colIn1); // will set color to all active & selected segments + col1 = RGBW32(colInSec[0], colInSec[1], colInSec[2], colInSec[3]); + selseg.setColor(1, col1); + } + + //swap 2nd & 1st + pos = req.indexOf(F("SC")); + if (pos > 0) { + std::swap(col0,col1); + col0Changed = col1Changed = true; } bool fxModeChanged = false, speedChanged = false, intensityChanged = false, paletteChanged = false; @@ -1055,6 +1049,9 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply) if (speedChanged) seg.speed = speedIn; if (intensityChanged) seg.intensity = intensityIn; if (paletteChanged) seg.setPalette(paletteIn); + if (col0Changed) seg.setColor(0, col0); + if (col1Changed) seg.setColor(1, col1); + if (col2Changed) seg.setColor(2, col2); if (custom1Changed) seg.custom1 = custom1In; if (custom2Changed) seg.custom2 = custom2In; if (custom3Changed) seg.custom3 = custom3In; diff --git a/wled00/udp.cpp b/wled00/udp.cpp index a615cefcb..0ff39f110 100644 --- a/wled00/udp.cpp +++ b/wled00/udp.cpp @@ -234,12 +234,12 @@ void parseNotifyPacket(uint8_t *udpIn) { //apply colors from notification to main segment, only if not syncing full segments if ((receiveNotificationColor || !someSel) && (version < 11 || !receiveSegmentOptions)) { // primary color, only apply white if intented (version > 0) - strip.setColor(0, RGBW32(udpIn[3], udpIn[4], udpIn[5], (version > 0) ? udpIn[10] : 0)); + strip.getMainSegment().setColor(0, RGBW32(udpIn[3], udpIn[4], udpIn[5], (version > 0) ? udpIn[10] : 0)); if (version > 1) { - strip.setColor(1, RGBW32(udpIn[12], udpIn[13], udpIn[14], udpIn[15])); // secondary color + strip.getMainSegment().setColor(1, RGBW32(udpIn[12], udpIn[13], udpIn[14], udpIn[15])); // secondary color } if (version > 6) { - strip.setColor(2, RGBW32(udpIn[20], udpIn[21], udpIn[22], udpIn[23])); // tertiary color + strip.getMainSegment().setColor(2, RGBW32(udpIn[20], udpIn[21], udpIn[22], udpIn[23])); // tertiary color if (version > 9 && udpIn[37] < 255) { // valid CCT/Kelvin value unsigned cct = udpIn[38]; if (udpIn[37] > 0) { //Kelvin diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 39e0d250b..a80808a78 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -221,6 +221,7 @@ void WLED::loop() strip.finalizeInit(); // also loads default ledmap if present if (aligned) strip.makeAutoSegments(); else strip.fixInvalidSegments(); + BusManager::setBrightness(bri); // fix re-initialised bus' brightness doSerializeConfig = true; } if (loadLedmap >= 0) { @@ -571,10 +572,11 @@ void WLED::beginStrip() } else { // fix for #3196 if (bootPreset > 0) { - bool oldTransition = fadeTransition; // workaround if transitions are enabled - fadeTransition = false; // ignore transitions temporarily - strip.setColor(0, BLACK); // set all segments black - fadeTransition = oldTransition; // restore transitions + // set all segments black (no transition) + for (unsigned i = 0; i < strip.getSegmentsNum(); i++) { + Segment &seg = strip.getSegment(i); + if (seg.isActive()) seg.colors[0] = BLACK; + } col[0] = col[1] = col[2] = col[3] = 0; // needed for colorUpdated() } briLast = briS; bri = 0; diff --git a/wled00/wled.h b/wled00/wled.h index 052f29b29..f8021e92b 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -584,7 +584,6 @@ WLED_GLOBAL bool transitionActive _INIT(false); WLED_GLOBAL uint16_t transitionDelay _INIT(750); // global transition duration WLED_GLOBAL uint16_t transitionDelayDefault _INIT(750); // default transition time (stored in cfg.json) WLED_GLOBAL unsigned long transitionStartTime; -WLED_GLOBAL float tperLast _INIT(0.0f); // crossfade transition progress, 0.0f - 1.0f WLED_GLOBAL bool jsonTransitionOnce _INIT(false); // flag to override transitionDelay (playlist, JSON API: "live" & "seg":{"i"} & "tt") WLED_GLOBAL uint8_t randomPaletteChangeTime _INIT(5); // amount of time [s] between random palette changes (min: 1s, max: 255s) WLED_GLOBAL bool useHarmonicRandomPalette _INIT(true); // use *harmonic* random palette generation (nicer looking) or truly random From a15c391e6c00dd1b81f4e3116aa87a81628a17e8 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Thu, 3 Oct 2024 21:19:34 +0200 Subject: [PATCH 057/287] Improvement to `setPixelColorXY` and some flash optimisations - changes to `setPixelColorXY` give an extra FPS, some checks and the loops are only done when needed, additional function call is still faster (force inlining it gives negligible speed boost but eats more flash) - commented out the unused `boxBlur` function - code size improvemnts (also faster) in `moveX()` and `moveY()` by only copying whats required and avoiding code duplications - consolidated the `blur()` functions by enabling asymmetrical blur2D() to replace `blurRow` and `blurCol` - compiler warning fixes (explicit unsigned casts) --- wled00/FX.h | 19 ++- wled00/FX_2Dfcn.cpp | 284 +++++++++++++++++++++----------------------- wled00/FX_fcn.cpp | 2 +- 3 files changed, 142 insertions(+), 163 deletions(-) diff --git a/wled00/FX.h b/wled00/FX.h index 14c268162..c06332c76 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -457,6 +457,8 @@ typedef struct Segment { {} } *_t; + [[gnu::hot]] void _setPixelColorXY_raw(int& x, int& y, uint32_t& col); // set pixel without mapping (internal use only) + public: Segment(uint16_t sStart=0, uint16_t sStop=30) : @@ -617,12 +619,10 @@ typedef struct Segment { // 2D Blur: shortcuts for bluring columns or rows only (50% faster than full 2D blur) inline void blurCols(fract8 blur_amount, bool smear = false) { // blur all columns - const unsigned cols = virtualWidth(); - for (unsigned k = 0; k < cols; k++) blurCol(k, blur_amount, smear); + blur2D(0, blur_amount, smear); } inline void blurRows(fract8 blur_amount, bool smear = false) { // blur all rows - const unsigned rows = virtualHeight(); - for ( unsigned i = 0; i < rows; i++) blurRow(i, blur_amount, smear); + blur2D(blur_amount, 0, smear); } // 2D matrix @@ -649,10 +649,8 @@ typedef struct Segment { inline void addPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0, bool preserveCR = true) { addPixelColorXY(x, y, RGBW32(r,g,b,w), preserveCR); } inline void addPixelColorXY(int x, int y, CRGB c, bool preserveCR = true) { addPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), preserveCR); } inline void fadePixelColorXY(uint16_t x, uint16_t y, uint8_t fade) { setPixelColorXY(x, y, color_fade(getPixelColorXY(x,y), fade, true)); } - void box_blur(unsigned r = 1U, bool smear = false); // 2D box blur - void blur2D(uint8_t blur_amount, bool smear = false); - void blurRow(int row, fract8 blur_amount, bool smear = false); - void blurCol(int col, fract8 blur_amount, bool smear = false); + //void box_blur(unsigned r = 1U, bool smear = false); // 2D box blur + void blur2D(uint8_t blur_x, uint8_t blur_y, bool smear = false); void moveX(int delta, bool wrap = false); void moveY(int delta, bool wrap = false); void move(unsigned dir, unsigned delta, bool wrap = false); @@ -666,7 +664,6 @@ typedef struct Segment { inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, CRGB c) { drawCharacter(chr, x, y, w, h, RGBW32(c.r,c.g,c.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, int8_t rotate = 0) { drawCharacter(chr, x, y, w, h, RGBW32(c.r,c.g,c.b,0), RGBW32(c2.r,c2.g,c2.b,0), rotate); } // automatic inline void wu_pixel(uint32_t x, uint32_t y, CRGB c); - inline void blur2d(fract8 blur_amount) { blur(blur_amount); } inline void fill_solid(CRGB c) { fill(RGBW32(c.r,c.g,c.b,0)); } #else inline uint16_t XY(int x, int y) { return x; } @@ -687,8 +684,8 @@ typedef struct Segment { inline void addPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0, bool saturate = false) { addPixelColor(x, RGBW32(r,g,b,w), saturate); } inline void addPixelColorXY(int x, int y, CRGB c, bool saturate = false) { addPixelColor(x, RGBW32(c.r,c.g,c.b,0), saturate); } inline void fadePixelColorXY(uint16_t x, uint16_t y, uint8_t fade) { fadePixelColor(x, fade); } - inline void box_blur(unsigned i, bool vertical, fract8 blur_amount) {} - inline void blur2D(uint8_t blur_amount, bool smear = false) {} + //inline void box_blur(unsigned i, bool vertical, fract8 blur_amount) {} + inline void blur2D(uint8_t blur_x, uint8_t blur_y, bool smear = false) {} inline void blurRow(int row, fract8 blur_amount, bool smear = false) {} inline void blurCol(int col, fract8 blur_amount, bool smear = false) {} inline void moveX(int delta, bool wrap = false) {} diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index 9b39b4c8f..3ad061a8c 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -168,13 +168,34 @@ uint16_t IRAM_ATTR_YN Segment::XY(int x, int y) return isActive() ? (x%vW) + (y%vH) * vW : 0; } +// raw setColor function without checks (checks are done in setPixelColorXY()) +void IRAM_ATTR_YN Segment::_setPixelColorXY_raw(int& x, int& y, uint32_t& col) +{ +#ifndef WLED_DISABLE_MODE_BLEND + // if blending modes, blend with underlying pixel + if (_modeBlend) col = color_blend(strip.getPixelColorXY(start + x, startY + y), col, 0xFFFFU - progress(), true); +#endif + strip.setPixelColorXY(start + x, startY + y, col); + if (mirror) { //set the corresponding horizontally mirrored pixel + if (transpose) strip.setPixelColorXY(start + x, startY + height() - y - 1, col); + else strip.setPixelColorXY(start + width() - x - 1, startY + y, col); + } + if (mirror_y) { //set the corresponding vertically mirrored pixel + if (transpose) strip.setPixelColorXY(start + width() - x - 1, startY + y, col); + else strip.setPixelColorXY(start + x, startY + height() - y - 1, col); + } + if (mirror_y && mirror) { //set the corresponding vertically AND horizontally mirrored pixel + strip.setPixelColorXY(start + width() - x - 1, startY + height() - y - 1, col); + } +} + void IRAM_ATTR_YN Segment::setPixelColorXY(int x, int y, uint32_t col) { if (!isActive()) return; // not active const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive) const int vH = vHeight(); // segment height in logical pixels (is always >= 1) - if (unsigned(x) >= vW || unsigned(y) >= vH) return; // if pixel would fall out of virtual segment just exit + if (unsigned(x) >= unsigned(vW) || unsigned(y) >= unsigned(vH)) return; // if pixel would fall out of virtual segment just exit // if color is unscaled if (!_colorScaled) col = color_fade(col, _segBri); @@ -182,36 +203,28 @@ void IRAM_ATTR_YN Segment::setPixelColorXY(int x, int y, uint32_t col) if (reverse ) x = vW - x - 1; if (reverse_y) y = vH - y - 1; if (transpose) { std::swap(x,y); } // swap X & Y if segment transposed - x *= groupLength(); // expand to physical pixels - y *= groupLength(); // expand to physical pixels - int W = width(); - int H = height(); + unsigned groupLen = groupLength(); - int yY = y; - for (int j = 0; j < grouping; j++) { // groupping vertically - if (yY >= H) break; - int xX = x; - for (int g = 0; g < grouping; g++) { // groupping horizontally - if (xX >= W) break; // we have reached X dimension's end -#ifndef WLED_DISABLE_MODE_BLEND - // if blending modes, blend with underlying pixel - if (_modeBlend) col = color_blend(strip.getPixelColorXY(start + xX, startY + yY), col, 0xFFFFU - progress(), true); -#endif - strip.setPixelColorXY(start + xX, startY + yY, col); - if (mirror) { //set the corresponding horizontally mirrored pixel - if (transpose) strip.setPixelColorXY(start + xX, startY + height() - yY - 1, col); - else strip.setPixelColorXY(start + width() - xX - 1, startY + yY, col); + if(groupLen > 1) + { + int W = width(); + int H = height(); + x *= groupLen; // expand to physical pixels + y *= groupLen; // expand to physical pixels + int yY = y; + for (int j = 0; j < grouping; j++) { // groupping vertically + if (yY >= H) break; + int xX = x; + for (int g = 0; g < grouping; g++) { // groupping horizontally + if (xX >= W) break; // we have reached X dimension's end + _setPixelColorXY_raw(xX, yY, col); + xX++; } - if (mirror_y) { //set the corresponding vertically mirrored pixel - if (transpose) strip.setPixelColorXY(start + width() - xX - 1, startY + yY, col); - else strip.setPixelColorXY(start + xX, startY + height() - yY - 1, col); - } - if (mirror_y && mirror) { //set the corresponding vertically AND horizontally mirrored pixel - strip.setPixelColorXY(start + width() - xX - 1, startY + height() - yY - 1, col); - } - xX++; + yY++; } - yY++; + } + else { + _setPixelColorXY_raw(x, y, col); } } @@ -263,7 +276,7 @@ uint32_t IRAM_ATTR_YN Segment::getPixelColorXY(int x, int y) const { if (!isActive()) return 0; // not active int vW = vWidth(); int vH = vHeight(); - if (x >= vW || y >= vH || x<0 || y<0) return 0; // if pixel would fall out of virtual segment just exit + if (unsigned(x) >= unsigned(vW) || unsigned(y) >= unsigned(vH)) return 0; // if pixel would fall out of virtual segment just exit if (reverse ) x = vW - x - 1; if (reverse_y) y = vH - y - 1; if (transpose) { std::swap(x,y); } // swap X & Y if segment transposed @@ -273,119 +286,62 @@ uint32_t IRAM_ATTR_YN Segment::getPixelColorXY(int x, int y) const { return strip.getPixelColorXY(start + x, startY + y); } -// blurRow: perform a blur on a row of a rectangular matrix -void Segment::blurRow(int row, fract8 blur_amount, bool smear){ - if (!isActive() || blur_amount == 0) return; // not active - const int cols = vWidth(); - const int rows = vHeight(); - - if (row >= rows) return; - // blur one row - uint8_t keep = smear ? 255 : 255 - blur_amount; - uint8_t seep = blur_amount >> 1; - uint32_t carryover = BLACK; - uint32_t lastnew; - uint32_t last; - uint32_t curnew = BLACK; - for (int x = 0; x < cols; x++) { - uint32_t cur = getPixelColorXY(x, row); - uint32_t part = color_fade(cur, seep); - curnew = color_fade(cur, keep); - if (x > 0) { - if (carryover) curnew = color_add(curnew, carryover); - uint32_t prev = color_add(lastnew, part); - // optimization: only set pixel if color has changed - if (last != prev) setPixelColorXY(x - 1, row, prev); - } else // first pixel - setPixelColorXY(x, row, curnew); - lastnew = curnew; - last = cur; // save original value for comparison on next iteration - carryover = part; - } - setPixelColorXY(cols-1, row, curnew); // set last pixel -} - -// blurCol: perform a blur on a column of a rectangular matrix -void Segment::blurCol(int col, fract8 blur_amount, bool smear) { - if (!isActive() || blur_amount == 0) return; // not active - const int cols = vWidth(); - const int rows = vHeight(); - - if (col >= cols) return; - // blur one column - uint8_t keep = smear ? 255 : 255 - blur_amount; - uint8_t seep = blur_amount >> 1; - uint32_t carryover = BLACK; - uint32_t lastnew; - uint32_t last; - uint32_t curnew = BLACK; - for (int y = 0; y < rows; y++) { - uint32_t cur = getPixelColorXY(col, y); - uint32_t part = color_fade(cur, seep); - curnew = color_fade(cur, keep); - if (y > 0) { - if (carryover) curnew = color_add(curnew, carryover); - uint32_t prev = color_add(lastnew, part); - // optimization: only set pixel if color has changed - if (last != prev) setPixelColorXY(col, y - 1, prev); - } else // first pixel - setPixelColorXY(col, y, curnew); - lastnew = curnew; - last = cur; //save original value for comparison on next iteration - carryover = part; - } - setPixelColorXY(col, rows - 1, curnew); -} - -void Segment::blur2D(uint8_t blur_amount, bool smear) { - if (!isActive() || blur_amount == 0) return; // not active +// 2D blurring, can be asymmetrical +void Segment::blur2D(uint8_t blur_x, uint8_t blur_y, bool smear) { + if (!isActive()) return; // not active const unsigned cols = vWidth(); const unsigned rows = vHeight(); - - const uint8_t keep = smear ? 255 : 255 - blur_amount; - const uint8_t seep = blur_amount >> (1 + smear); uint32_t lastnew; uint32_t last; - for (unsigned row = 0; row < rows; row++) { - uint32_t carryover = BLACK; - uint32_t curnew = BLACK; - for (unsigned x = 0; x < cols; x++) { - uint32_t cur = getPixelColorXY(x, row); - uint32_t part = color_fade(cur, seep); - curnew = color_fade(cur, keep); - if (x > 0) { - if (carryover) curnew = color_add(curnew, carryover); - uint32_t prev = color_add(lastnew, part); - // optimization: only set pixel if color has changed - if (last != prev) setPixelColorXY(x - 1, row, prev); - } else setPixelColorXY(x, row, curnew); // first pixel - lastnew = curnew; - last = cur; // save original value for comparison on next iteration - carryover = part; + if(blur_x) { + const uint8_t keepx = smear ? 255 : 255 - blur_x; + const uint8_t seepx = blur_x >> (1 + smear); + for (unsigned row = 0; row < rows; row++) { // blur rows (x direction) + uint32_t carryover = BLACK; + uint32_t curnew = BLACK; + for (unsigned x = 0; x < cols; x++) { + uint32_t cur = getPixelColorXY(x, row); + uint32_t part = color_fade(cur, seepx); + curnew = color_fade(cur, keepx); + if (x > 0) { + if (carryover) curnew = color_add(curnew, carryover); + uint32_t prev = color_add(lastnew, part); + // optimization: only set pixel if color has changed + if (last != prev) setPixelColorXY(x - 1, row, prev); + } else setPixelColorXY(x, row, curnew); // first pixel + lastnew = curnew; + last = cur; // save original value for comparison on next iteration + carryover = part; + } + setPixelColorXY(cols-1, row, curnew); // set last pixel } - setPixelColorXY(cols-1, row, curnew); // set last pixel } - for (unsigned col = 0; col < cols; col++) { - uint32_t carryover = BLACK; - uint32_t curnew = BLACK; - for (unsigned y = 0; y < rows; y++) { - uint32_t cur = getPixelColorXY(col, y); - uint32_t part = color_fade(cur, seep); - curnew = color_fade(cur, keep); - if (y > 0) { - if (carryover) curnew = color_add(curnew, carryover); - uint32_t prev = color_add(lastnew, part); - // optimization: only set pixel if color has changed - if (last != prev) setPixelColorXY(col, y - 1, prev); - } else setPixelColorXY(col, y, curnew); // first pixel - lastnew = curnew; - last = cur; //save original value for comparison on next iteration - carryover = part; + if(blur_y) { + const uint8_t keepy = smear ? 255 : 255 - blur_y; + const uint8_t seepy = blur_y >> (1 + smear); + for (unsigned col = 0; col < cols; col++) { + uint32_t carryover = BLACK; + uint32_t curnew = BLACK; + for (unsigned y = 0; y < rows; y++) { + uint32_t cur = getPixelColorXY(col, y); + uint32_t part = color_fade(cur, seepy); + curnew = color_fade(cur, keepy); + if (y > 0) { + if (carryover) curnew = color_add(curnew, carryover); + uint32_t prev = color_add(lastnew, part); + // optimization: only set pixel if color has changed + if (last != prev) setPixelColorXY(col, y - 1, prev); + } else setPixelColorXY(col, y, curnew); // first pixel + lastnew = curnew; + last = cur; //save original value for comparison on next iteration + carryover = part; + } + setPixelColorXY(col, rows - 1, curnew); } - setPixelColorXY(col, rows - 1, curnew); } } +/* // 2D Box blur void Segment::box_blur(unsigned radius, bool smear) { if (!isActive() || radius == 0) return; // not active @@ -458,43 +414,69 @@ void Segment::box_blur(unsigned radius, bool smear) { delete[] tmpBSum; delete[] tmpWSum; } - +*/ void Segment::moveX(int delta, bool wrap) { - if (!isActive()) return; // not active + if (!isActive() || !delta) return; // not active const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive) const int vH = vHeight(); // segment height in logical pixels (is always >= 1) - if (!delta || abs(delta) >= vW) return; + int absDelta = abs(delta); + if (absDelta >= vW) return; uint32_t newPxCol[vW]; + int newDelta; + int stop = vW; + int start = 0; + if(wrap) newDelta = (delta + vW) % vW; // +cols in case delta < 0 + else { + if(delta < 0) start = -delta; + stop = vW - absDelta; + newDelta = delta > 0 ? delta : 0; + } for (int y = 0; y < vH; y++) { - if (delta > 0) { - for (int x = 0; x < vW-delta; x++) newPxCol[x] = getPixelColorXY((x + delta), y); - for (int x = vW-delta; x < vW; x++) newPxCol[x] = getPixelColorXY(wrap ? (x + delta) - vW : x, y); - } else { - for (int x = vW-1; x >= -delta; x--) newPxCol[x] = getPixelColorXY((x + delta), y); - for (int x = -delta-1; x >= 0; x--) newPxCol[x] = getPixelColorXY(wrap ? (x + delta) + vW : x, y); + for (int x = 0; x < stop; x++) { + int srcX; + if (wrap) { + srcX = (x + newDelta) % vW; // Wrap using modulo when `wrap` is true + } else { + srcX = x + newDelta; + } + newPxCol[x] = getPixelColorXY(srcX, y); } - for (int x = 0; x < vW; x++) setPixelColorXY(x, y, newPxCol[x]); + for (int x = 0; x < stop; x++) setPixelColorXY(x + start, y, newPxCol[x]); } } void Segment::moveY(int delta, bool wrap) { - if (!isActive()) return; // not active + if (!isActive() || !delta) return; // not active const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive) const int vH = vHeight(); // segment height in logical pixels (is always >= 1) - if (!delta || abs(delta) >= vH) return; + int absDelta = abs(delta); + if (absDelta >= vH) return; uint32_t newPxCol[vH]; + int newDelta; + int stop = vH; + int start = 0; + if(wrap) newDelta = (delta + vH) % vH; // +rows in case delta < 0 + else { + if(delta < 0) start = -delta; + stop = vH - absDelta; + newDelta = delta > 0 ? delta : 0; + } for (int x = 0; x < vW; x++) { - if (delta > 0) { - for (int y = 0; y < vH-delta; y++) newPxCol[y] = getPixelColorXY(x, (y + delta)); - for (int y = vH-delta; y < vH; y++) newPxCol[y] = getPixelColorXY(x, wrap ? (y + delta) - vH : y); - } else { - for (int y = vH-1; y >= -delta; y--) newPxCol[y] = getPixelColorXY(x, (y + delta)); - for (int y = -delta-1; y >= 0; y--) newPxCol[y] = getPixelColorXY(x, wrap ? (y + delta) + vH : y); + for (int y = 0; y < stop; y++) { + int srcY; + if (wrap) { + srcY = (y + newDelta) % vH; // Wrap using modulo when `wrap` is true + } else { + srcY = y + newDelta; + } + newPxCol[y] = getPixelColorXY(x, srcY); } - for (int y = 0; y < vH; y++) setPixelColorXY(x, y, newPxCol[y]); + for (int y = 0; y < stop; y++) setPixelColorXY(x, y + start, newPxCol[y]); } } +// TODO: check if it works, used in FX rain and 2D spaceships + // move() - move all pixels in desired direction delta number of pixels // @param dir direction: 0=left, 1=left-up, 2=up, 3=right-up, 4=right, 5=right-down, 6=down, 7=left-down // @param delta number of pixels to move diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 7927269ed..613b41fad 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1146,7 +1146,7 @@ void Segment::blur(uint8_t blur_amount, bool smear) { #ifndef WLED_DISABLE_2D if (is2D()) { // compatibility with 2D - blur2D(blur_amount, smear); + blur2D(blur_amount, blur_amount, smear); // symmetrical 2D blur //box_blur(map(blur_amount,1,255,1,3), smear); return; } From ca062140f3c17eb64928a5b47566bb7599828cb1 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Thu, 3 Oct 2024 19:39:39 +0000 Subject: [PATCH 058/287] removed todo. --- wled00/FX_2Dfcn.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index 3ad061a8c..6b3e6ef11 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -475,8 +475,6 @@ void Segment::moveY(int delta, bool wrap) { } } -// TODO: check if it works, used in FX rain and 2D spaceships - // move() - move all pixels in desired direction delta number of pixels // @param dir direction: 0=left, 1=left-up, 2=up, 3=right-up, 4=right, 5=right-down, 6=down, 7=left-down // @param delta number of pixels to move From 7b855c851d7824596557d4444796c2c3aec6356c Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Sat, 5 Oct 2024 12:29:31 +0200 Subject: [PATCH 059/287] Added integer based `sin()/cos()` functions, changed all trig functions to wled_math - `sin16_t() / cos16_t()` are faster and more accurate than fastled versions - `sin_approx() / cos_approx()` are float wrappers for `sin16_t() / cos16_t()` and are accurate enough to replace `sinf()/cosf()` - `atan2()` is used only in octopus to calculate center offset, new approximated version saves flash - `tan(), atan(), asin(), acos(), floor(), fmod()` are used only for sunrise/sunset calculation, using wled_math version saves flash - `beatsinx()` replacements are to make use of new `sin16_t()/sin8_t()` functions to reduce flash size - Extensively tested surnise/sunset calculation: deviation is 1min. max - Tested some of the relevant FX and found no visual difference: Julia, 2D Drift, Drift Rose, Ghost rider, Rotozoomer, Palette, Arc 1D expansion - total flash savings: 7.4k --- usermods/Analog_Clock/Analog_Clock.h | 4 +- wled00/FX.cpp | 258 +++++++++++++-------------- wled00/fcn_declare.h | 53 +++--- wled00/util.cpp | 41 ++++- wled00/wled_math.cpp | 89 ++++++++- 5 files changed, 288 insertions(+), 157 deletions(-) diff --git a/usermods/Analog_Clock/Analog_Clock.h b/usermods/Analog_Clock/Analog_Clock.h index 596f0acb3..9d82f7670 100644 --- a/usermods/Analog_Clock/Analog_Clock.h +++ b/usermods/Analog_Clock/Analog_Clock.h @@ -102,9 +102,9 @@ private: void secondsEffectSineFade(int16_t secondLed, Toki::Time const& time) { uint32_t ms = time.ms % 1000; - uint8_t b0 = (cos8(ms * 64 / 1000) - 128) * 2; + uint8_t b0 = (cos8_t(ms * 64 / 1000) - 128) * 2; setPixelColor(secondLed, gamma32(scale32(secondColor, b0))); - uint8_t b1 = (sin8(ms * 64 / 1000) - 128) * 2; + uint8_t b1 = (sin8_t(ms * 64 / 1000) - 128) * 2; setPixelColor(inc(secondLed, 1, secondsSegment), gamma32(scale32(secondColor, b1))); } diff --git a/wled00/FX.cpp b/wled00/FX.cpp index f3e82275c..338dd2df9 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -39,7 +39,7 @@ // effect utility functions uint8_t sin_gap(uint16_t in) { if (in & 0x100) return 0; - return sin8(in + 192); // correct phase shift of sine so that it starts and stops at 0 + return sin8_t(in + 192); // correct phase shift of sine so that it starts and stops at 0 } uint16_t triwave16(uint16_t in) { @@ -349,7 +349,7 @@ uint16_t mode_breath(void) { counter = (counter >> 2) + (counter >> 4); //0-16384 + 0-2048 if (counter < 16384) { if (counter > 8192) counter = 8192 - (counter - 8192); - var = sin16(counter) / 103; //close to parabolic in range 0-8192, max val. 23170 + var = sin16_t(counter) / 103; //close to parabolic in range 0-8192, max val. 23170 } unsigned lum = 30 + var; @@ -531,7 +531,7 @@ static uint16_t running_base(bool saw, bool dual=false) { } a = 255 - a; } - uint8_t s = dual ? sin_gap(a) : sin8(a); + uint8_t s = dual ? sin_gap(a) : sin8_t(a); uint32_t ca = color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0), s); if (dual) { unsigned b = (SEGLEN-1-i)*x_scale - counter; @@ -1893,16 +1893,16 @@ uint16_t mode_pride_2015(void) { unsigned sPseudotime = SEGENV.step; unsigned sHue16 = SEGENV.aux0; - uint8_t sat8 = beatsin88( 87, 220, 250); - uint8_t brightdepth = beatsin88( 341, 96, 224); - unsigned brightnessthetainc16 = beatsin88( 203, (25 * 256), (40 * 256)); - unsigned msmultiplier = beatsin88(147, 23, 60); + uint8_t sat8 = beatsin88_t( 87, 220, 250); + uint8_t brightdepth = beatsin88_t( 341, 96, 224); + unsigned brightnessthetainc16 = beatsin88_t( 203, (25 * 256), (40 * 256)); + unsigned msmultiplier = beatsin88_t(147, 23, 60); unsigned hue16 = sHue16;//gHue * 256; - unsigned hueinc16 = beatsin88(113, 1, 3000); + unsigned hueinc16 = beatsin88_t(113, 1, 3000); sPseudotime += duration * msmultiplier; - sHue16 += duration * beatsin88( 400, 5,9); + sHue16 += duration * beatsin88_t( 400, 5,9); unsigned brightnesstheta16 = sPseudotime; for (unsigned i = 0 ; i < SEGLEN; i++) { @@ -1910,7 +1910,7 @@ uint16_t mode_pride_2015(void) { uint8_t hue8 = hue16 >> 8; brightnesstheta16 += brightnessthetainc16; - unsigned b16 = sin16( brightnesstheta16 ) + 32768; + unsigned b16 = sin16_t( brightnesstheta16 ) + 32768; unsigned bri16 = (uint32_t)((uint32_t)b16 * (uint32_t)b16) / 65536; uint8_t bri8 = (uint32_t)(((uint32_t)bri16) * brightdepth) / 65536; @@ -1935,7 +1935,7 @@ uint16_t mode_juggle(void) { CRGB fastled_col; byte dothue = 0; for (int i = 0; i < 8; i++) { - int index = 0 + beatsin88((16 + SEGMENT.speed)*(i + 7), 0, SEGLEN -1); + int index = 0 + beatsin88_t((16 + SEGMENT.speed)*(i + 7), 0, SEGLEN -1); fastled_col = CRGB(SEGMENT.getPixelColor(index)); fastled_col |= (SEGMENT.palette==0)?CHSV(dothue, 220, 255):ColorFromPalette(SEGPALETTE, dothue, 255); SEGMENT.setPixelColor(index, fastled_col); @@ -1956,8 +1956,8 @@ uint16_t mode_palette() { constexpr mathType maxAngle = 0x8000; constexpr mathType staticRotationScale = 256; constexpr mathType animatedRotationScale = 1; - constexpr int16_t (*sinFunction)(uint16_t) = &sin16; - constexpr int16_t (*cosFunction)(uint16_t) = &cos16; + constexpr int16_t (*sinFunction)(uint16_t) = &sin16_t; + constexpr int16_t (*cosFunction)(uint16_t) = &cos16_t; #else using mathType = float; using wideMathType = float; @@ -2141,15 +2141,15 @@ uint16_t mode_colorwaves() { unsigned sPseudotime = SEGENV.step; unsigned sHue16 = SEGENV.aux0; - unsigned brightdepth = beatsin88(341, 96, 224); - unsigned brightnessthetainc16 = beatsin88( 203, (25 * 256), (40 * 256)); - unsigned msmultiplier = beatsin88(147, 23, 60); + unsigned brightdepth = beatsin88_t(341, 96, 224); + unsigned brightnessthetainc16 = beatsin88_t( 203, (25 * 256), (40 * 256)); + unsigned msmultiplier = beatsin88_t(147, 23, 60); unsigned hue16 = sHue16;//gHue * 256; - unsigned hueinc16 = beatsin88(113, 60, 300)*SEGMENT.intensity*10/255; // Use the Intensity Slider for the hues + unsigned hueinc16 = beatsin88_t(113, 60, 300)*SEGMENT.intensity*10/255; // Use the Intensity Slider for the hues sPseudotime += duration * msmultiplier; - sHue16 += duration * beatsin88(400, 5, 9); + sHue16 += duration * beatsin88_t(400, 5, 9); unsigned brightnesstheta16 = sPseudotime; for (int i = 0 ; i < SEGLEN; i++) { @@ -2163,7 +2163,7 @@ uint16_t mode_colorwaves() { } brightnesstheta16 += brightnessthetainc16; - unsigned b16 = sin16(brightnesstheta16) + 32768; + unsigned b16 = sin16_t(brightnesstheta16) + 32768; unsigned bri16 = (uint32_t)((uint32_t)b16 * (uint32_t)b16) / 65536; uint8_t bri8 = (uint32_t)(((uint32_t)bri16) * brightdepth) / 65536; @@ -2182,7 +2182,7 @@ static const char _data_FX_MODE_COLORWAVES[] PROGMEM = "Colorwaves@!,Hue;!;!"; // colored stripes pulsing at a defined Beats-Per-Minute (BPM) uint16_t mode_bpm() { uint32_t stp = (strip.now / 20) & 0xFF; - uint8_t beat = beatsin8(SEGMENT.speed, 64, 255); + uint8_t beat = beatsin8_t(SEGMENT.speed, 64, 255); for (int i = 0; i < SEGLEN; i++) { SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(stp + (i * 2), false, PALETTE_SOLID_WRAP, 0, beat - stp + (i * 10))); } @@ -2198,7 +2198,7 @@ uint16_t mode_fillnoise8() { unsigned index = inoise8(i * SEGLEN, SEGENV.step + i * SEGLEN); SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(index, false, PALETTE_SOLID_WRAP, 0)); } - SEGENV.step += beatsin8(SEGMENT.speed, 1, 6); //10,1,4 + SEGENV.step += beatsin8_t(SEGMENT.speed, 1, 6); //10,1,4 return FRAMETIME; } @@ -2210,13 +2210,13 @@ uint16_t mode_noise16_1() { SEGENV.step += (1 + SEGMENT.speed/16); for (int i = 0; i < SEGLEN; i++) { - unsigned shift_x = beatsin8(11); // the x position of the noise field swings @ 17 bpm + unsigned shift_x = beatsin8_t(11); // the x position of the noise field swings @ 17 bpm unsigned shift_y = SEGENV.step/42; // the y position becomes slowly incremented unsigned real_x = (i + shift_x) * scale; // the x position of the noise field swings @ 17 bpm unsigned real_y = (i + shift_y) * scale; // the y position becomes slowly incremented uint32_t real_z = SEGENV.step; // the z position becomes quickly incremented unsigned noise = inoise16(real_x, real_y, real_z) >> 8; // get the noise data and scale it down - unsigned index = sin8(noise * 3); // map LED color based on noise data + unsigned index = sin8_t(noise * 3); // map LED color based on noise data SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(index, false, PALETTE_SOLID_WRAP, 0)); } @@ -2234,7 +2234,7 @@ uint16_t mode_noise16_2() { unsigned shift_x = SEGENV.step >> 6; // x as a function of time uint32_t real_x = (i + shift_x) * scale; // calculate the coordinates within the noise field unsigned noise = inoise16(real_x, 0, 4223) >> 8; // get the noise data and scale it down - unsigned index = sin8(noise * 3); // map led color based on noise data + unsigned index = sin8_t(noise * 3); // map led color based on noise data SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(index, false, PALETTE_SOLID_WRAP, 0, noise)); } @@ -2255,7 +2255,7 @@ uint16_t mode_noise16_3() { uint32_t real_y = (i + shift_y) * scale; // based on the precalculated positions uint32_t real_z = SEGENV.step*8; unsigned noise = inoise16(real_x, real_y, real_z) >> 8; // get the noise data and scale it down - unsigned index = sin8(noise * 3); // map led color based on noise data + unsigned index = sin8_t(noise * 3); // map led color based on noise data SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(index, false, PALETTE_SOLID_WRAP, 0, noise)); } @@ -2335,13 +2335,13 @@ static const char _data_FX_MODE_COLORTWINKLE[] PROGMEM = "Colortwinkles@Fade spe //Calm effect, like a lake at night uint16_t mode_lake() { unsigned sp = SEGMENT.speed/10; - int wave1 = beatsin8(sp +2, -64,64); - int wave2 = beatsin8(sp +1, -64,64); - int wave3 = beatsin8(sp +2, 0,80); + int wave1 = beatsin8_t(sp +2, -64,64); + int wave2 = beatsin8_t(sp +1, -64,64); + int wave3 = beatsin8_t(sp +2, 0,80); for (int i = 0; i < SEGLEN; i++) { - int index = cos8((i*15)+ wave1)/2 + cubicwave8((i*23)+ wave2)/2; + int index = cos8_t((i*15)+ wave1)/2 + cubicwave8((i*23)+ wave2)/2; uint8_t lum = (index > wave3) ? index - wave3 : 0; SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(index, false, false, 0, lum)); } @@ -2514,7 +2514,7 @@ static uint16_t ripple_base() { propI /= 2; unsigned cx = rippleorigin >> 8; unsigned cy = rippleorigin & 0xFF; - unsigned mag = scale8(sin8((propF>>2)), amp); + unsigned mag = scale8(sin8_t((propF>>2)), amp); if (propI > 0) SEGMENT.drawCircle(cx, cy, propI, color_blend(SEGMENT.getPixelColorXY(cx + propI, cy), col, mag), true); } else #endif @@ -2581,7 +2581,7 @@ static CRGB twinklefox_one_twinkle(uint32_t ms, uint8_t salt, bool cat) unsigned ticks = ms / SEGENV.aux0; unsigned fastcycle8 = ticks; unsigned slowcycle16 = (ticks >> 8) + salt; - slowcycle16 += sin8(slowcycle16); + slowcycle16 += sin8_t(slowcycle16); slowcycle16 = (slowcycle16 * 2053) + 1384; unsigned slowcycle8 = (slowcycle16 & 0xFF) + (slowcycle16 >> 8); @@ -3152,7 +3152,7 @@ static const char _data_FX_MODE_ROLLINGBALLS[] PROGMEM = "Rolling Balls@!,# of b static uint16_t sinelon_base(bool dual, bool rainbow=false) { if (SEGLEN == 1) return mode_static(); SEGMENT.fade_out(SEGMENT.intensity); - unsigned pos = beatsin16(SEGMENT.speed/10,0,SEGLEN-1); + unsigned pos = beatsin16_t(SEGMENT.speed/10,0,SEGLEN-1); if (SEGENV.call == 0) SEGENV.aux0 = pos; uint32_t color1 = SEGMENT.color_from_palette(pos, true, false, 0); uint32_t color2 = SEGCOLOR(2); @@ -3868,13 +3868,13 @@ uint16_t mode_plasma(void) { if (SEGENV.call == 0) { SEGENV.aux0 = random8(0,2); // add a bit of randomness } - unsigned thisPhase = beatsin8(6+SEGENV.aux0,-64,64); - unsigned thatPhase = beatsin8(7+SEGENV.aux0,-64,64); + unsigned thisPhase = beatsin8_t(6+SEGENV.aux0,-64,64); + unsigned thatPhase = beatsin8_t(7+SEGENV.aux0,-64,64); for (unsigned i = 0; i < SEGLEN; i++) { // For each of the LED's in the strand, set color & brightness based on a wave as follows: unsigned colorIndex = cubicwave8((i*(2+ 3*(SEGMENT.speed >> 5))+thisPhase) & 0xFF)/2 // factor=23 // Create a wave and add a phase change and add another wave with its own phase change. - + cos8((i*(1+ 2*(SEGMENT.speed >> 5))+thatPhase) & 0xFF)/2; // factor=15 // Hey, you can even change the frequencies if you wish. - unsigned thisBright = qsub8(colorIndex, beatsin8(7,0, (128 - (SEGMENT.intensity>>1)))); + + cos8_t((i*(1+ 2*(SEGMENT.speed >> 5))+thatPhase) & 0xFF)/2; // factor=15 // Hey, you can even change the frequencies if you wish. + unsigned thisBright = qsub8(colorIndex, beatsin8_t(7,0, (128 - (SEGMENT.intensity>>1)))); SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(colorIndex, false, PALETTE_SOLID_WRAP, 0, thisBright)); } @@ -4001,10 +4001,10 @@ static CRGB pacifica_one_layer(uint16_t i, CRGBPalette16& p, uint16_t cistart, u unsigned wavescale_half = (wavescale >> 1) + 20; waveangle += ((120 + SEGMENT.intensity) * i); //original 250 * i - unsigned s16 = sin16(waveangle) + 32768; + unsigned s16 = sin16_t(waveangle) + 32768; unsigned cs = scale16(s16, wavescale_half) + wavescale_half; ci += (cs * i); - unsigned sindex16 = sin16(ci) + 32768; + unsigned sindex16 = sin16_t(ci) + 32768; unsigned sindex8 = scale16(sindex16, 240); return ColorFromPalette(p, sindex8, bri, LINEARBLEND); } @@ -4036,34 +4036,34 @@ uint16_t mode_pacifica() uint64_t deltat = (strip.now >> 2) + ((strip.now * SEGMENT.speed) >> 7); strip.now = deltat; - unsigned speedfactor1 = beatsin16(3, 179, 269); - unsigned speedfactor2 = beatsin16(4, 179, 269); + unsigned speedfactor1 = beatsin16_t(3, 179, 269); + unsigned speedfactor2 = beatsin16_t(4, 179, 269); uint32_t deltams1 = (deltams * speedfactor1) / 256; uint32_t deltams2 = (deltams * speedfactor2) / 256; uint32_t deltams21 = (deltams1 + deltams2) / 2; - sCIStart1 += (deltams1 * beatsin88(1011,10,13)); - sCIStart2 -= (deltams21 * beatsin88(777,8,11)); - sCIStart3 -= (deltams1 * beatsin88(501,5,7)); - sCIStart4 -= (deltams2 * beatsin88(257,4,6)); + sCIStart1 += (deltams1 * beatsin88_t(1011,10,13)); + sCIStart2 -= (deltams21 * beatsin88_t(777,8,11)); + sCIStart3 -= (deltams1 * beatsin88_t(501,5,7)); + sCIStart4 -= (deltams2 * beatsin88_t(257,4,6)); SEGENV.aux0 = sCIStart1; SEGENV.aux1 = sCIStart2; SEGENV.step = sCIStart4; SEGENV.step = (SEGENV.step << 16) + sCIStart3; // Clear out the LED array to a dim background blue-green //SEGMENT.fill(132618); - unsigned basethreshold = beatsin8( 9, 55, 65); + unsigned basethreshold = beatsin8_t( 9, 55, 65); unsigned wave = beat8( 7 ); for (int i = 0; i < SEGLEN; i++) { CRGB c = CRGB(2, 6, 10); // Render each of four layers, with different scales and speeds, that vary over time - c += pacifica_one_layer(i, pacifica_palette_1, sCIStart1, beatsin16(3, 11 * 256, 14 * 256), beatsin8(10, 70, 130), 0-beat16(301)); - c += pacifica_one_layer(i, pacifica_palette_2, sCIStart2, beatsin16(4, 6 * 256, 9 * 256), beatsin8(17, 40, 80), beat16(401)); - c += pacifica_one_layer(i, pacifica_palette_3, sCIStart3, 6 * 256 , beatsin8(9, 10,38) , 0-beat16(503)); - c += pacifica_one_layer(i, pacifica_palette_3, sCIStart4, 5 * 256 , beatsin8(8, 10,28) , beat16(601)); + c += pacifica_one_layer(i, pacifica_palette_1, sCIStart1, beatsin16_t(3, 11 * 256, 14 * 256), beatsin8_t(10, 70, 130), 0-beat16(301)); + c += pacifica_one_layer(i, pacifica_palette_2, sCIStart2, beatsin16_t(4, 6 * 256, 9 * 256), beatsin8_t(17, 40, 80), beat16(401)); + c += pacifica_one_layer(i, pacifica_palette_3, sCIStart3, 6 * 256 , beatsin8_t(9, 10,38) , 0-beat16(503)); + c += pacifica_one_layer(i, pacifica_palette_3, sCIStart4, 5 * 256 , beatsin8_t(8, 10,28) , beat16(601)); // Add extra 'white' to areas where the four layers of light have lined up brightly - unsigned threshold = scale8( sin8( wave), 20) + basethreshold; + unsigned threshold = scale8( sin8_t( wave), 20) + basethreshold; wave += 7; unsigned l = c.getAverageLight(); if (l > threshold) { @@ -4187,7 +4187,7 @@ uint16_t mode_twinkleup(void) { // A very short twinkle routine for (int i = 0; i < SEGLEN; i++) { unsigned ranstart = random8(); // The starting value (aka brightness) for each pixel. Must be consistent each time through the loop for this to work. - unsigned pixBri = sin8(ranstart + 16 * strip.now/(256-SEGMENT.speed)); + unsigned pixBri = sin8_t(ranstart + 16 * strip.now/(256-SEGMENT.speed)); if (random8() > SEGMENT.intensity) pixBri = 0; SEGMENT.setPixelColor(i, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(random8()+strip.now/100, false, PALETTE_SOLID_WRAP, 0), pixBri)); } @@ -4230,7 +4230,7 @@ uint16_t mode_noisepal(void) { // Slow noise SEGMENT.setPixelColor(i, color.red, color.green, color.blue); } - SEGENV.aux0 += beatsin8(10,1,4); // Moving along the distance. Vary it a bit with a sine wave. + SEGENV.aux0 += beatsin8_t(10,1,4); // Moving along the distance. Vary it a bit with a sine wave. return FRAMETIME; } @@ -4312,7 +4312,7 @@ uint16_t mode_chunchun(void) for (unsigned i = 0; i < numBirds; i++) { counter -= span; - unsigned megumin = sin16(counter) + 0x8000; + unsigned megumin = sin16_t(counter) + 0x8000; unsigned bird = uint32_t(megumin * SEGLEN) >> 16; uint32_t c = SEGMENT.color_from_palette((i * 255)/ numBirds, false, false, 0); // no palette wrapping bird = constrain(bird, 0U, SEGLEN-1U); @@ -4481,7 +4481,7 @@ uint16_t mode_washing_machine(void) { SEGENV.step += (speed * 2048) / (512 - SEGMENT.speed); for (int i = 0; i < SEGLEN; i++) { - uint8_t col = sin8(((SEGMENT.intensity / 25 + 1) * 255 * i / SEGLEN) + (SEGENV.step >> 7)); + uint8_t col = sin8_t(((SEGMENT.intensity / 25 + 1) * 255 * i / SEGLEN) + (SEGENV.step >> 7)); SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(col, false, PALETTE_SOLID_WRAP, 3)); } @@ -4837,8 +4837,8 @@ static const char _data_FX_MODE_PERLINMOVE[] PROGMEM = "Perlin Move@!,# of pixel uint16_t mode_wavesins(void) { for (int i = 0; i < SEGLEN; i++) { - uint8_t bri = sin8(strip.now/4 + i * SEGMENT.intensity); - uint8_t index = beatsin8(SEGMENT.speed, SEGMENT.custom1, SEGMENT.custom1+SEGMENT.custom2, 0, i * (SEGMENT.custom3<<3)); // custom3 is reduced resolution slider + uint8_t bri = sin8_t(strip.now/4 + i * SEGMENT.intensity); + uint8_t index = beatsin8_t(SEGMENT.speed, SEGMENT.custom1, SEGMENT.custom1+SEGMENT.custom2, 0, i * (SEGMENT.custom3<<3)); // custom3 is reduced resolution slider //SEGMENT.setPixelColor(i, ColorFromPalette(SEGPALETTE, index, bri, LINEARBLEND)); SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(index, false, PALETTE_SOLID_WRAP, 0, bri)); } @@ -4860,9 +4860,9 @@ uint16_t mode_FlowStripe(void) { for (int i = 0; i < SEGLEN; i++) { int c = (abs(i - hl) / hl) * 127; - c = sin8(c); - c = sin8(c / 2 + t); - byte b = sin8(c + t/8); + c = sin8_t(c); + c = sin8_t(c / 2 + t); + byte b = sin8_t(c + t/8); SEGMENT.setPixelColor(i, CHSV(b + hue, 255, 255)); } @@ -4889,14 +4889,14 @@ uint16_t mode_2DBlackHole(void) { // By: Stepko https://editor.soulma unsigned long t = strip.now/128; // timebase // outer stars for (size_t i = 0; i < 8; i++) { - x = beatsin8(SEGMENT.custom1>>3, 0, cols - 1, 0, ((i % 2) ? 128 : 0) + t * i); - y = beatsin8(SEGMENT.intensity>>3, 0, rows - 1, 0, ((i % 2) ? 192 : 64) + t * i); + x = beatsin8_t(SEGMENT.custom1>>3, 0, cols - 1, 0, ((i % 2) ? 128 : 0) + t * i); + y = beatsin8_t(SEGMENT.intensity>>3, 0, rows - 1, 0, ((i % 2) ? 192 : 64) + t * i); SEGMENT.addPixelColorXY(x, y, SEGMENT.color_from_palette(i*32, false, PALETTE_SOLID_WRAP, SEGMENT.check1?0:255)); } // inner stars for (size_t i = 0; i < 4; i++) { - x = beatsin8(SEGMENT.custom2>>3, cols/4, cols - 1 - cols/4, 0, ((i % 2) ? 128 : 0) + t * i); - y = beatsin8(SEGMENT.custom3 , rows/4, rows - 1 - rows/4, 0, ((i % 2) ? 192 : 64) + t * i); + x = beatsin8_t(SEGMENT.custom2>>3, cols/4, cols - 1 - cols/4, 0, ((i % 2) ? 128 : 0) + t * i); + y = beatsin8_t(SEGMENT.custom3 , rows/4, rows - 1 - rows/4, 0, ((i % 2) ? 192 : 64) + t * i); SEGMENT.addPixelColorXY(x, y, SEGMENT.color_from_palette(255-i*64, false, PALETTE_SOLID_WRAP, SEGMENT.check1?0:255)); } // central white dot @@ -4930,10 +4930,10 @@ uint16_t mode_2DColoredBursts() { // By: ldirko https://editor.so SEGENV.aux0++; // hue SEGMENT.fadeToBlackBy(40); for (size_t i = 0; i < numLines; i++) { - byte x1 = beatsin8(2 + SEGMENT.speed/16, 0, (cols - 1)); - byte x2 = beatsin8(1 + SEGMENT.speed/16, 0, (rows - 1)); - byte y1 = beatsin8(5 + SEGMENT.speed/16, 0, (cols - 1), 0, i * 24); - byte y2 = beatsin8(3 + SEGMENT.speed/16, 0, (rows - 1), 0, i * 48 + 64); + byte x1 = beatsin8_t(2 + SEGMENT.speed/16, 0, (cols - 1)); + byte x2 = beatsin8_t(1 + SEGMENT.speed/16, 0, (rows - 1)); + byte y1 = beatsin8_t(5 + SEGMENT.speed/16, 0, (cols - 1), 0, i * 24); + byte y2 = beatsin8_t(3 + SEGMENT.speed/16, 0, (rows - 1), 0, i * 48 + 64); CRGB color = ColorFromPalette(SEGPALETTE, i * 255 / numLines + (SEGENV.aux0&0xFF), 255, LINEARBLEND); byte xsteps = abs8(x1 - y1) + 1; @@ -4972,8 +4972,8 @@ uint16_t mode_2Ddna(void) { // dna originally by by ldirko at https://pa SEGMENT.fadeToBlackBy(64); for (int i = 0; i < cols; i++) { - SEGMENT.setPixelColorXY(i, beatsin8(SEGMENT.speed/8, 0, rows-1, 0, i*4 ), ColorFromPalette(SEGPALETTE, i*5+strip.now/17, beatsin8(5, 55, 255, 0, i*10), LINEARBLEND)); - SEGMENT.setPixelColorXY(i, beatsin8(SEGMENT.speed/8, 0, rows-1, 0, i*4+128), ColorFromPalette(SEGPALETTE, i*5+128+strip.now/17, beatsin8(5, 55, 255, 0, i*10+128), LINEARBLEND)); + SEGMENT.setPixelColorXY(i, beatsin8_t(SEGMENT.speed/8, 0, rows-1, 0, i*4 ), ColorFromPalette(SEGPALETTE, i*5+strip.now/17, beatsin8_t(5, 55, 255, 0, i*10), LINEARBLEND)); + SEGMENT.setPixelColorXY(i, beatsin8_t(SEGMENT.speed/8, 0, rows-1, 0, i*4+128), ColorFromPalette(SEGPALETTE, i*5+128+strip.now/17, beatsin8_t(5, 55, 255, 0, i*10+128), LINEARBLEND)); } SEGMENT.blur(SEGMENT.intensity>>3); @@ -5002,8 +5002,8 @@ uint16_t mode_2DDNASpiral() { // By: ldirko https://editor.soulma SEGMENT.fadeToBlackBy(135); for (int i = 0; i < rows; i++) { - int x = beatsin8(speeds, 0, cols - 1, 0, i * freq) + beatsin8(speeds - 7, 0, cols - 1, 0, i * freq + 128); - int x1 = beatsin8(speeds, 0, cols - 1, 0, 128 + i * freq) + beatsin8(speeds - 7, 0, cols - 1, 0, 128 + 64 + i * freq); + int x = beatsin8_t(speeds, 0, cols - 1, 0, i * freq) + beatsin8_t(speeds - 7, 0, cols - 1, 0, i * freq + 128); + int x1 = beatsin8_t(speeds, 0, cols - 1, 0, 128 + i * freq) + beatsin8_t(speeds - 7, 0, cols - 1, 0, 128 + 64 + i * freq); unsigned hue = (i * 128 / rows) + ms; // skip every 4th row every now and then (fade it more) if ((i + ms / 8) & 3) { @@ -5104,9 +5104,9 @@ uint16_t mode_2DFrizzles(void) { // By: Stepko https://editor.so SEGMENT.fadeToBlackBy(16); for (size_t i = 8; i > 0; i--) { - SEGMENT.addPixelColorXY(beatsin8(SEGMENT.speed/8 + i, 0, cols - 1), - beatsin8(SEGMENT.intensity/8 - i, 0, rows - 1), - ColorFromPalette(SEGPALETTE, beatsin8(12, 0, 255), 255, LINEARBLEND)); + SEGMENT.addPixelColorXY(beatsin8_t(SEGMENT.speed/8 + i, 0, cols - 1), + beatsin8_t(SEGMENT.intensity/8 - i, 0, rows - 1), + ColorFromPalette(SEGPALETTE, beatsin8_t(12, 0, 255), 255, LINEARBLEND)); } SEGMENT.blur(SEGMENT.custom1>>3); @@ -5238,7 +5238,7 @@ uint16_t mode_2DHiphotic() { // By: ldirko https://edit for (int x = 0; x < cols; x++) { for (int y = 0; y < rows; y++) { - SEGMENT.setPixelColorXY(x, y, SEGMENT.color_from_palette(sin8(cos8(x * SEGMENT.speed/16 + a / 3) + sin8(y * SEGMENT.intensity/16 + a / 4) + a), false, PALETTE_SOLID_WRAP, 0)); + SEGMENT.setPixelColorXY(x, y, SEGMENT.color_from_palette(sin8_t(cos8_t(x * SEGMENT.speed/16 + a / 3) + sin8_t(y * SEGMENT.intensity/16 + a / 4) + a), false, PALETTE_SOLID_WRAP, 0)); } } @@ -5378,10 +5378,10 @@ uint16_t mode_2DLissajous(void) { // By: Andrew Tuline //for (int i=0; i < 4*(cols+rows); i ++) { for (int i=0; i < 256; i ++) { - //float xlocn = float(sin8(now/4+i*(SEGMENT.speed>>5))) / 255.0f; - //float ylocn = float(cos8(now/4+i*2)) / 255.0f; - uint_fast8_t xlocn = sin8(phase/2 + (i*SEGMENT.speed)/32); - uint_fast8_t ylocn = cos8(phase/2 + i*2); + //float xlocn = float(sin8_t(now/4+i*(SEGMENT.speed>>5))) / 255.0f; + //float ylocn = float(cos8_t(now/4+i*2)) / 255.0f; + uint_fast8_t xlocn = sin8_t(phase/2 + (i*SEGMENT.speed)/32); + uint_fast8_t ylocn = cos8_t(phase/2 + i*2); xlocn = (cols < 2) ? 1 : (map(2*xlocn, 0,511, 0,2*(cols-1)) +1) /2; // softhack007: "(2* ..... +1) /2" for proper rounding ylocn = (rows < 2) ? 1 : (map(2*ylocn, 0,511, 0,2*(rows-1)) +1) /2; // "rows > 1" is needed to avoid div/0 in map() SEGMENT.setPixelColorXY((uint8_t)xlocn, (uint8_t)ylocn, SEGMENT.color_from_palette(strip.now/100+i, false, PALETTE_SOLID_WRAP, 0)); @@ -5481,8 +5481,8 @@ uint16_t mode_2Dmetaballs(void) { // Metaballs by Stefan Petrick. Cannot have int y3 = map(inoise8(strip.now * speed, 25355, 22685), 0, 255, 0, rows-1); // and one Lissajou function - int x1 = beatsin8(23 * speed, 0, cols-1); - int y1 = beatsin8(28 * speed, 0, rows-1); + int x1 = beatsin8_t(23 * speed, 0, cols-1); + int y1 = beatsin8_t(28 * speed, 0, rows-1); for (int y = 0; y < rows; y++) { for (int x = 0; x < cols; x++) { @@ -5647,7 +5647,7 @@ uint16_t mode_2DPulser(void) { // By: ldirko https://edi SEGMENT.fadeToBlackBy(8 - (SEGMENT.intensity>>5)); uint32_t a = strip.now / (18 - SEGMENT.speed / 16); int x = (a / 14) % cols; - int y = map((sin8(a * 5) + sin8(a * 4) + sin8(a * 2)), 0, 765, rows-1, 0); + int y = map((sin8_t(a * 5) + sin8_t(a * 4) + sin8_t(a * 2)), 0, 765, rows-1, 0); SEGMENT.setPixelColorXY(x, y, ColorFromPalette(SEGPALETTE, map(y, 0, rows-1, 0, 255), 255, LINEARBLEND)); SEGMENT.blur(SEGMENT.intensity>>4); @@ -5673,10 +5673,10 @@ uint16_t mode_2DSindots(void) { // By: ldirko http SEGMENT.fadeToBlackBy(SEGMENT.custom1>>3); byte t1 = strip.now / (257 - SEGMENT.speed); // 20; - byte t2 = sin8(t1) / 4 * 2; + byte t2 = sin8_t(t1) / 4 * 2; for (int i = 0; i < 13; i++) { - int x = sin8(t1 + i * SEGMENT.intensity/8)*(cols-1)/255; // max index now 255x15/255=15! - int y = sin8(t2 + i * SEGMENT.intensity/8)*(rows-1)/255; // max index now 255x15/255=15! + int x = sin8_t(t1 + i * SEGMENT.intensity/8)*(cols-1)/255; // max index now 255x15/255=15! + int y = sin8_t(t2 + i * SEGMENT.intensity/8)*(rows-1)/255; // max index now 255x15/255=15! SEGMENT.setPixelColorXY(x, y, ColorFromPalette(SEGPALETTE, i * 255 / 13, 255, LINEARBLEND)); } SEGMENT.blur(SEGMENT.custom2>>3); @@ -5703,12 +5703,12 @@ uint16_t mode_2Dsquaredswirl(void) { // By: Mark Kriegsman. https://g SEGMENT.blur(SEGMENT.custom3>>1); // Use two out-of-sync sine waves - int i = beatsin8(19, kBorderWidth, cols-kBorderWidth); - int j = beatsin8(22, kBorderWidth, cols-kBorderWidth); - int k = beatsin8(17, kBorderWidth, cols-kBorderWidth); - int m = beatsin8(18, kBorderWidth, rows-kBorderWidth); - int n = beatsin8(15, kBorderWidth, rows-kBorderWidth); - int p = beatsin8(20, kBorderWidth, rows-kBorderWidth); + int i = beatsin8_t(19, kBorderWidth, cols-kBorderWidth); + int j = beatsin8_t(22, kBorderWidth, cols-kBorderWidth); + int k = beatsin8_t(17, kBorderWidth, cols-kBorderWidth); + int m = beatsin8_t(18, kBorderWidth, rows-kBorderWidth); + int n = beatsin8_t(15, kBorderWidth, rows-kBorderWidth); + int p = beatsin8_t(20, kBorderWidth, rows-kBorderWidth); SEGMENT.addPixelColorXY(i, m, ColorFromPalette(SEGPALETTE, strip.now/29, 255, LINEARBLEND)); SEGMENT.addPixelColorXY(j, n, ColorFromPalette(SEGPALETTE, strip.now/41, 255, LINEARBLEND)); @@ -5784,19 +5784,19 @@ uint16_t mode_2Dtartan(void) { // By: Elliott Kember https://editor.so uint8_t hue, bri; size_t intensity; - int offsetX = beatsin16(3, -360, 360); - int offsetY = beatsin16(2, -360, 360); + int offsetX = beatsin16_t(3, -360, 360); + int offsetY = beatsin16_t(2, -360, 360); int sharpness = SEGMENT.custom3 / 8; // 0-3 for (int x = 0; x < cols; x++) { for (int y = 0; y < rows; y++) { - hue = x * beatsin16(10, 1, 10) + offsetY; - intensity = bri = sin8(x * SEGMENT.speed/2 + offsetX); + hue = x * beatsin16_t(10, 1, 10) + offsetY; + intensity = bri = sin8_t(x * SEGMENT.speed/2 + offsetX); for (int i=0; i>= 8*sharpness; SEGMENT.setPixelColorXY(x, y, ColorFromPalette(SEGPALETTE, hue, intensity, LINEARBLEND)); hue = y * 3 + offsetX; - intensity = bri = sin8(y * SEGMENT.intensity/2 + offsetY); + intensity = bri = sin8_t(y * SEGMENT.intensity/2 + offsetY); for (int i=0; i>= 8*sharpness; SEGMENT.addPixelColorXY(x, y, ColorFromPalette(SEGPALETTE, hue, intensity, LINEARBLEND)); @@ -5831,9 +5831,9 @@ uint16_t mode_2Dspaceships(void) { //// Space ships by stepko (c)05.02.21 [ht SEGMENT.move(SEGENV.aux0, 1); for (size_t i = 0; i < 8; i++) { - int x = beatsin8(12 + i, 2, cols - 3); - int y = beatsin8(15 + i, 2, rows - 3); - CRGB color = ColorFromPalette(SEGPALETTE, beatsin8(12 + i, 0, 255), 255); + int x = beatsin8_t(12 + i, 2, cols - 3); + int y = beatsin8_t(15 + i, 2, rows - 3); + CRGB color = ColorFromPalette(SEGPALETTE, beatsin8_t(12 + i, 0, 255), 255); SEGMENT.addPixelColorXY(x, y, color); if (cols > 24 || rows > 24) { SEGMENT.addPixelColorXY(x+1, y, color); @@ -6229,8 +6229,8 @@ uint16_t mode_2Ddriftrose(void) { SEGMENT.fadeToBlackBy(32+(SEGMENT.speed>>3)); for (size_t i = 1; i < 37; i++) { float angle = radians(i * 10); - uint32_t x = (CX + (sin_t(angle) * (beatsin8(i, 0, L*2)-L))) * 255.f; - uint32_t y = (CY + (cos_t(angle) * (beatsin8(i, 0, L*2)-L))) * 255.f; + uint32_t x = (CX + (sin_t(angle) * (beatsin8_t(i, 0, L*2)-L))) * 255.f; + uint32_t y = (CY + (cos_t(angle) * (beatsin8_t(i, 0, L*2)-L))) * 255.f; SEGMENT.wu_pixel(x, y, CHSV(i * 10, 255, 255)); } SEGMENT.blur(SEGMENT.intensity>>4); @@ -6426,8 +6426,8 @@ uint16_t mode_2DSwirl(void) { SEGMENT.blur(SEGMENT.custom1); - int i = beatsin8( 27*SEGMENT.speed/255, borderWidth, cols - borderWidth); - int j = beatsin8( 41*SEGMENT.speed/255, borderWidth, rows - borderWidth); + int i = beatsin8_t( 27*SEGMENT.speed/255, borderWidth, cols - borderWidth); + int j = beatsin8_t( 41*SEGMENT.speed/255, borderWidth, rows - borderWidth); int ni = (cols - 1) - i; int nj = (cols - 1) - j; @@ -6639,7 +6639,7 @@ uint16_t mode_juggles(void) { // Juggles. By Andrew Tuline. for (size_t i=0; ithisphase += beatsin8(6,-4,4); // You can change direction and speed individually. - plasmoip->thatphase += beatsin8(7,-4,4); // Two phase values to make a complex pattern. By Andrew Tuline. + plasmoip->thisphase += beatsin8_t(6,-4,4); // You can change direction and speed individually. + plasmoip->thatphase += beatsin8_t(7,-4,4); // Two phase values to make a complex pattern. By Andrew Tuline. for (int i = 0; i < SEGLEN; i++) { // For each of the LED's in the strand, set a brightness based on a wave as follows. // updated, similar to "plasma" effect - softhack007 uint8_t thisbright = cubicwave8(((i*(1 + (3*SEGMENT.speed/32)))+plasmoip->thisphase) & 0xFF)/2; - thisbright += cos8(((i*(97 +(5*SEGMENT.speed/32)))+plasmoip->thatphase) & 0xFF)/2; // Let's munge the brightness a bit and animate it all with the phases. + thisbright += cos8_t(((i*(97 +(5*SEGMENT.speed/32)))+plasmoip->thatphase) & 0xFF)/2; // Let's munge the brightness a bit and animate it all with the phases. uint8_t colorIndex=thisbright; if (volumeSmth * SEGMENT.intensity / 64 < thisbright) {thisbright = 0;} @@ -7280,7 +7280,7 @@ uint16_t mode_rocktaves(void) { // Rocktaves. Same note from eac frTemp -= 132.0f; // This should give us a base musical note of C3 frTemp = fabsf(frTemp * 2.1f); // Fudge factors to compress octave range starting at 0 and going to 255; - unsigned i = map(beatsin8(8+octCount*4, 0, 255, 0, octCount*8), 0, 255, 0, SEGLEN-1); + unsigned i = map(beatsin8_t(8+octCount*4, 0, 255, 0, octCount*8), 0, 255, 0, SEGLEN-1); i = constrain(i, 0U, SEGLEN-1U); SEGMENT.addPixelColor(i, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette((uint8_t)frTemp, false, PALETTE_SOLID_WRAP, 0), volTemp)); @@ -7567,12 +7567,12 @@ uint16_t mode_2Ddistortionwaves() { unsigned a2 = a/2; unsigned a3 = a/3; - unsigned cx = beatsin8(10-speed,0,cols-1)*scale; - unsigned cy = beatsin8(12-speed,0,rows-1)*scale; - unsigned cx1 = beatsin8(13-speed,0,cols-1)*scale; - unsigned cy1 = beatsin8(15-speed,0,rows-1)*scale; - unsigned cx2 = beatsin8(17-speed,0,cols-1)*scale; - unsigned cy2 = beatsin8(14-speed,0,rows-1)*scale; + unsigned cx = beatsin8_t(10-speed,0,cols-1)*scale; + unsigned cy = beatsin8_t(12-speed,0,rows-1)*scale; + unsigned cx1 = beatsin8_t(13-speed,0,cols-1)*scale; + unsigned cy1 = beatsin8_t(15-speed,0,rows-1)*scale; + unsigned cx2 = beatsin8_t(17-speed,0,cols-1)*scale; + unsigned cy2 = beatsin8_t(14-speed,0,rows-1)*scale; unsigned xoffs = 0; for (int x = 0; x < cols; x++) { @@ -7582,17 +7582,17 @@ uint16_t mode_2Ddistortionwaves() { for (int y = 0; y < rows; y++) { yoffs += scale; - byte rdistort = cos8((cos8(((x<<3)+a )&255)+cos8(((y<<3)-a2)&255)+a3 )&255)>>1; - byte gdistort = cos8((cos8(((x<<3)-a2)&255)+cos8(((y<<3)+a3)&255)+a+32 )&255)>>1; - byte bdistort = cos8((cos8(((x<<3)+a3)&255)+cos8(((y<<3)-a) &255)+a2+64)&255)>>1; + byte rdistort = cos8_t((cos8_t(((x<<3)+a )&255)+cos8_t(((y<<3)-a2)&255)+a3 )&255)>>1; + byte gdistort = cos8_t((cos8_t(((x<<3)-a2)&255)+cos8_t(((y<<3)+a3)&255)+a+32 )&255)>>1; + byte bdistort = cos8_t((cos8_t(((x<<3)+a3)&255)+cos8_t(((y<<3)-a) &255)+a2+64)&255)>>1; byte valueR = rdistort+ w* (a- ( ((xoffs - cx) * (xoffs - cx) + (yoffs - cy) * (yoffs - cy))>>7 )); byte valueG = gdistort+ w* (a2-( ((xoffs - cx1) * (xoffs - cx1) + (yoffs - cy1) * (yoffs - cy1))>>7 )); byte valueB = bdistort+ w* (a3-( ((xoffs - cx2) * (xoffs - cx2) + (yoffs - cy2) * (yoffs - cy2))>>7 )); - valueR = gamma8(cos8(valueR)); - valueG = gamma8(cos8(valueG)); - valueB = gamma8(cos8(valueB)); + valueR = gamma8(cos8_t(valueR)); + valueG = gamma8(cos8_t(valueG)); + valueB = gamma8(cos8_t(valueB)); SEGMENT.setPixelColorXY(x, y, RGBW32(valueR, valueG, valueB, 0)); } @@ -7749,7 +7749,7 @@ uint16_t mode_2Doctopus() { const int C_Y = (rows / 2) + ((SEGMENT.custom2 - 128)*rows)/255; for (int x = 0; x < cols; x++) { for (int y = 0; y < rows; y++) { - rMap[XY(x, y)].angle = 40.7436f * atan2f((y - C_Y), (x - C_X)); // avoid 128*atan2()/PI + rMap[XY(x, y)].angle = 40.7436f * atan2_t((y - C_Y), (x - C_X)); // avoid 128*atan2()/PI rMap[XY(x, y)].radius = hypotf((x - C_X), (y - C_Y)) * mapp; //thanks Sutaburosu } } @@ -7760,8 +7760,8 @@ uint16_t mode_2Doctopus() { for (int y = 0; y < rows; y++) { byte angle = rMap[XY(x,y)].angle; byte radius = rMap[XY(x,y)].radius; - //CRGB c = CHSV(SEGENV.step / 2 - radius, 255, sin8(sin8((angle * 4 - radius) / 4 + SEGENV.step) + radius - SEGENV.step * 2 + angle * (SEGMENT.custom3/3+1))); - unsigned intensity = sin8(sin8((angle * 4 - radius) / 4 + SEGENV.step/2) + radius - SEGENV.step + angle * (SEGMENT.custom3/4+1)); + //CRGB c = CHSV(SEGENV.step / 2 - radius, 255, sin8_t(sin8_t((angle * 4 - radius) / 4 + SEGENV.step) + radius - SEGENV.step * 2 + angle * (SEGMENT.custom3/3+1))); + unsigned intensity = sin8_t(sin8_t((angle * 4 - radius) / 4 + SEGENV.step/2) + radius - SEGENV.step + angle * (SEGMENT.custom3/4+1)); intensity = map((intensity*intensity) & 0xFFFF, 0, 65535, 0, 255); // add a bit of non-linearity for cleaner display CRGB c = ColorFromPalette(SEGPALETTE, SEGENV.step / 2 - radius, intensity); SEGMENT.setPixelColorXY(x, y, c); @@ -7769,7 +7769,7 @@ uint16_t mode_2Doctopus() { } return FRAMETIME; } -static const char _data_FX_MODE_2DOCTOPUS[] PROGMEM = "Octopus@!,,Offset X,Offset Y,Legs;;!;2;"; +static const char _data_FX_MODE_2DOCTOPUS[] PROGMEM = "Octopus@!,,Offset X,Offset Y,Legs,fasttan;;!;2;"; //Waving Cell @@ -7786,7 +7786,7 @@ uint16_t mode_2Dwavingcell() { uint8_t aY = SEGMENT.custom2/16 + 1; uint8_t aZ = SEGMENT.custom3 + 1; for (int x = 0; x < cols; x++) for (int y = 0; y T atan_t(T x); - float cos_t(float phi); - float sin_t(float x); - float tan_t(float x); - float acos_t(float x); - float asin_t(float x); - float floor_t(float x); - float fmod_t(float num, float denom); -#else - #include - #define sin_t sinf - #define cos_t cosf - #define tan_t tanf - #define asin_t asinf - #define acos_t acosf - #define atan_t atanf - #define fmod_t fmodf - #define floor_t floorf -#endif +//float cos_t(float phi); // use float math +//float sin_t(float phi); +//float tan_t(float x); +int16_t sin16_t(uint16_t theta); +int16_t cos16_t(uint16_t theta); +uint8_t sin8_t(uint8_t theta); +uint8_t cos8_t(uint8_t theta); +float sin_approx(float theta); // uses integer math (converted to float), accuracy +/-0.0015 (compared to sinf()) +float cos_approx(float theta); +float tan_approx(float x); +float atan2_t(float y, float x); +float acos_t(float x); +float asin_t(float x); +template T atan_t(T x); +float floor_t(float x); +float fmod_t(float num, float denom); +#define sin_t sin_approx +#define cos_t cos_approx +#define tan_t tan_approx +/* +#include // standard math functions. use a lot of flash +#define sin_t sinf +#define cos_t cosf +#define tan_t tanf +#define asin_t asinf +#define acos_t acosf +#define atan_t atanf +#define fmod_t fmodf +#define floor_t floorf +*/ //wled_serial.cpp void handleSerial(); void updateBaudRate(uint32_t rate); diff --git a/wled00/util.cpp b/wled00/util.cpp index 0b78a4646..c43fdeabf 100644 --- a/wled00/util.cpp +++ b/wled00/util.cpp @@ -372,6 +372,39 @@ uint16_t crc16(const unsigned char* data_p, size_t length) { return crc; } +// fastled beatsin: 1:1 replacements to remove the use of fastled sin16() +// Generates a 16-bit sine wave at a given BPM that oscillates within a given range. see fastled for details. +uint16_t beatsin88_t(accum88 beats_per_minute_88, uint16_t lowest, uint16_t highest, uint32_t timebase, uint16_t phase_offset) +{ + uint16_t beat = beat88( beats_per_minute_88, timebase); + uint16_t beatsin (sin16_t( beat + phase_offset) + 32768); + uint16_t rangewidth = highest - lowest; + uint16_t scaledbeat = scale16( beatsin, rangewidth); + uint16_t result = lowest + scaledbeat; + return result; +} + +// Generates a 16-bit sine wave at a given BPM that oscillates within a given range. see fastled for details. +uint16_t beatsin16_t(accum88 beats_per_minute, uint16_t lowest, uint16_t highest, uint32_t timebase, uint16_t phase_offset) +{ + uint16_t beat = beat16( beats_per_minute, timebase); + uint16_t beatsin = (sin16_t( beat + phase_offset) + 32768); + uint16_t rangewidth = highest - lowest; + uint16_t scaledbeat = scale16( beatsin, rangewidth); + uint16_t result = lowest + scaledbeat; + return result; +} + +// Generates an 8-bit sine wave at a given BPM that oscillates within a given range. see fastled for details. +uint8_t beatsin8_t(accum88 beats_per_minute, uint8_t lowest, uint8_t highest, uint32_t timebase, uint8_t phase_offset) +{ + uint8_t beat = beat8( beats_per_minute, timebase); + uint8_t beatsin = sin8_t( beat + phase_offset); + uint8_t rangewidth = highest - lowest; + uint8_t scaledbeat = scale8( beatsin, rangewidth); + uint8_t result = lowest + scaledbeat; + return result; +} /////////////////////////////////////////////////////////////////////////////// // Begin simulateSound (to enable audio enhanced effects to display something) @@ -431,8 +464,8 @@ um_data_t* simulateSound(uint8_t simulationId) default: case UMS_BeatSin: for (int i = 0; i<16; i++) - fftResult[i] = beatsin8(120 / (i+1), 0, 255); - // fftResult[i] = (beatsin8(120, 0, 255) + (256/16 * i)) % 256; + fftResult[i] = beatsin8_t(120 / (i+1), 0, 255); + // fftResult[i] = (beatsin8_t(120, 0, 255) + (256/16 * i)) % 256; volumeSmth = fftResult[8]; break; case UMS_WeWillRockYou: @@ -469,12 +502,12 @@ um_data_t* simulateSound(uint8_t simulationId) break; case UMS_10_13: for (int i = 0; i<16; i++) - fftResult[i] = inoise8(beatsin8(90 / (i+1), 0, 200)*15 + (ms>>10), ms>>3); + fftResult[i] = inoise8(beatsin8_t(90 / (i+1), 0, 200)*15 + (ms>>10), ms>>3); volumeSmth = fftResult[8]; break; case UMS_14_3: for (int i = 0; i<16; i++) - fftResult[i] = inoise8(beatsin8(120 / (i+1), 10, 30)*10 + (ms>>14), ms>>3); + fftResult[i] = inoise8(beatsin8_t(120 / (i+1), 10, 30)*10 + (ms>>14), ms>>3); volumeSmth = fftResult[8]; break; } diff --git a/wled00/wled_math.cpp b/wled00/wled_math.cpp index a4c9fc123..401446a23 100644 --- a/wled00/wled_math.cpp +++ b/wled00/wled_math.cpp @@ -12,6 +12,12 @@ #define modd(x, y) ((x) - (int)((x) / (y)) * (y)) +// Note: cos_t, sin_t and tan_t are very accurate but may be slow +// the math.h functions use several kB of flash and are to be avoided if possible +// sin16_t / cos16_t are faster and much more accurate than the fastled variants +// sin_approx and cos_approx are float wrappers for sin16_t/cos16_t and have an accuracy of +/-0.0015 compared to sinf() +// sin8_t / cos8_t are fastled replacements and use sin16_t / cos16_t. Slightly slower than fastled version but very accurate + float cos_t(float phi) { float x = modd(phi, TWO_PI); @@ -31,8 +37,8 @@ float cos_t(float phi) return res; } -float sin_t(float x) { - float res = cos_t(HALF_PI - x); +float sin_t(float phi) { + float res = cos_t(HALF_PI - phi); #ifdef WLED_DEBUG_MATH Serial.printf("sin: %f,%f,%f,(%f)\n",x,res,sin(x),res-sin(x)); #endif @@ -49,6 +55,85 @@ float tan_t(float x) { return res; } +// 16-bit, integer based Bhaskara I's sine approximation: 16*x*(pi - x) / (5*pi^2 - 4*x*(pi - x)) +// input is 16bit unsigned (0-65535), output is 16bit signed (-32767 to +32767) +// optimized integer implementation by @dedehai +int16_t sin16_t(uint16_t theta) { + int scale = 1; + if (theta > 0x7FFF) { + theta = 0xFFFF - theta; + scale = -1; // second half of the sine function is negative (pi - 2*pi) + } + uint32_t precal = theta * (0x7FFF - theta); + uint64_t numerator = (uint64_t)precal * (4 * 0x7FFF); // 64bit required + int32_t denominator = 1342095361 - precal; // 1342095361 is 5 * 0x7FFF^2 / 4 + int16_t result = numerator / denominator; + return result * scale; +} + +int16_t cos16_t(uint16_t theta) { + return sin16_t(theta + 16384); //cos(x) = sin(x+pi/2) +} + +uint8_t sin8_t(uint8_t theta) { + int32_t sin16 = sin16_t((uint16_t)theta * 257); // 255 * 257 = 0xFFFF + sin16 += 0x7FFF; //shift result to range 0-0xFFFF + return sin16 >> 8; +} + +uint8_t cos8_t(uint8_t theta) { + return sin8_t(theta + 64); //cos(x) = sin(x+pi/2) +} + +float sin_approx(float theta) +{ + theta = modd(theta, TWO_PI); // modulo: bring to -2pi to 2pi range + if(theta < 0) theta += M_TWOPI; // 0-2pi range + uint16_t scaled_theta = (uint16_t)(theta * (0xFFFF / M_TWOPI)); + int32_t result = sin16_t(scaled_theta); + float sin = float(result) / 0x7FFF; + return sin; +} + +float cos_approx(float theta) +{ + return sin_approx(theta + M_PI_2); +} + +float tan_approx(float x) { + float c = cos_approx(x); + if (c==0.0f) return 0; + float res = sin_approx(x) / c; + return res; +} + +#define ATAN2_CONST_A 0.1963f +#define ATAN2_CONST_B 0.9817f + +// fast atan2() approximation source: public domain +float atan2_t(float y, float x) { + if (x == 0.0f) return (y > 0.0f) ? M_PI_2 : (y < 0.0f) ? -M_PI_2 : 0.0f; + + float abs_y = (y < 0.0f) ? -y : y + 1e-10f; // make sure y is not zero to prevent division by 0 + float z = abs_y / x; + float atan_approx; + + if (z < 1.0f) { + atan_approx = z / (1.0f + ATAN2_CONST_A * z * z); + if (x < 0.0f) { + return (y >= 0.0f) ? atan_approx + PI : atan_approx - PI; + } + } + else { + z = x / abs_y; + atan_approx = M_PI_2 - z / (1.0f + ATAN2_CONST_A * z * z); + if (y < 0.0f) { + return -atan_approx; + } + } + return atan_approx; +} + //https://stackoverflow.com/questions/3380628 // Absolute error <= 6.7e-5 float acos_t(float x) { From eb5ad232a0e8dd5623051bc8d03c399fba318291 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Kristan?= Date: Sat, 5 Oct 2024 23:31:31 +0200 Subject: [PATCH 060/287] Minor tweaks and whitespace --- wled00/FX_2Dfcn.cpp | 35 +++++++++++++---------------------- 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index 6b3e6ef11..0e31083df 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -195,6 +195,7 @@ void IRAM_ATTR_YN Segment::setPixelColorXY(int x, int y, uint32_t col) const int vW = vWidth(); // segment width in logical pixels (can be 0 if segment is inactive) const int vH = vHeight(); // segment height in logical pixels (is always >= 1) + // negative values of x & y cast into unsigend will become very large values and will therefore be greater than vW/vH if (unsigned(x) >= unsigned(vW) || unsigned(y) >= unsigned(vH)) return; // if pixel would fall out of virtual segment just exit // if color is unscaled @@ -205,8 +206,7 @@ void IRAM_ATTR_YN Segment::setPixelColorXY(int x, int y, uint32_t col) if (transpose) { std::swap(x,y); } // swap X & Y if segment transposed unsigned groupLen = groupLength(); - if(groupLen > 1) - { + if (groupLen > 1) { int W = width(); int H = height(); x *= groupLen; // expand to physical pixels @@ -222,8 +222,7 @@ void IRAM_ATTR_YN Segment::setPixelColorXY(int x, int y, uint32_t col) } yY++; } - } - else { + } else { _setPixelColorXY_raw(x, y, col); } } @@ -293,7 +292,7 @@ void Segment::blur2D(uint8_t blur_x, uint8_t blur_y, bool smear) { const unsigned rows = vHeight(); uint32_t lastnew; uint32_t last; - if(blur_x) { + if (blur_x) { const uint8_t keepx = smear ? 255 : 255 - blur_x; const uint8_t seepx = blur_x >> (1 + smear); for (unsigned row = 0; row < rows; row++) { // blur rows (x direction) @@ -316,7 +315,7 @@ void Segment::blur2D(uint8_t blur_x, uint8_t blur_y, bool smear) { setPixelColorXY(cols-1, row, curnew); // set last pixel } } - if(blur_y) { + if (blur_y) { const uint8_t keepy = smear ? 255 : 255 - blur_y; const uint8_t seepy = blur_y >> (1 + smear); for (unsigned col = 0; col < cols; col++) { @@ -425,20 +424,16 @@ void Segment::moveX(int delta, bool wrap) { int newDelta; int stop = vW; int start = 0; - if(wrap) newDelta = (delta + vW) % vW; // +cols in case delta < 0 + if (wrap) newDelta = (delta + vW) % vW; // +cols in case delta < 0 else { - if(delta < 0) start = -delta; + if (delta < 0) start = absDelta; stop = vW - absDelta; newDelta = delta > 0 ? delta : 0; } for (int y = 0; y < vH; y++) { for (int x = 0; x < stop; x++) { - int srcX; - if (wrap) { - srcX = (x + newDelta) % vW; // Wrap using modulo when `wrap` is true - } else { - srcX = x + newDelta; - } + int srcX = x + newDelta; + if (wrap) srcX %= vW; // Wrap using modulo when `wrap` is true newPxCol[x] = getPixelColorXY(srcX, y); } for (int x = 0; x < stop; x++) setPixelColorXY(x + start, y, newPxCol[x]); @@ -455,20 +450,16 @@ void Segment::moveY(int delta, bool wrap) { int newDelta; int stop = vH; int start = 0; - if(wrap) newDelta = (delta + vH) % vH; // +rows in case delta < 0 + if (wrap) newDelta = (delta + vH) % vH; // +rows in case delta < 0 else { - if(delta < 0) start = -delta; + if (delta < 0) start = absDelta; stop = vH - absDelta; newDelta = delta > 0 ? delta : 0; } for (int x = 0; x < vW; x++) { for (int y = 0; y < stop; y++) { - int srcY; - if (wrap) { - srcY = (y + newDelta) % vH; // Wrap using modulo when `wrap` is true - } else { - srcY = y + newDelta; - } + int srcY = y + newDelta; + if (wrap) srcY %= vH; // Wrap using modulo when `wrap` is true newPxCol[y] = getPixelColorXY(x, srcY); } for (int y = 0; y < stop; y++) setPixelColorXY(x, y + start, newPxCol[y]); From f301296f1e1c9ba9ceeb49aead1f89fc3cd94ba6 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Sun, 6 Oct 2024 21:27:58 +0200 Subject: [PATCH 061/287] added rounding to sin8_t thx to @softhack007 --- wled00/wled_math.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wled00/wled_math.cpp b/wled00/wled_math.cpp index 401446a23..080cc9425 100644 --- a/wled00/wled_math.cpp +++ b/wled00/wled_math.cpp @@ -77,8 +77,8 @@ int16_t cos16_t(uint16_t theta) { uint8_t sin8_t(uint8_t theta) { int32_t sin16 = sin16_t((uint16_t)theta * 257); // 255 * 257 = 0xFFFF - sin16 += 0x7FFF; //shift result to range 0-0xFFFF - return sin16 >> 8; + sin16 += 0x7FFF + 128; //shift result to range 0-0xFFFF, +128 for rounding + return min(sin16, int32_t(0xFFFF)) >> 8; // min performs saturation, and prevents overflow } uint8_t cos8_t(uint8_t theta) { From be64930ebb97b554e3e03c6c178269cec791053a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Kristan?= Date: Mon, 7 Oct 2024 16:50:51 +0200 Subject: [PATCH 062/287] Indentation and shadowed variable. --- wled00/FX_2Dfcn.cpp | 6 +++--- wled00/colors.cpp | 48 ++++++++++++++++++++++----------------------- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index 0e31083df..5a7dc76d3 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -652,9 +652,9 @@ void Segment::drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, case 60: bits = pgm_read_byte_near(&console_font_5x12[(chr * h) + i]); break; // 5x12 font default: return; } - uint32_t col = ColorFromPaletteWLED(grad, (i+1)*255/h, 255, NOBLEND); + uint32_t c = ColorFromPaletteWLED(grad, (i+1)*255/h, 255, NOBLEND); // pre-scale color for all pixels - col = color_fade(col, _segBri); + c = color_fade(c, _segBri); _colorScaled = true; for (int j = 0; j= (int)vWidth() || y0 < 0 || y0 >= (int)vHeight()) continue; // drawing off-screen if (((bits>>(j+(8-w))) & 0x01)) { // bit set - setPixelColorXY(x0, y0, col); + setPixelColorXY(x0, y0, c); } } _colorScaled = false; diff --git a/wled00/colors.cpp b/wled00/colors.cpp index c059ea9db..27c9c8289 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -98,30 +98,30 @@ uint32_t color_fade(uint32_t c1, uint8_t amount, bool video) // 1:1 replacement of fastled function optimized for ESP, slightly faster, more accurate and uses less flash (~ -200bytes) uint32_t ColorFromPaletteWLED(const CRGBPalette16& pal, unsigned index, uint8_t brightness, TBlendType blendType) { - if (blendType == LINEARBLEND_NOWRAP) { - index = (index*240) >> 8; // Blend range is affected by lo4 blend of values, remap to avoid wrapping - } - unsigned hi4 = byte(index) >> 4; - const CRGB* entry = (CRGB*)( (uint8_t*)(&(pal[0])) + (hi4 * sizeof(CRGB))); - unsigned red1 = entry->r; - unsigned green1 = entry->g; - unsigned blue1 = entry->b; - if (blendType != NOBLEND) { - if (hi4 == 15) entry = &(pal[0]); - else ++entry; - unsigned f2 = ((index & 0x0F) << 4) + 1; // +1 so we scale by 256 as a max value, then result can just be shifted by 8 - unsigned f1 = (257 - f2); // f2 is 1 minimum, so this is 256 max - red1 = (red1 * f1 + (unsigned)entry->r * f2) >> 8; - green1 = (green1 * f1 + (unsigned)entry->g * f2) >> 8; - blue1 = (blue1 * f1 + (unsigned)entry->b * f2) >> 8; - } - if (brightness < 255) { // note: zero checking could be done to return black but that is hardly ever used so it is omitted - uint32_t scale = brightness + 1; // adjust for rounding (bitshift) - red1 = (red1 * scale) >> 8; - green1 = (green1 * scale) >> 8; - blue1 = (blue1 * scale) >> 8; - } - return RGBW32(red1,green1,blue1,0); + if (blendType == LINEARBLEND_NOWRAP) { + index = (index*240) >> 8; // Blend range is affected by lo4 blend of values, remap to avoid wrapping + } + unsigned hi4 = byte(index) >> 4; + const CRGB* entry = (CRGB*)((uint8_t*)(&(pal[0])) + (hi4 * sizeof(CRGB))); + unsigned red1 = entry->r; + unsigned green1 = entry->g; + unsigned blue1 = entry->b; + if (blendType != NOBLEND) { + if (hi4 == 15) entry = &(pal[0]); + else ++entry; + unsigned f2 = ((index & 0x0F) << 4) + 1; // +1 so we scale by 256 as a max value, then result can just be shifted by 8 + unsigned f1 = (257 - f2); // f2 is 1 minimum, so this is 256 max + red1 = (red1 * f1 + (unsigned)entry->r * f2) >> 8; + green1 = (green1 * f1 + (unsigned)entry->g * f2) >> 8; + blue1 = (blue1 * f1 + (unsigned)entry->b * f2) >> 8; + } + if (brightness < 255) { // note: zero checking could be done to return black but that is hardly ever used so it is omitted + uint32_t scale = brightness + 1; // adjust for rounding (bitshift) + red1 = (red1 * scale) >> 8; + green1 = (green1 * scale) >> 8; + blue1 = (blue1 * scale) >> 8; + } + return RGBW32(red1,green1,blue1,0); } void setRandomColor(byte* rgb) From 210191b251f0ebdca0d5d12f21278b03fe404755 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Kristan?= Date: Mon, 7 Oct 2024 20:19:07 +0200 Subject: [PATCH 063/287] Fix for realtime drawing on main segment --- wled00/e131.cpp | 4 ++++ wled00/udp.cpp | 5 +++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/wled00/e131.cpp b/wled00/e131.cpp index 7c074759e..bc26a0639 100644 --- a/wled00/e131.cpp +++ b/wled00/e131.cpp @@ -39,6 +39,7 @@ void handleDDPPacket(e131_packet_t* p) { realtimeLock(realtimeTimeoutMs, REALTIME_MODE_DDP); if (!realtimeOverride || (realtimeMode && useMainSegmentOnly)) { + if (useMainSegmentOnly) strip.getMainSegment().beginDraw(); for (unsigned i = start; i < stop; i++, c += ddpChannelsPerLed) { setRealtimePixel(i, data[c], data[c+1], data[c+2], ddpChannelsPerLed >3 ? data[c+3] : 0); } @@ -147,6 +148,7 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){ if (realtimeOverride && !(realtimeMode && useMainSegmentOnly)) return; wChannel = (availDMXLen > 3) ? e131_data[dataOffset+3] : 0; + if (useMainSegmentOnly) strip.getMainSegment().beginDraw(); for (unsigned i = 0; i < totalLen; i++) setRealtimePixel(i, e131_data[dataOffset+0], e131_data[dataOffset+1], e131_data[dataOffset+2], wChannel); break; @@ -164,6 +166,7 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){ strip.setBrightness(bri, true); } + if (useMainSegmentOnly) strip.getMainSegment().beginDraw(); for (unsigned i = 0; i < totalLen; i++) setRealtimePixel(i, e131_data[dataOffset+1], e131_data[dataOffset+2], e131_data[dataOffset+3], wChannel); break; @@ -308,6 +311,7 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){ } } + if (useMainSegmentOnly) strip.getMainSegment().beginDraw(); if (!is4Chan) { for (unsigned i = previousLeds; i < ledsTotal; i++) { setRealtimePixel(i, e131_data[dmxOffset], e131_data[dmxOffset+1], e131_data[dmxOffset+2], 0); diff --git a/wled00/udp.cpp b/wled00/udp.cpp index 0ff39f110..a6a0f6aa2 100644 --- a/wled00/udp.cpp +++ b/wled00/udp.cpp @@ -687,10 +687,11 @@ void setRealtimePixel(uint16_t i, byte r, byte g, byte b, byte w) b = gamma8(b); w = gamma8(w); } + uint32_t col = RGBW32(r,g,b,w); if (useMainSegmentOnly) { - strip.getMainSegment().setPixelColor(pix, r, g, b, w); // this expects that strip.getMainSegment().beginDraw() has been called in handleNotification() + strip.getMainSegment().setPixelColor(pix, col); // this expects that strip.getMainSegment().beginDraw() has been called in handleNotification() } else { - strip.setPixelColor(pix, r, g, b, w); + strip.setPixelColor(pix, col); } } } From 5e29f2c1b7844492c5ccaf194f524fe810947c3f Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Tue, 15 Oct 2024 20:11:33 +0200 Subject: [PATCH 064/287] fixed atan2_t approximation was incorrect, now doing it right. also removed hypotf() from octopus, saving a little flash. --- wled00/FX.cpp | 6 ++++-- wled00/wled_math.cpp | 39 +++++++++++++++++---------------------- 2 files changed, 21 insertions(+), 24 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 338dd2df9..5b4513dc7 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -7749,8 +7749,10 @@ uint16_t mode_2Doctopus() { const int C_Y = (rows / 2) + ((SEGMENT.custom2 - 128)*rows)/255; for (int x = 0; x < cols; x++) { for (int y = 0; y < rows; y++) { - rMap[XY(x, y)].angle = 40.7436f * atan2_t((y - C_Y), (x - C_X)); // avoid 128*atan2()/PI - rMap[XY(x, y)].radius = hypotf((x - C_X), (y - C_Y)) * mapp; //thanks Sutaburosu + rMap[XY(x, y)].angle = int(40.7436f * atan2_t((y - C_Y), (x - C_X))); // avoid 128*atan2()/PI + int dx = (x - C_X); + int dy = (y - C_Y); + rMap[XY(x, y)].radius = sqrtf(dx * dx + dy * dy) * mapp; //thanks Sutaburosu } } } diff --git a/wled00/wled_math.cpp b/wled00/wled_math.cpp index 080cc9425..1825ab198 100644 --- a/wled00/wled_math.cpp +++ b/wled00/wled_math.cpp @@ -88,8 +88,8 @@ uint8_t cos8_t(uint8_t theta) { float sin_approx(float theta) { theta = modd(theta, TWO_PI); // modulo: bring to -2pi to 2pi range - if(theta < 0) theta += M_TWOPI; // 0-2pi range - uint16_t scaled_theta = (uint16_t)(theta * (0xFFFF / M_TWOPI)); + if(theta < 0) theta += TWO_PI; // 0-2pi range + uint16_t scaled_theta = (uint16_t)(theta * (0xFFFF / TWO_PI)); int32_t result = sin16_t(scaled_theta); float sin = float(result) / 0x7FFF; return sin; @@ -110,28 +110,23 @@ float tan_approx(float x) { #define ATAN2_CONST_A 0.1963f #define ATAN2_CONST_B 0.9817f -// fast atan2() approximation source: public domain +// atan2_t approximation, with the idea from https://gist.github.com/volkansalma/2972237?permalink_comment_id=3872525#gistcomment-3872525 float atan2_t(float y, float x) { - if (x == 0.0f) return (y > 0.0f) ? M_PI_2 : (y < 0.0f) ? -M_PI_2 : 0.0f; - - float abs_y = (y < 0.0f) ? -y : y + 1e-10f; // make sure y is not zero to prevent division by 0 - float z = abs_y / x; - float atan_approx; - - if (z < 1.0f) { - atan_approx = z / (1.0f + ATAN2_CONST_A * z * z); - if (x < 0.0f) { - return (y >= 0.0f) ? atan_approx + PI : atan_approx - PI; - } + float abs_y = fabs(y); + float abs_x = fabs(x); + float r = (abs_x - abs_y) / (abs_y + abs_x + 1e-10f); // avoid division by zero by adding a small nubmer + float angle; + if(x < 0) { + r = -r; + angle = M_PI/2.0f + M_PI/4.f; } - else { - z = x / abs_y; - atan_approx = M_PI_2 - z / (1.0f + ATAN2_CONST_A * z * z); - if (y < 0.0f) { - return -atan_approx; - } - } - return atan_approx; + else + angle = M_PI/2.0f - M_PI/4.f; + + float add = (ATAN2_CONST_A * (r * r) - ATAN2_CONST_B) * r; + angle += add; + angle = y < 0 ? -angle : angle; + return angle; } //https://stackoverflow.com/questions/3380628 From 98a6907976bfa3142545467359e876bd6d0ddc22 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Wed, 16 Oct 2024 19:53:58 +0200 Subject: [PATCH 065/287] cleanup and improvement to sin_appros() -replaced all PI references with M_PI version -there is no need to do the angle-modulo in float, casting it to an integer does the same BUT it has to be cast to an `int` first, see comment. --- wled00/wled_math.cpp | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/wled00/wled_math.cpp b/wled00/wled_math.cpp index 1825ab198..a191968e1 100644 --- a/wled00/wled_math.cpp +++ b/wled00/wled_math.cpp @@ -15,17 +15,17 @@ // Note: cos_t, sin_t and tan_t are very accurate but may be slow // the math.h functions use several kB of flash and are to be avoided if possible // sin16_t / cos16_t are faster and much more accurate than the fastled variants -// sin_approx and cos_approx are float wrappers for sin16_t/cos16_t and have an accuracy of +/-0.0015 compared to sinf() +// sin_approx and cos_approx are float wrappers for sin16_t/cos16_t and have an accuracy better than +/-0.0015 compared to sinf() // sin8_t / cos8_t are fastled replacements and use sin16_t / cos16_t. Slightly slower than fastled version but very accurate float cos_t(float phi) { - float x = modd(phi, TWO_PI); + float x = modd(phi, M_TWOPI); if (x < 0) x = -1 * x; int8_t sign = 1; - if (x > PI) + if (x > M_PI) { - x -= PI; + x -= M_PI; sign = -1; } float xx = x * x; @@ -38,7 +38,7 @@ float cos_t(float phi) } float sin_t(float phi) { - float res = cos_t(HALF_PI - phi); + float res = cos_t(M_PI_2 - phi); #ifdef WLED_DEBUG_MATH Serial.printf("sin: %f,%f,%f,(%f)\n",x,res,sin(x),res-sin(x)); #endif @@ -87,9 +87,7 @@ uint8_t cos8_t(uint8_t theta) { float sin_approx(float theta) { - theta = modd(theta, TWO_PI); // modulo: bring to -2pi to 2pi range - if(theta < 0) theta += TWO_PI; // 0-2pi range - uint16_t scaled_theta = (uint16_t)(theta * (0xFFFF / TWO_PI)); + uint16_t scaled_theta = (int)(theta * (0xFFFF / M_TWOPI)); // note: do not cast negative float to uint! cast to int first (undefined on C3) int32_t result = sin16_t(scaled_theta); float sin = float(result) / 0x7FFF; return sin; @@ -118,10 +116,10 @@ float atan2_t(float y, float x) { float angle; if(x < 0) { r = -r; - angle = M_PI/2.0f + M_PI/4.f; + angle = M_PI_2 + M_PI_4; } else - angle = M_PI/2.0f - M_PI/4.f; + angle = M_PI_2 - M_PI_4; float add = (ATAN2_CONST_A * (r * r) - ATAN2_CONST_B) * r; angle += add; @@ -140,10 +138,10 @@ float acos_t(float x) { ret = ret * xabs; ret = ret - 0.2121144f; ret = ret * xabs; - ret = ret + HALF_PI; + ret = ret + M_PI_2; ret = ret * sqrt(1.0f-xabs); ret = ret - 2 * negate * ret; - float res = negate * PI + ret; + float res = negate * M_PI + ret; #ifdef WLED_DEBUG_MATH Serial.printf("acos: %f,%f,%f,(%f)\n",x,res,acos(x),res-acos(x)); #endif @@ -151,7 +149,7 @@ float acos_t(float x) { } float asin_t(float x) { - float res = HALF_PI - acos_t(x); + float res = M_PI_2 - acos_t(x); #ifdef WLED_DEBUG_MATH Serial.printf("asin: %f,%f,%f,(%f)\n",x,res,asin(x),res-asin(x)); #endif @@ -167,7 +165,7 @@ float atan_t(float x) { //For A/B/C, see https://stackoverflow.com/a/42542593 static const double A { 0.0776509570923569 }; static const double B { -0.287434475393028 }; - static const double C { ((HALF_PI/2) - A - B) }; + static const double C { ((M_PI_4) - A - B) }; // polynominal factors for approximation between 1 and 5 static const float C0 { 0.089494f }; static const float C1 { 0.974207f }; @@ -182,7 +180,7 @@ float atan_t(float x) { x = std::abs(x); float res; if (x > 5.0f) { // atan(x) converges to pi/2 - (1/x) for large values - res = HALF_PI - (1.0f/x); + res = M_PI_2 - (1.0f/x); } else if (x > 1.0f) { //1 < x < 5 float xx = x * x; res = (C4*xx*xx)+(C3*xx*x)+(C2*xx)+(C1*x)+C0; From bd68b977d512e28b072cfeb1d3c146e4af221e31 Mon Sep 17 00:00:00 2001 From: maxi4329 Date: Thu, 17 Oct 2024 18:07:01 +0200 Subject: [PATCH 066/287] minor webui enhancements --- wled00/data/index.css | 20 +++++++++++++++++--- wled00/data/index.htm | 2 +- wled00/data/settings_2D.htm | 12 ++++++------ wled00/data/settings_leds.htm | 8 ++++---- wled00/data/settings_pin.htm | 11 ++++++++++- wled00/data/settings_sec.htm | 4 ++-- wled00/data/settings_sync.htm | 10 +++++----- wled00/data/settings_time.htm | 6 +++--- wled00/data/settings_um.htm | 10 +++++----- wled00/data/settings_wifi.htm | 8 ++++---- wled00/data/update.htm | 2 +- 11 files changed, 58 insertions(+), 35 deletions(-) diff --git a/wled00/data/index.css b/wled00/data/index.css index 6f465e407..2ae17ffe5 100644 --- a/wled00/data/index.css +++ b/wled00/data/index.css @@ -97,6 +97,7 @@ button { .labels { margin: 0; padding: 8px 0 2px 0; + font-size: 19px; } #namelabel { @@ -890,12 +891,12 @@ a.btn { line-height: 28px; } -/* Quick color select Black button (has white border) */ -.qcsb { +/* Quick color select Black and White button (has white/black border, depending on the theme) */ +.qcsb, .qcsw { width: 26px; height: 26px; line-height: 26px; - border: 1px solid #fff; + border: 1px solid var(--c-f); } /* Hex color input wrapper div */ @@ -1299,6 +1300,14 @@ TD .checkmark, TD .radiomark { width: 100%; } +#segutil { + margin-bottom: 12px; +} + +#segcont > div:first-child, #fxFind { + margin-top: 4px; +} + /* Simplify segments */ .simplified #segcont .lstI { margin-top: 4px; @@ -1433,6 +1442,11 @@ dialog { position: relative; } +.presin { + width: 100%; + box-sizing: border-box; +} + .btn-s, .btn-n { border: 1px solid var(--c-2); diff --git a/wled00/data/index.htm b/wled00/data/index.htm index e74ea0076..8adec791f 100644 --- a/wled00/data/index.htm +++ b/wled00/data/index.htm @@ -106,7 +106,7 @@
-
+

diff --git a/wled00/data/settings_2D.htm b/wled00/data/settings_2D.htm index a70ca76be..2aec03682 100644 --- a/wled00/data/settings_2D.htm +++ b/wled00/data/settings_2D.htm @@ -54,8 +54,8 @@ Orientation:
Serpentine:
Dimensions (WxH): x
-Offset X: -Y:
(offset from top-left corner in # LEDs) +Offset X: +Y:
(offset from top-left corner in # LEDs) `; p.insertAdjacentHTML("beforeend", b); } @@ -246,7 +246,7 @@ Y:

2D setup

- Strip or panel: + Strip or panel:  Panel dimensions (WxH): x
Horizontal panels: Vertical panels:
- 1st panel:
- Orientation:
@@ -286,7 +286,7 @@ Y:
Gap file:
- Note: Gap file is a .json file containing an array with number of elements equal to the matrix size.
+ Note: Gap file is a .json file containing an array with number of elements equal to the matrix size.
A value of -1 means that pixel at that position is missing, a value of 0 means never paint that pixel, and 1 means regular pixel.

diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index 6be5becd1..0a28fda3f 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -747,7 +747,7 @@ Swap:

- LED memory usage: 0 / ? B
+ LED memory usage: 0 / ? B

Advanced

- Palette wrapping: + Palette wrapping:  +
diff --git a/wled00/data/settings_sec.htm b/wled00/data/settings_sec.htm index fa75882c0..6cc03e9eb 100644 --- a/wled00/data/settings_sec.htm +++ b/wled00/data/settings_sec.htm @@ -72,8 +72,8 @@ Contributors, dependencies and special thanks
A huge thank you to everyone who helped me create WLED!

(c) 2016-2024 Christian Schwinne
- Licensed under the EUPL v1.2 license

- Server message: Response error!
+ Licensed under the EUPL v1.2 license

+ Server message:  Response error!
diff --git a/wled00/data/settings_sync.htm b/wled00/data/settings_sync.htm index 34b9fc6cd..754605bf0 100644 --- a/wled00/data/settings_sync.htm +++ b/wled00/data/settings_sync.htm @@ -118,7 +118,7 @@ Receive UDP realtime:
Use main segment only:
Respect LED Maps:

Network DMX input
-Type: +Type: 
Multicast:
Start universe:
-Reboot required. Check out LedFx!
+Reboot required. Check out LedFx!
Skip out-of-sequence packets:
DMX start address:
DMX segment spacing:
E1.31 port priority:
-DMX mode: +DMX mode: 
Group Topic:
Publish on button press:
Retain brightness & color messages:
-Reboot required to apply changes. MQTT info +Reboot required to apply changes. MQTT info

Philips Hue

@@ -209,7 +209,7 @@ Hue status: Disabled in this build This firmware build does not support Serial interface.
-Baud rate: +Baud rate: 

Use 24h format:
- Time zone: + Time zone: 
UTC offset: seconds (max. 18 hours)
Current local time is unknown.
- Latitude:
- Longitude:
+ Latitude: 
+ Longitude: 
(opens new tab, only works in browser)
diff --git a/wled00/data/settings_um.htm b/wled00/data/settings_um.htm index c2f0ffbf2..9c53d560d 100644 --- a/wled00/data/settings_um.htm +++ b/wled00/data/settings_um.htm @@ -283,14 +283,14 @@

Usermod Setup

Global I2C GPIOs (HW)
(change requires reboot!)
- SDA: - SCL: + SDA:  + SCL: 
Global SPI GPIOs (HW)
(only changable on ESP32, change requires reboot!)
- MOSI: - MISO: - SCLK: + MOSI:  + MISO:  + SCLK: 
Reboot after save?
Loading settings...
diff --git a/wled00/data/settings_wifi.htm b/wled00/data/settings_wifi.htm index 30b6600ae..3b840f509 100644 --- a/wled00/data/settings_wifi.htm +++ b/wled00/data/settings_wifi.htm @@ -166,7 +166,7 @@ Static subnet mask:
Hide AP name:
AP password (leave empty for open):

Access Point WiFi channel:
- AP opens: + AP opens: 
- AP IP: Not active
+ AP IP:  Not active

Experimental

Force 802.11g mode (ESP8266 only):
Disable WiFi sleep:
Can help with connectivity issues and Audioreactive sync.
Disabling WiFi sleep increases power consumption.

-
TX power: @@ -205,7 +205,7 @@ Static subnet mask:
Listen for events over ESP-NOW
Keep disabled if not using a remote or wireless sync, increases power consumption.
Paired Remote MAC:
- Last device seen: None
+ Last device seen: None
diff --git a/wled00/data/update.htm b/wled00/data/update.htm index b68645a52..791a76f6b 100644 --- a/wled00/data/update.htm +++ b/wled00/data/update.htm @@ -16,7 +16,7 @@

WLED Software Update

- Installed version: ##VERSION##
+ Installed version: ##VERSION##
Download the latest binary: 
From caa997fff1750bd08e7d815674c3ff7728a14029 Mon Sep 17 00:00:00 2001 From: maxi4329 Date: Fri, 18 Oct 2024 18:43:41 +0200 Subject: [PATCH 067/287] removed onkeydown tried to find a replacement for the nbsp --- package-lock.json | 2 +- wled00/data/settings_2D.htm | 8 ++++---- wled00/data/settings_leds.htm | 8 ++++---- wled00/data/settings_pin.htm | 11 +---------- wled00/data/settings_sec.htm | 4 ++-- wled00/data/settings_sync.htm | 10 +++++----- wled00/data/settings_time.htm | 6 +++--- wled00/data/settings_um.htm | 10 +++++----- wled00/data/settings_wifi.htm | 8 ++++---- wled00/data/style.css | 3 +++ wled00/data/update.htm | 2 +- 11 files changed, 33 insertions(+), 39 deletions(-) diff --git a/package-lock.json b/package-lock.json index 85ee1df0f..e85857017 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "clean-css": "^5.3.3", "html-minifier-terser": "^7.2.0", "inliner": "^1.13.1", - "nodemon": "^3.0.2" + "nodemon": "^3.1.7" } }, "node_modules/@jridgewell/gen-mapping": { diff --git a/wled00/data/settings_2D.htm b/wled00/data/settings_2D.htm index 2aec03682..e372b3e21 100644 --- a/wled00/data/settings_2D.htm +++ b/wled00/data/settings_2D.htm @@ -246,7 +246,7 @@ Y: Back

2D setup

- Strip or panel:  + Strip or panel: x
Horizontal panels: Vertical panels:
- 1st panel: 
- Orientation: 
@@ -286,7 +286,7 @@ Y:
Gap file:
- Note: Gap file is a .json file containing an array with number of elements equal to the matrix size.
+ Note: Gap file is a .json file containing an array with number of elements equal to the matrix size.
A value of -1 means that pixel at that position is missing, a value of 0 means never paint that pixel, and 1 means regular pixel.

diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index 0a28fda3f..6be5becd1 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -747,7 +747,7 @@ Swap:

- LED memory usage: 0 / ? B
+ LED memory usage: 0 / ? B

Advanced

- Palette wrapping:  + Palette wrapping: +
diff --git a/wled00/data/settings_sec.htm b/wled00/data/settings_sec.htm index 6cc03e9eb..fa75882c0 100644 --- a/wled00/data/settings_sec.htm +++ b/wled00/data/settings_sec.htm @@ -72,8 +72,8 @@ Contributors, dependencies and special thanks
A huge thank you to everyone who helped me create WLED!

(c) 2016-2024 Christian Schwinne
- Licensed under the EUPL v1.2 license

- Server message:  Response error!
+ Licensed under the EUPL v1.2 license

+ Server message: Response error!
diff --git a/wled00/data/settings_sync.htm b/wled00/data/settings_sync.htm index 754605bf0..34b9fc6cd 100644 --- a/wled00/data/settings_sync.htm +++ b/wled00/data/settings_sync.htm @@ -118,7 +118,7 @@ Receive UDP realtime:
Use main segment only:
Respect LED Maps:

Network DMX input
-Type:  +Type:
Multicast:
Start universe:
-Reboot required. Check out LedFx!
+Reboot required. Check out LedFx!
Skip out-of-sequence packets:
DMX start address:
DMX segment spacing:
E1.31 port priority:
-DMX mode:  +DMX mode:
Group Topic:
Publish on button press:
Retain brightness & color messages:
-Reboot required to apply changes. MQTT info +Reboot required to apply changes. MQTT info

Philips Hue

@@ -209,7 +209,7 @@ Hue status: Disabled in this build This firmware build does not support Serial interface.
-Baud rate:  +Baud rate:

Use 24h format:
- Time zone:  + Time zone:
UTC offset: seconds (max. 18 hours)
Current local time is unknown.
- Latitude: 
- Longitude: 
+ Latitude:
+ Longitude:
(opens new tab, only works in browser)
diff --git a/wled00/data/settings_um.htm b/wled00/data/settings_um.htm index 9c53d560d..c2f0ffbf2 100644 --- a/wled00/data/settings_um.htm +++ b/wled00/data/settings_um.htm @@ -283,14 +283,14 @@

Usermod Setup

Global I2C GPIOs (HW)
(change requires reboot!)
- SDA:  - SCL:  + SDA: + SCL:
Global SPI GPIOs (HW)
(only changable on ESP32, change requires reboot!)
- MOSI:  - MISO:  - SCLK:  + MOSI: + MISO: + SCLK:
Reboot after save?
Loading settings...
diff --git a/wled00/data/settings_wifi.htm b/wled00/data/settings_wifi.htm index 3b840f509..30b6600ae 100644 --- a/wled00/data/settings_wifi.htm +++ b/wled00/data/settings_wifi.htm @@ -166,7 +166,7 @@ Static subnet mask:
Hide AP name:
AP password (leave empty for open):

Access Point WiFi channel:
- AP opens:  + AP opens:
- AP IP:  Not active
+ AP IP: Not active

Experimental

Force 802.11g mode (ESP8266 only):
Disable WiFi sleep:
Can help with connectivity issues and Audioreactive sync.
Disabling WiFi sleep increases power consumption.

-
TX power:  @@ -205,7 +205,7 @@ Static subnet mask:
Listen for events over ESP-NOW
Keep disabled if not using a remote or wireless sync, increases power consumption.
Paired Remote MAC:
- Last device seen: None
+ Last device seen: None
diff --git a/wled00/data/style.css b/wled00/data/style.css index b6cb0f9e6..42e49d304 100644 --- a/wled00/data/style.css +++ b/wled00/data/style.css @@ -44,6 +44,9 @@ button.sml { min-width: 40px; margin: 0 0 0 10px; } +span:before, b:before, b:after, i:after{ + content: "\00A0"; +} #scan { margin-top: -10px; } diff --git a/wled00/data/update.htm b/wled00/data/update.htm index 791a76f6b..b68645a52 100644 --- a/wled00/data/update.htm +++ b/wled00/data/update.htm @@ -16,7 +16,7 @@

WLED Software Update

- Installed version: ##VERSION##
+ Installed version: ##VERSION##
Download the latest binary: 
From 4fa8a3898a8771c6680bf84eaa5ab5101dd82b5b Mon Sep 17 00:00:00 2001 From: 1Prototype1 Date: Sat, 19 Oct 2024 00:30:24 +0530 Subject: [PATCH 068/287] Added Distribute for cpal Added a button to distribute the color markers in palette equally --- wled00/data/cpal/cpal.htm | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/wled00/data/cpal/cpal.htm b/wled00/data/cpal/cpal.htm index b58c0987a..8fa715bc8 100644 --- a/wled00/data/cpal/cpal.htm +++ b/wled00/data/cpal/cpal.htm @@ -167,9 +167,10 @@
-
- Currently in use custom palettes -
+
+
+ Currently in use custom palettes +
@@ -187,7 +188,7 @@ Available static palettes
- + @@ -204,6 +205,13 @@ var paletteName = []; // Holds the names of the palettes after load. var svgSave = '' var svgEdit = '' + var svgDist = '' + var svgTrash = '' + + const distDiv = gId("distDiv"); + distDiv.addEventListener('click', distribute); + distDiv.setAttribute('title', 'Distribute colors equally'); + distDiv.innerHTML = svgDist; function recOf() { rect = gradientBox.getBoundingClientRect(); @@ -433,7 +441,7 @@ renderY = e.srcElement.getBoundingClientRect().y + 13; trash.id = "trash"; - trash.innerHTML = ''; + trash.innerHTML = svgTrash; trash.style.position = "absolute"; trash.style.left = (renderX) + "px"; trash.style.top = (renderY) + "px"; @@ -712,9 +720,27 @@ } } + function distribute() { + let colorMarkers = [...gradientBox.querySelectorAll('.color-marker')]; + colorMarkers.sort((a, b) => a.getAttribute('data-truepos') - b.getAttribute('data-truepos')); + colorMarkers = colorMarkers.slice(1, -1); + const spacing = Math.round(256 / (colorMarkers.length + 1)); + + colorMarkers.forEach((e, i) => { + const markerId = e.id.match(/\d+/)[0]; + const trueCol = e.getAttribute("data-truecol"); + gradientBox.removeChild(e); + gradientBox.removeChild(gId(`colorPicker${markerId}`)); + gradientBox.removeChild(gId(`colorPickerMarker${markerId}`)); + gradientBox.removeChild(gId(`deleteMarker${markerId}`)); + addC(spacing * (i + 1), trueCol); + }); + } + function rgbToHex(r, g, b) { const hex = ((r << 16) | (g << 8) | b).toString(16); return "#" + "0".repeat(6 - hex.length) + hex; } + From 95b4bde918c00c2d3191a0f71a391965d69b3d34 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Sun, 20 Oct 2024 10:42:02 -0400 Subject: [PATCH 069/287] UsermodManager: Make into namespace Namespaces are the C++ language construct for grouping global functions. --- wled00/fcn_declare.h | 47 ++++++++++++++++++++----------------------- wled00/um_manager.cpp | 5 +++-- wled00/wled.h | 3 --- 3 files changed, 25 insertions(+), 30 deletions(-) diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 71b00599c..78655b271 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -326,36 +326,33 @@ class Usermod { template static inline void oappend(const T& t) { oappend_shim->print(t); }; }; -class UsermodManager { - private: - static Usermod* ums[WLED_MAX_USERMODS]; - static byte numMods; +namespace UsermodManager { + extern byte numMods; - public: - static void loop(); - static void handleOverlayDraw(); - static bool handleButton(uint8_t b); - static bool getUMData(um_data_t **um_data, uint8_t mod_id = USERMOD_ID_RESERVED); // USERMOD_ID_RESERVED will poll all usermods - static void setup(); - static void connected(); - static void appendConfigData(Print&); - static void addToJsonState(JsonObject& obj); - static void addToJsonInfo(JsonObject& obj); - static void readFromJsonState(JsonObject& obj); - static void addToConfig(JsonObject& obj); - static bool readFromConfig(JsonObject& obj); + void loop(); + void handleOverlayDraw(); + bool handleButton(uint8_t b); + bool getUMData(um_data_t **um_data, uint8_t mod_id = USERMOD_ID_RESERVED); // USERMOD_ID_RESERVED will poll all usermods + void setup(); + void connected(); + void appendConfigData(Print&); + void addToJsonState(JsonObject& obj); + void addToJsonInfo(JsonObject& obj); + void readFromJsonState(JsonObject& obj); + void addToConfig(JsonObject& obj); + bool readFromConfig(JsonObject& obj); #ifndef WLED_DISABLE_MQTT - static void onMqttConnect(bool sessionPresent); - static bool onMqttMessage(char* topic, char* payload); + void onMqttConnect(bool sessionPresent); + bool onMqttMessage(char* topic, char* payload); #endif #ifndef WLED_DISABLE_ESPNOW - static bool onEspNowMessage(uint8_t* sender, uint8_t* payload, uint8_t len); + bool onEspNowMessage(uint8_t* sender, uint8_t* payload, uint8_t len); #endif - static void onUpdateBegin(bool); - static void onStateChange(uint8_t); - static bool add(Usermod* um); - static Usermod* lookup(uint16_t mod_id); - static inline byte getModCount() {return numMods;}; + void onUpdateBegin(bool); + void onStateChange(uint8_t); + bool add(Usermod* um); + Usermod* lookup(uint16_t mod_id); + inline byte getModCount() {return numMods;}; }; //usermods_list.cpp diff --git a/wled00/um_manager.cpp b/wled00/um_manager.cpp index 46fdf5b3b..1fdb6d688 100644 --- a/wled00/um_manager.cpp +++ b/wled00/um_manager.cpp @@ -3,6 +3,9 @@ * Registration and management utility for v2 usermods */ +static Usermod* ums[WLED_MAX_USERMODS] = {nullptr}; +byte UsermodManager::numMods = 0; + //Usermod Manager internals void UsermodManager::setup() { for (unsigned i = 0; i < numMods; i++) ums[i]->setup(); } void UsermodManager::connected() { for (unsigned i = 0; i < numMods; i++) ums[i]->connected(); } @@ -69,8 +72,6 @@ bool UsermodManager::add(Usermod* um) return true; } -Usermod* UsermodManager::ums[WLED_MAX_USERMODS] = {nullptr}; -byte UsermodManager::numMods = 0; /* Usermod v2 interface shim for oappend */ Print* Usermod::oappend_shim = nullptr; diff --git a/wled00/wled.h b/wled00/wled.h index bc525cd6f..5f1952bec 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -896,9 +896,6 @@ WLED_GLOBAL uint32_t ledMaps _INIT(0); // bitfield representation of available l WLED_GLOBAL uint16_t ledMaps _INIT(0); // bitfield representation of available ledmaps #endif -// Usermod manager -WLED_GLOBAL UsermodManager usermods _INIT(UsermodManager()); - // global I2C SDA pin (used for usermods) #ifndef I2CSDAPIN WLED_GLOBAL int8_t i2c_sda _INIT(-1); From 32eee3365ac06858b5d6821931c39c020af233bc Mon Sep 17 00:00:00 2001 From: Will Miles Date: Sun, 20 Oct 2024 10:48:31 -0400 Subject: [PATCH 070/287] PinManager: Make in to namespace Namespaces are the C++ language construct for grouping global functions. --- wled00/pin_manager.cpp | 20 +++++------ wled00/pin_manager.h | 78 +++++++++++++++++++----------------------- 2 files changed, 45 insertions(+), 53 deletions(-) diff --git a/wled00/pin_manager.cpp b/wled00/pin_manager.cpp index 793b5440c..14209977a 100644 --- a/wled00/pin_manager.cpp +++ b/wled00/pin_manager.cpp @@ -13,6 +13,16 @@ #endif #endif +// Pin management state variables +#ifdef ESP8266 +static uint32_t pinAlloc = 0UL; // 1 bit per pin, we use first 17bits +#else +static uint64_t pinAlloc = 0ULL; // 1 bit per pin, we use 50 bits on ESP32-S3 +static uint16_t ledcAlloc = 0; // up to 16 LEDC channels (WLED_MAX_ANALOG_CHANNELS) +#endif +static uint8_t i2cAllocCount = 0; // allow multiple allocation of I2C bus pins but keep track of allocations +static uint8_t spiAllocCount = 0; // allow multiple allocation of SPI bus pins but keep track of allocations +static PinOwner ownerTag[WLED_NUM_PINS] = { PinOwner::None }; /// Actual allocation/deallocation routines bool PinManager::deallocatePin(byte gpio, PinOwner tag) @@ -273,13 +283,3 @@ void PinManager::deallocateLedc(byte pos, byte channels) } } #endif - -#ifdef ESP8266 -uint32_t PinManager::pinAlloc = 0UL; -#else -uint64_t PinManager::pinAlloc = 0ULL; -uint16_t PinManager::ledcAlloc = 0; -#endif -uint8_t PinManager::i2cAllocCount = 0; -uint8_t PinManager::spiAllocCount = 0; -PinOwner PinManager::ownerTag[WLED_NUM_PINS] = { PinOwner::None }; diff --git a/wled00/pin_manager.h b/wled00/pin_manager.h index 73a4a3656..c8fb165ce 100644 --- a/wled00/pin_manager.h +++ b/wled00/pin_manager.h @@ -9,6 +9,12 @@ #endif #include "const.h" // for USERMOD_* values +#ifdef ESP8266 +#define WLED_NUM_PINS (GPIO_PIN_COUNT+1) // somehow they forgot GPIO 16 (0-16==17) +#else +#define WLED_NUM_PINS (GPIO_PIN_COUNT) +#endif + typedef struct PinManagerPinType { int8_t pin; bool isOutput; @@ -70,53 +76,39 @@ enum struct PinOwner : uint8_t { }; static_assert(0u == static_cast(PinOwner::None), "PinOwner::None must be zero, so default array initialization works as expected"); -class PinManager { - private: - #ifdef ESP8266 - #define WLED_NUM_PINS (GPIO_PIN_COUNT+1) // somehow they forgot GPIO 16 (0-16==17) - static uint32_t pinAlloc; // 1 bit per pin, we use first 17bits - #else - #define WLED_NUM_PINS (GPIO_PIN_COUNT) - static uint64_t pinAlloc; // 1 bit per pin, we use 50 bits on ESP32-S3 - static uint16_t ledcAlloc; // up to 16 LEDC channels (WLED_MAX_ANALOG_CHANNELS) - #endif - static uint8_t i2cAllocCount; // allow multiple allocation of I2C bus pins but keep track of allocations - static uint8_t spiAllocCount; // allow multiple allocation of SPI bus pins but keep track of allocations - static PinOwner ownerTag[WLED_NUM_PINS]; +namespace PinManager { + // De-allocates a single pin + bool deallocatePin(byte gpio, PinOwner tag); + // De-allocates multiple pins but only if all can be deallocated (PinOwner has to be specified) + bool deallocateMultiplePins(const uint8_t *pinArray, byte arrayElementCount, PinOwner tag); + bool deallocateMultiplePins(const managed_pin_type *pinArray, byte arrayElementCount, PinOwner tag); + // Allocates a single pin, with an owner tag. + // De-allocation requires the same owner tag (or override) + bool allocatePin(byte gpio, bool output, PinOwner tag); + // Allocates all the pins, or allocates none of the pins, with owner tag. + // Provided to simplify error condition handling in clients + // using more than one pin, such as I2C, SPI, rotary encoders, + // ethernet, etc.. + bool allocateMultiplePins(const managed_pin_type * mptArray, byte arrayElementCount, PinOwner tag ); - public: - // De-allocates a single pin - static bool deallocatePin(byte gpio, PinOwner tag); - // De-allocates multiple pins but only if all can be deallocated (PinOwner has to be specified) - static bool deallocateMultiplePins(const uint8_t *pinArray, byte arrayElementCount, PinOwner tag); - static bool deallocateMultiplePins(const managed_pin_type *pinArray, byte arrayElementCount, PinOwner tag); - // Allocates a single pin, with an owner tag. - // De-allocation requires the same owner tag (or override) - static bool allocatePin(byte gpio, bool output, PinOwner tag); - // Allocates all the pins, or allocates none of the pins, with owner tag. - // Provided to simplify error condition handling in clients - // using more than one pin, such as I2C, SPI, rotary encoders, - // ethernet, etc.. - static bool allocateMultiplePins(const managed_pin_type * mptArray, byte arrayElementCount, PinOwner tag ); + [[deprecated("Replaced by three-parameter allocatePin(gpio, output, ownerTag), for improved debugging")]] + inline bool allocatePin(byte gpio, bool output = true) { return allocatePin(gpio, output, PinOwner::None); } + [[deprecated("Replaced by two-parameter deallocatePin(gpio, ownerTag), for improved debugging")]] + inline void deallocatePin(byte gpio) { deallocatePin(gpio, PinOwner::None); } - [[deprecated("Replaced by three-parameter allocatePin(gpio, output, ownerTag), for improved debugging")]] - static inline bool allocatePin(byte gpio, bool output = true) { return allocatePin(gpio, output, PinOwner::None); } - [[deprecated("Replaced by two-parameter deallocatePin(gpio, ownerTag), for improved debugging")]] - static inline void deallocatePin(byte gpio) { deallocatePin(gpio, PinOwner::None); } + // will return true for reserved pins + bool isPinAllocated(byte gpio, PinOwner tag = PinOwner::None); + // will return false for reserved pins + bool isPinOk(byte gpio, bool output = true); + + bool isReadOnlyPin(byte gpio); - // will return true for reserved pins - static bool isPinAllocated(byte gpio, PinOwner tag = PinOwner::None); - // will return false for reserved pins - static bool isPinOk(byte gpio, bool output = true); - - static bool isReadOnlyPin(byte gpio); + PinOwner getPinOwner(byte gpio); - static PinOwner getPinOwner(byte gpio); - - #ifdef ARDUINO_ARCH_ESP32 - static byte allocateLedc(byte channels); - static void deallocateLedc(byte pos, byte channels); - #endif + #ifdef ARDUINO_ARCH_ESP32 + byte allocateLedc(byte channels); + void deallocateLedc(byte pos, byte channels); + #endif }; //extern PinManager pinManager; From dcfdca63515f2d3f80674d05c6ee7186f9458354 Mon Sep 17 00:00:00 2001 From: ingDIY <10012263+ingDIY@users.noreply.github.com> Date: Fri, 25 Oct 2024 12:14:37 +0200 Subject: [PATCH 071/287] cleaned up old references to USE_ALT_DISPlAY that aren't used anymore in the code/source files; fixed readme.md documents and updated instructions; removed referencies to old usermods; fixed invalid filenames; removed referencies to old usermods; checked cycle options; splitted and fixed platformio_override.sample.ini; --- platformio_override.sample.ini | 2 -- .../platformio_override.sample.ini} | 11 +++---- .../readme.md | 30 +++++-------------- .../platformio_override.sample.ini | 14 +++++++++ .../readme.md | 22 ++++---------- 5 files changed, 32 insertions(+), 47 deletions(-) rename usermods/{usermod_v2_rotary_encoder_ui_ALT/platformio–override.sample.ini => usermod_v2_four_line_display_ALT/platformio_override.sample.ini} (54%) create mode 100644 usermods/usermod_v2_rotary_encoder_ui_ALT/platformio_override.sample.ini diff --git a/platformio_override.sample.ini b/platformio_override.sample.ini index 8e5fdf003..c445cd32e 100644 --- a/platformio_override.sample.ini +++ b/platformio_override.sample.ini @@ -111,7 +111,6 @@ build_flags = ${common.build_flags} ${esp8266.build_flags} ; ; Use 4 Line Display usermod with SPI display ; -D USERMOD_FOUR_LINE_DISPLAY -; -D USE_ALT_DISPlAY # mandatory ; -DFLD_SPI_DEFAULT ; -D FLD_TYPE=SSD1306_SPI64 ; -D FLD_PIN_CLOCKSPI=14 @@ -377,7 +376,6 @@ build_flags = ${common.build_flags} ${esp32.build_flags} -D USERMOD_DALLASTEMPERATURE -D USERMOD_FOUR_LINE_DISPLAY -D TEMPERATURE_PIN=23 - -D USE_ALT_DISPlAY ; new versions of USERMOD_FOUR_LINE_DISPLAY and USERMOD_ROTARY_ENCODER_UI -D USERMOD_AUDIOREACTIVE lib_deps = ${esp32.lib_deps} OneWire@~2.3.5 diff --git a/usermods/usermod_v2_rotary_encoder_ui_ALT/platformio–override.sample.ini b/usermods/usermod_v2_four_line_display_ALT/platformio_override.sample.ini similarity index 54% rename from usermods/usermod_v2_rotary_encoder_ui_ALT/platformio–override.sample.ini rename to usermods/usermod_v2_four_line_display_ALT/platformio_override.sample.ini index 6b32c71fb..9010648b3 100644 --- a/usermods/usermod_v2_rotary_encoder_ui_ALT/platformio–override.sample.ini +++ b/usermods/usermod_v2_four_line_display_ALT/platformio_override.sample.ini @@ -7,11 +7,12 @@ platform = ${esp32.platform} build_unflags = ${common.build_unflags} build_flags = ${common.build_flags_esp32} - -D USERMOD_FOUR_LINE_DISPLAY -D USE_ALT_DISPlAY - -D USERMOD_ROTARY_ENCODER_UI -D ENCODER_DT_PIN=18 -D ENCODER_CLK_PIN=5 -D ENCODER_SW_PIN=19 -upload_speed = 460800 + -D USERMOD_FOUR_LINE_DISPLAY + -D FLD_TYPE=SH1106 + -D I2CSCLPIN=27 + -D I2CSDAPIN=26 + lib_deps = ${esp32.lib_deps} - U8g2@~2.34.4 + U8g2 Wire - diff --git a/usermods/usermod_v2_four_line_display_ALT/readme.md b/usermods/usermod_v2_four_line_display_ALT/readme.md index a8f386dac..39bb5d28e 100644 --- a/usermods/usermod_v2_four_line_display_ALT/readme.md +++ b/usermods/usermod_v2_four_line_display_ALT/readme.md @@ -1,16 +1,8 @@ # I2C/SPI 4 Line Display Usermod ALT -Thank you to the authors of the original version of these usermods. It would not have been possible without them! -"usermod_v2_four_line_display" -"usermod_v2_rotary_encoder_ui" +This usermod could be used in compination with `usermod_v2_rotary_encoder_ui_ALT`. -The core of these usermods are a copy of the originals. The main changes are to the FourLineDisplay usermod. -The display usermod UI has been completely changed. - - -The changes made to the RotaryEncoder usermod were made to support the new UI in the display usermod. -Without the display, it functions identical to the original. -The original "usermod_v2_auto_save" will not work with the display just yet. +## Functionalities Press the encoder to cycle through the options: * Brightness @@ -18,26 +10,18 @@ Press the encoder to cycle through the options: * Intensity * Palette * Effect -* Main Color (only if display is used) -* Saturation (only if display is used) +* Main Color +* Saturation -Press and hold the encoder to display Network Info. If AP is active, it will display AP, SSID and password +Press and hold the encoder to display Network Info. If AP is active, it will display the AP, SSID and Password -Also shows if the timer is enabled +Also shows if the timer is enabled. [See the pair of usermods in action](https://www.youtube.com/watch?v=ulZnBt9z3TI) ## Installation -Please refer to the original `usermod_v2_rotary_encoder_ui` readme for the main instructions. - -Copy the example `platformio_override.sample.ini` from the usermod_v2_rotary_encoder_ui_ALT folder to the root directory of your particular build and rename it to `platformio_override.ini`. - -This file should be placed in the same directory as `platformio.ini`. - -Then, to activate this alternative usermod, add `#define USE_ALT_DISPlAY` (NOTE: CASE SENSITIVE) to the `usermods_list.cpp` file, - or add `-D USE_ALT_DISPlAY` to the original `platformio_override.ini.sample` file - +Copy the example `platformio_override.sample.ini` to the root directory of your particular build. ## Configuration diff --git a/usermods/usermod_v2_rotary_encoder_ui_ALT/platformio_override.sample.ini b/usermods/usermod_v2_rotary_encoder_ui_ALT/platformio_override.sample.ini new file mode 100644 index 000000000..8a88fd6b5 --- /dev/null +++ b/usermods/usermod_v2_rotary_encoder_ui_ALT/platformio_override.sample.ini @@ -0,0 +1,14 @@ +[platformio] +default_envs = esp32dev + +[env:esp32dev] +board = esp32dev +platform = ${esp32.platform} +build_unflags = ${common.build_unflags} +build_flags = + ${common.build_flags_esp32} + -D USERMOD_ROTARY_ENCODER_UI + -D USERMOD_ROTARY_ENCODER_GPIO=INPUT + -D ENCODER_DT_PIN=21 + -D ENCODER_CLK_PIN=23 + -D ENCODER_SW_PIN=0 diff --git a/usermods/usermod_v2_rotary_encoder_ui_ALT/readme.md b/usermods/usermod_v2_rotary_encoder_ui_ALT/readme.md index 10db879fb..c46e87663 100644 --- a/usermods/usermod_v2_rotary_encoder_ui_ALT/readme.md +++ b/usermods/usermod_v2_rotary_encoder_ui_ALT/readme.md @@ -1,16 +1,8 @@ # Rotary Encoder UI Usermod ALT -Thank you to the authors of the original version of these usermods. It would not have been possible without them! -"usermod_v2_four_line_display" -"usermod_v2_rotary_encoder_ui" +This usermod supports the UI of the `usermod_v2_rotary_encoder_ui_ALT`. -The core of these usermods are a copy of the originals. The main changes are to the FourLineDisplay usermod. -The display usermod UI has been completely changed. - - -The changes made to the RotaryEncoder usermod were made to support the new UI in the display usermod. -Without the display, it functions identical to the original. -The original "usermod_v2_auto_save" will not work with the display just yet. +## Functionalities Press the encoder to cycle through the options: * Brightness @@ -21,8 +13,7 @@ Press the encoder to cycle through the options: * Main Color (only if display is used) * Saturation (only if display is used) -Press and hold the encoder to display Network Info - if AP is active, it will display the AP, SSID and Password +Press and hold the encoder to display Network Info. If AP is active, it will display the AP, SSID and Password Also shows if the timer is enabled. @@ -30,9 +21,7 @@ Also shows if the timer is enabled. ## Installation -Copy the example `platformio_override.sample.ini` to the root directory of your particular build and rename it to `platformio_override.ini`. - -To activate this alternative usermod, add `#define USE_ALT_DISPlAY` (NOTE: CASE SENSITIVE) to the `usermods_list.cpp` file, or add `-D USE_ALT_DISPlAY` to your `platformio_override.ini` file +Copy the example `platformio_override.sample.ini` to the root directory of your particular build. ### Define Your Options @@ -40,7 +29,6 @@ To activate this alternative usermod, add `#define USE_ALT_DISPlAY` (NOTE: CASE * `USERMOD_FOUR_LINE_DISPLAY` - define this to have this the Four Line Display mod included wled00\usermods_list.cpp also tells this usermod that the display is available (see the Four Line Display usermod `readme.md` for more details) -* `USE_ALT_DISPlAY` - Mandatory to use Four Line Display * `ENCODER_DT_PIN` - defaults to 18 * `ENCODER_CLK_PIN` - defaults to 5 * `ENCODER_SW_PIN` - defaults to 19 @@ -50,7 +38,7 @@ To activate this alternative usermod, add `#define USE_ALT_DISPlAY` (NOTE: CASE ### PlatformIO requirements -Note: the Four Line Display usermod requires the libraries `U8g2` and `Wire`. +No special requirements. ## Change Log From 798c398f23f9ea2c6e826acafa5a4a81ce35945a Mon Sep 17 00:00:00 2001 From: maxi4329 Date: Sat, 26 Oct 2024 20:33:38 +0200 Subject: [PATCH 072/287] specified required nodejs ver --- package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/package.json b/package.json index d76d87687..0c016329b 100644 --- a/package.json +++ b/package.json @@ -27,5 +27,8 @@ "html-minifier-terser": "^7.2.0", "inliner": "^1.13.1", "nodemon": "^3.1.7" + }, + "engines": { + "node": ">=20.0.0" } } From 7c6bc5c4211c5c45342b2b833bf3d04991ee76ea Mon Sep 17 00:00:00 2001 From: maxi4329 Date: Sat, 26 Oct 2024 21:07:17 +0200 Subject: [PATCH 073/287] indened formating --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0c016329b..35fda6002 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,6 @@ "nodemon": "^3.1.7" }, "engines": { - "node": ">=20.0.0" + "node": ">=21.0.0" } } From d05c358fd23d86697ec343b9c46c231d30346fcb Mon Sep 17 00:00:00 2001 From: maxi4329 Date: Sun, 27 Oct 2024 11:13:56 +0100 Subject: [PATCH 074/287] version changed to 20 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 35fda6002..0c016329b 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,6 @@ "nodemon": "^3.1.7" }, "engines": { - "node": ">=21.0.0" + "node": ">=20.0.0" } } From fa053b7e60effbbb76dc49822003ea5acb4b4dcf Mon Sep 17 00:00:00 2001 From: ingDIY <10012263+ingDIY@users.noreply.github.com> Date: Tue, 29 Oct 2024 01:14:02 +0100 Subject: [PATCH 075/287] Update platformio_override.sample.ini fixed back U8g2 version --- .../platformio_override.sample.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usermods/usermod_v2_four_line_display_ALT/platformio_override.sample.ini b/usermods/usermod_v2_four_line_display_ALT/platformio_override.sample.ini index 9010648b3..e59637453 100644 --- a/usermods/usermod_v2_four_line_display_ALT/platformio_override.sample.ini +++ b/usermods/usermod_v2_four_line_display_ALT/platformio_override.sample.ini @@ -14,5 +14,5 @@ build_flags = lib_deps = ${esp32.lib_deps} - U8g2 + U8g2@~2.34.4 Wire From 451cd4c74aff2faff9ab11f746f9e454ab10bd2e Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Fri, 1 Nov 2024 23:19:38 +0100 Subject: [PATCH 076/287] Improved framerate control in strip.show(), strip.service() * separate fps calculation (strip.show) from framerate control (strio.service) * improved condition for early exit in strip.show * make MIN_SHOW_DELAY depend on target fps * strip.show consideres complete time for effect calculation + show; old code wrongly used the time between completion of last show and start of next effect drawing, causing unexpected slowdown * add "unlimited FPS mode" for testing * increase warning limits for "slow strip" and "slow effects" --- wled00/FX.h | 22 +++++++++++++++++----- wled00/FX_fcn.cpp | 37 +++++++++++++++++++++++++++---------- 2 files changed, 44 insertions(+), 15 deletions(-) diff --git a/wled00/FX.h b/wled00/FX.h index ad39a7c06..acdb62c81 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -44,8 +44,20 @@ /* Not used in all effects yet */ #define WLED_FPS 42 -#define FRAMETIME_FIXED (1000/WLED_FPS) #define FRAMETIME strip.getFrameTime() +#if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3) // all ESP32 except -C3(very slow, untested) + #define FRAMETIME_FIXED (strip.getFrameTime() < 10 ? 12 : 24) // allow faster FRAMETIME_FIXED when target FPS >= 100 + #define MIN_SHOW_DELAY (max(2, (_frametime*5)/8)) // supports higher framerates and better animation control -- 5/8 = 62% + // used to initialize for strip attributes: + #define WLED_FPS_SLOW 42 + #define FRAMETIME_FIXED_SLOW (24) // 1000/42 = 24ms +#else + #define FRAMETIME_FIXED (1000/WLED_FPS) + #define MIN_SHOW_DELAY (_frametime < 16 ? 8 : 15) // legacy MIN_SHOW_DELAY - creates more idle loops, but reduces framerates + #define WLED_FPS_SLOW WLED_FPS + #define FRAMETIME_FIXED_SLOW FRAMETIME_FIXED +#endif +#define FPS_UNLIMITED 119 /* each segment uses 82 bytes of SRAM memory, so if you're application fails because of insufficient memory, decreasing MAX_NUM_SEGMENTS may help */ @@ -68,8 +80,6 @@ assuming each segment uses the same amount of data. 256 for ESP8266, 640 for ESP32. */ #define FAIR_DATA_PER_SEG (MAX_SEGMENT_DATA / strip.getMaxSegments()) -#define MIN_SHOW_DELAY (_frametime < 16 ? 8 : 15) - #define NUM_COLORS 3 /* number of colors per segment */ #define SEGMENT strip._segments[strip.getCurrSegmentId()] #define SEGENV strip._segments[strip.getCurrSegmentId()] @@ -727,8 +737,8 @@ class WS2812FX { // 96 bytes _length(DEFAULT_LED_COUNT), _brightness(DEFAULT_BRIGHTNESS), _transitionDur(750), - _targetFps(WLED_FPS), - _frametime(FRAMETIME_FIXED), + _targetFps(WLED_FPS_SLOW), + _frametime(FRAMETIME_FIXED_SLOW), _cumulativeFps(2), _isServicing(false), _isOffRefreshRequired(false), @@ -739,6 +749,7 @@ class WS2812FX { // 96 bytes customMappingTable(nullptr), customMappingSize(0), _lastShow(0), + _lastServiceShow(0), _segment_index(0), _mainSegment(0) { @@ -949,6 +960,7 @@ class WS2812FX { // 96 bytes uint16_t customMappingSize; unsigned long _lastShow; + unsigned long _lastServiceShow; uint8_t _segment_index; uint8_t _mainSegment; diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 949b6a932..5856ad786 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1309,11 +1309,23 @@ void WS2812FX::finalizeInit() { void WS2812FX::service() { unsigned long nowUp = millis(); // Be aware, millis() rolls over every 49 days now = nowUp + timebase; - if (nowUp - _lastShow < MIN_SHOW_DELAY || _suspend) return; + if (_suspend) return; + unsigned long elapsed = nowUp - _lastServiceShow; + + #if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3) + if (elapsed < 2) return; // keep wifi alive + if ( !_triggered && (_targetFps < FPS_UNLIMITED) && (_targetFps > 0)) { + if (elapsed < MIN_SHOW_DELAY) return; // WLEDMM too early for service + } + #else // legacy + if (nowUp - _lastShow < MIN_SHOW_DELAY) return; + #endif + bool doShow = false; _isServicing = true; _segment_index = 0; + unsigned speedLimit = (_targetFps < FPS_UNLIMITED) ? (0.85f * FRAMETIME) : 1; // lower limit for effect frametime for (segment &seg : _segments) { if (_suspend) return; // immediately stop processing segments if suspend requested during service() @@ -1326,10 +1338,10 @@ void WS2812FX::service() { if (!seg.isActive()) continue; // last condition ensures all solid segments are updated at the same time - if (nowUp > seg.next_time || _triggered || (doShow && seg.mode == FX_MODE_STATIC)) + if (nowUp >= seg.next_time || _triggered || (doShow && seg.mode == FX_MODE_STATIC)) { doShow = true; - unsigned delay = FRAMETIME; + unsigned frameDelay = FRAMETIME; if (!seg.freeze) { //only run effect function if not frozen int oldCCT = BusManager::getSegmentCCT(); // store original CCT value (actually it is not Segment based) @@ -1349,7 +1361,8 @@ void WS2812FX::service() { // overwritten by later effect. To enable seamless blending for every effect, additional LED buffer // would need to be allocated for each effect and then blended together for each pixel. [[maybe_unused]] uint8_t tmpMode = seg.currentMode(); // this will return old mode while in transition - delay = (*_mode[seg.mode])(); // run new/current mode + frameDelay = (*_mode[seg.mode])(); // run new/current mode + if (frameDelay < speedLimit) frameDelay = FRAMETIME; // limit effects that want to go faster than target FPS #ifndef WLED_DISABLE_MODE_BLEND if (modeBlending && seg.mode != tmpMode) { Segment::tmpsegd_t _tmpSegData; @@ -1358,16 +1371,16 @@ void WS2812FX::service() { _virtualSegmentLength = seg.virtualLength(); // update SEGLEN (mapping may have changed) unsigned d2 = (*_mode[tmpMode])(); // run old mode seg.restoreSegenv(_tmpSegData); // restore mode state (will also update transitional state) - delay = MIN(delay,d2); // use shortest delay + frameDelay = min(frameDelay,d2); // use shortest delay Segment::modeBlend(false); // unset semaphore } #endif seg.call++; - if (seg.isInTransition() && delay > FRAMETIME) delay = FRAMETIME; // force faster updates during transition + if (seg.isInTransition() && frameDelay > FRAMETIME) frameDelay = FRAMETIME; // force faster updates during transition BusManager::setSegmentCCT(oldCCT); // restore old CCT for ABL adjustments } - seg.next_time = nowUp + delay; + seg.next_time = nowUp + frameDelay; } _segment_index++; } @@ -1376,15 +1389,16 @@ void WS2812FX::service() { _triggered = false; #ifdef WLED_DEBUG - if (millis() - nowUp > _frametime) DEBUG_PRINTF_P(PSTR("Slow effects %u/%d.\n"), (unsigned)(millis()-nowUp), (int)_frametime); + if (millis() - nowUp > _frametime*2) DEBUG_PRINTF_P(PSTR("Slow effects %u/%d.\n"), (unsigned)(millis()-nowUp), (int)_frametime); #endif if (doShow) { yield(); Segment::handleRandomPalette(); // slowly transition random palette; move it into for loop when each segment has individual random palette show(); + _lastServiceShow = nowUp; // correct timestamp, for better FPS control } #ifdef WLED_DEBUG - if (millis() - nowUp > _frametime) DEBUG_PRINTF_P(PSTR("Slow strip %u/%d.\n"), (unsigned)(millis()-nowUp), (int)_frametime); + if (millis() - nowUp > _frametime*2) DEBUG_PRINTF_P(PSTR("Slow strip %u/%d.\n"), (unsigned)(millis()-nowUp), (int)_frametime); #endif } @@ -1404,18 +1418,19 @@ void WS2812FX::show() { // avoid race condition, capture _callback value show_callback callback = _callback; if (callback) callback(); + unsigned long showNow = millis(); // some buses send asynchronously and this method will return before // all of the data has been sent. // See https://github.com/Makuna/NeoPixelBus/wiki/ESP32-NeoMethods#neoesp32rmt-methods BusManager::show(); - unsigned long showNow = millis(); size_t diff = showNow - _lastShow; size_t fpsCurr = 200; if (diff > 0) fpsCurr = 1000 / diff; _cumulativeFps = (3 * _cumulativeFps + fpsCurr +2) >> 2; // "+2" for proper rounding (2/4 = 0.5) _lastShow = showNow; + _lastServiceShow = showNow; } /** @@ -1438,6 +1453,8 @@ uint16_t WS2812FX::getFps() const { void WS2812FX::setTargetFps(uint8_t fps) { if (fps > 0 && fps <= 120) _targetFps = fps; _frametime = 1000 / _targetFps; + if (_frametime < 1) _frametime = 1; // better safe than sorry + if (fps >= FPS_UNLIMITED) _frametime = 3; // unlimited mode } void WS2812FX::setMode(uint8_t segid, uint8_t m) { From 50934e6840995154109ab7d532dbb61af0377f90 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Sat, 2 Nov 2024 18:16:51 +0100 Subject: [PATCH 077/287] adressing some review comments * keep FRAMETIME_FIXED as a fixed value * remove WLED_FPS_SLOW and FRAMETIME_FIXED_SLOW * explicit test "(_targetFps != FPS_UNLIMITED)" for debug messages * don't modify _lastServiceShow in show() * test for "fps == FPS_UNLIMITED" explicitly, so we could pick a different magic number later --- wled00/FX.h | 13 ++++--------- wled00/FX_fcn.cpp | 12 +++++------- 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/wled00/FX.h b/wled00/FX.h index acdb62c81..1f0df9da3 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -44,20 +44,15 @@ /* Not used in all effects yet */ #define WLED_FPS 42 +#define FRAMETIME_FIXED (1000/WLED_FPS) #define FRAMETIME strip.getFrameTime() #if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3) // all ESP32 except -C3(very slow, untested) - #define FRAMETIME_FIXED (strip.getFrameTime() < 10 ? 12 : 24) // allow faster FRAMETIME_FIXED when target FPS >= 100 #define MIN_SHOW_DELAY (max(2, (_frametime*5)/8)) // supports higher framerates and better animation control -- 5/8 = 62% // used to initialize for strip attributes: - #define WLED_FPS_SLOW 42 - #define FRAMETIME_FIXED_SLOW (24) // 1000/42 = 24ms #else - #define FRAMETIME_FIXED (1000/WLED_FPS) #define MIN_SHOW_DELAY (_frametime < 16 ? 8 : 15) // legacy MIN_SHOW_DELAY - creates more idle loops, but reduces framerates - #define WLED_FPS_SLOW WLED_FPS - #define FRAMETIME_FIXED_SLOW FRAMETIME_FIXED #endif -#define FPS_UNLIMITED 119 +#define FPS_UNLIMITED 120 /* each segment uses 82 bytes of SRAM memory, so if you're application fails because of insufficient memory, decreasing MAX_NUM_SEGMENTS may help */ @@ -737,8 +732,8 @@ class WS2812FX { // 96 bytes _length(DEFAULT_LED_COUNT), _brightness(DEFAULT_BRIGHTNESS), _transitionDur(750), - _targetFps(WLED_FPS_SLOW), - _frametime(FRAMETIME_FIXED_SLOW), + _targetFps(WLED_FPS), + _frametime(FRAMETIME_FIXED), _cumulativeFps(2), _isServicing(false), _isOffRefreshRequired(false), diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 5856ad786..3972dad2c 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1314,7 +1314,7 @@ void WS2812FX::service() { #if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3) if (elapsed < 2) return; // keep wifi alive - if ( !_triggered && (_targetFps < FPS_UNLIMITED) && (_targetFps > 0)) { + if ( !_triggered && (_targetFps != FPS_UNLIMITED) && (_targetFps > 0)) { if (elapsed < MIN_SHOW_DELAY) return; // WLEDMM too early for service } #else // legacy @@ -1325,7 +1325,7 @@ void WS2812FX::service() { _isServicing = true; _segment_index = 0; - unsigned speedLimit = (_targetFps < FPS_UNLIMITED) ? (0.85f * FRAMETIME) : 1; // lower limit for effect frametime + unsigned speedLimit = (_targetFps != FPS_UNLIMITED) ? (0.85f * FRAMETIME) : 1; // lower limit for effect frametime for (segment &seg : _segments) { if (_suspend) return; // immediately stop processing segments if suspend requested during service() @@ -1389,7 +1389,7 @@ void WS2812FX::service() { _triggered = false; #ifdef WLED_DEBUG - if (millis() - nowUp > _frametime*2) DEBUG_PRINTF_P(PSTR("Slow effects %u/%d.\n"), (unsigned)(millis()-nowUp), (int)_frametime); + if ((_targetFps != FPS_UNLIMITED) && (millis() - nowUp > _frametime)) DEBUG_PRINTF_P(PSTR("Slow effects %u/%d.\n"), (unsigned)(millis()-nowUp), (int)_frametime); #endif if (doShow) { yield(); @@ -1398,7 +1398,7 @@ void WS2812FX::service() { _lastServiceShow = nowUp; // correct timestamp, for better FPS control } #ifdef WLED_DEBUG - if (millis() - nowUp > _frametime*2) DEBUG_PRINTF_P(PSTR("Slow strip %u/%d.\n"), (unsigned)(millis()-nowUp), (int)_frametime); + if ((_targetFps != FPS_UNLIMITED) && (millis() - nowUp > _frametime)) DEBUG_PRINTF_P(PSTR("Slow strip %u/%d.\n"), (unsigned)(millis()-nowUp), (int)_frametime); #endif } @@ -1430,7 +1430,6 @@ void WS2812FX::show() { if (diff > 0) fpsCurr = 1000 / diff; _cumulativeFps = (3 * _cumulativeFps + fpsCurr +2) >> 2; // "+2" for proper rounding (2/4 = 0.5) _lastShow = showNow; - _lastServiceShow = showNow; } /** @@ -1453,8 +1452,7 @@ uint16_t WS2812FX::getFps() const { void WS2812FX::setTargetFps(uint8_t fps) { if (fps > 0 && fps <= 120) _targetFps = fps; _frametime = 1000 / _targetFps; - if (_frametime < 1) _frametime = 1; // better safe than sorry - if (fps >= FPS_UNLIMITED) _frametime = 3; // unlimited mode + if (fps == FPS_UNLIMITED) _frametime = 3; // unlimited mode } void WS2812FX::setMode(uint8_t segid, uint8_t m) { From cf1630a94a3de7b59e2dd21562a607627c9fa2d2 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Mon, 4 Nov 2024 19:49:43 +0100 Subject: [PATCH 078/287] 0 FPS = unlimited --- wled00/FX.h | 2 +- wled00/FX_fcn.cpp | 8 ++++---- wled00/data/settings_leds.htm | 8 +++++++- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/wled00/FX.h b/wled00/FX.h index 1f0df9da3..5c80686d6 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -52,7 +52,7 @@ #else #define MIN_SHOW_DELAY (_frametime < 16 ? 8 : 15) // legacy MIN_SHOW_DELAY - creates more idle loops, but reduces framerates #endif -#define FPS_UNLIMITED 120 +#define FPS_UNLIMITED 0 /* each segment uses 82 bytes of SRAM memory, so if you're application fails because of insufficient memory, decreasing MAX_NUM_SEGMENTS may help */ diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 3972dad2c..30882c4e7 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1314,7 +1314,7 @@ void WS2812FX::service() { #if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3) if (elapsed < 2) return; // keep wifi alive - if ( !_triggered && (_targetFps != FPS_UNLIMITED) && (_targetFps > 0)) { + if ( !_triggered && (_targetFps != FPS_UNLIMITED)) { if (elapsed < MIN_SHOW_DELAY) return; // WLEDMM too early for service } #else // legacy @@ -1450,9 +1450,9 @@ uint16_t WS2812FX::getFps() const { } void WS2812FX::setTargetFps(uint8_t fps) { - if (fps > 0 && fps <= 120) _targetFps = fps; - _frametime = 1000 / _targetFps; - if (fps == FPS_UNLIMITED) _frametime = 3; // unlimited mode + if (fps <= 120) _targetFps = fps; + if (_targetFps > 0) _frametime = 1000 / _targetFps; + else _frametime = 3; // unlimited mode } void WS2812FX::setMode(uint8_t segid, uint8_t m) { diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index 6be5becd1..35bbbd312 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -380,6 +380,10 @@ gId('psu').innerHTML = s; gId('psu2').innerHTML = s2; gId("json").style.display = d.Sf.IT.value==8 ? "" : "none"; + + // show/hide unlimited FPS message + gId('fpsNone').style.display = (d.Sf.FR.value == 0) ? 'block':'none'; + gId('fpsHelp').style.display = (d.Sf.FR.value == 0)? 'none':'block'; } function lastEnd(i) { if (i-- < 1) return 0; @@ -870,7 +874,9 @@ Swap:
- Target refresh rate: FPS + Target refresh rate: FPS +
use 0 for unlimited
+
Config template:

From 029293a08621df71ed0546ed5bf3c0e4cc9d49c7 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Mon, 4 Nov 2024 20:11:10 +0100 Subject: [PATCH 079/287] simplify sheduler logic * _frametime ensures that effects are not serviced too often * MIN_SHOW_DELAY is the minimum allowed FRAMETIME that can be requested by effects --- wled00/FX.h | 3 +-- wled00/FX_fcn.cpp | 13 ++++--------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/wled00/FX.h b/wled00/FX.h index 5c80686d6..d749d7796 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -47,8 +47,7 @@ #define FRAMETIME_FIXED (1000/WLED_FPS) #define FRAMETIME strip.getFrameTime() #if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3) // all ESP32 except -C3(very slow, untested) - #define MIN_SHOW_DELAY (max(2, (_frametime*5)/8)) // supports higher framerates and better animation control -- 5/8 = 62% - // used to initialize for strip attributes: + #define MIN_SHOW_DELAY 3 // supports higher framerates and better animation control #else #define MIN_SHOW_DELAY (_frametime < 16 ? 8 : 15) // legacy MIN_SHOW_DELAY - creates more idle loops, but reduces framerates #endif diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 30882c4e7..6bc61d10d 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1312,20 +1312,15 @@ void WS2812FX::service() { if (_suspend) return; unsigned long elapsed = nowUp - _lastServiceShow; - #if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3) - if (elapsed < 2) return; // keep wifi alive - if ( !_triggered && (_targetFps != FPS_UNLIMITED)) { - if (elapsed < MIN_SHOW_DELAY) return; // WLEDMM too early for service + if (elapsed < 2) return; // keep wifi alive - no matter if triggered or unlimited + if ( !_triggered && (_targetFps != FPS_UNLIMITED)) { // unlimited mode = no frametime + if (elapsed < _frametime) return; // too early for service } - #else // legacy - if (nowUp - _lastShow < MIN_SHOW_DELAY) return; - #endif bool doShow = false; _isServicing = true; _segment_index = 0; - unsigned speedLimit = (_targetFps != FPS_UNLIMITED) ? (0.85f * FRAMETIME) : 1; // lower limit for effect frametime for (segment &seg : _segments) { if (_suspend) return; // immediately stop processing segments if suspend requested during service() @@ -1362,7 +1357,6 @@ void WS2812FX::service() { // would need to be allocated for each effect and then blended together for each pixel. [[maybe_unused]] uint8_t tmpMode = seg.currentMode(); // this will return old mode while in transition frameDelay = (*_mode[seg.mode])(); // run new/current mode - if (frameDelay < speedLimit) frameDelay = FRAMETIME; // limit effects that want to go faster than target FPS #ifndef WLED_DISABLE_MODE_BLEND if (modeBlending && seg.mode != tmpMode) { Segment::tmpsegd_t _tmpSegData; @@ -1375,6 +1369,7 @@ void WS2812FX::service() { Segment::modeBlend(false); // unset semaphore } #endif + frameDelay = max(frameDelay, unsigned(MIN_SHOW_DELAY)); // limit effects that want to go faster than target FPS seg.call++; if (seg.isInTransition() && frameDelay > FRAMETIME) frameDelay = FRAMETIME; // force faster updates during transition BusManager::setSegmentCCT(oldCCT); // restore old CCT for ABL adjustments From ab7b2d729e68099b140473079dc22ea9b186d7b5 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Tue, 5 Nov 2024 12:24:20 +0100 Subject: [PATCH 080/287] use class="warn" for unlimited mode message --- wled00/data/settings_leds.htm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index 35bbbd312..caeaacea9 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -876,7 +876,7 @@ Swap:
Target refresh rate: FPS
use 0 for unlimited
- +
Config template:

From 1e761c31bdd53850aca805c9400ef24b10f3afda Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Wed, 6 Nov 2024 22:09:33 +0100 Subject: [PATCH 081/287] simpler hight FPS warning * removed "use 0 for unlimited" * added "high FPS mode is experimental" warning * added "backup first!" warning * added anchor #backup to sec page --- wled00/data/settings_leds.htm | 10 ++++++---- wled00/data/settings_sec.htm | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index caeaacea9..55f8122e8 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -381,9 +381,10 @@ gId('psu2').innerHTML = s2; gId("json").style.display = d.Sf.IT.value==8 ? "" : "none"; - // show/hide unlimited FPS message + // show/hide FPS warning messages gId('fpsNone').style.display = (d.Sf.FR.value == 0) ? 'block':'none'; - gId('fpsHelp').style.display = (d.Sf.FR.value == 0)? 'none':'block'; + gId('fpsWarn').style.display = (d.Sf.FR.value == 0) || (d.Sf.FR.value >= 80) ? 'block':'none'; + gId('fpsHigh').style.display = (d.Sf.FR.value >= 80) ? 'block':'none'; } function lastEnd(i) { if (i-- < 1) return 0; @@ -875,8 +876,9 @@ Swap:
Target refresh rate: FPS -
use 0 for unlimited
- + + +
Config template:

diff --git a/wled00/data/settings_sec.htm b/wled00/data/settings_sec.htm index fa75882c0..5181858d5 100644 --- a/wled00/data/settings_sec.htm +++ b/wled00/data/settings_sec.htm @@ -57,7 +57,7 @@

Software Update


Enable ArduinoOTA: -
+

Backup & Restore

⚠ Restoring presets/configuration will OVERWRITE your current presets/configuration.
Incorrect upload or configuration may require a factory reset or re-flashing of your ESP.
From 6ff5c88ebf0525f8101c49f19702c0ee0cec0e65 Mon Sep 17 00:00:00 2001 From: netmindz Date: Thu, 7 Nov 2024 08:17:08 +0000 Subject: [PATCH 082/287] List ESP32 first --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 11c1733f8..595b9ee0b 100644 --- a/readme.md +++ b/readme.md @@ -12,7 +12,7 @@ # Welcome to my project WLED! ✨ -A fast and feature-rich implementation of an ESP8266/ESP32 webserver to control NeoPixel (WS2812B, WS2811, SK6812) LEDs or also SPI based chipsets like the WS2801 and APA102! +A fast and feature-rich implementation of an ESP32 and ESP8266 webserver to control NeoPixel (WS2812B, WS2811, SK6812) LEDs or also SPI based chipsets like the WS2801 and APA102! ## ⚙️ Features - WS2812FX library with more than 100 special effects From 0404ec988137acca997e620384cf446b19dce350 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Thu, 7 Nov 2024 23:15:39 +0100 Subject: [PATCH 083/287] changes in response to feedback from @willmmiles * MIN_SHOW_DELAY -> MIN_FRAME_DELAY * allow up to 250 for target FPS * minor cleanup * added specific MIN_FRAME_DELAY for -S2 --- wled00/FX.h | 10 ++++++---- wled00/FX_fcn.cpp | 11 +++++------ wled00/data/settings_leds.htm | 2 +- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/wled00/FX.h b/wled00/FX.h index d749d7796..56a0c9bd0 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -46,10 +46,12 @@ #define WLED_FPS 42 #define FRAMETIME_FIXED (1000/WLED_FPS) #define FRAMETIME strip.getFrameTime() -#if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3) // all ESP32 except -C3(very slow, untested) - #define MIN_SHOW_DELAY 3 // supports higher framerates and better animation control +#if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S2) + #define MIN_FRAME_DELAY 2 // minimum wait between repaints, to keep other functions like WiFi alive +#elif defined(CONFIG_IDF_TARGET_ESP32S2) + #define MIN_FRAME_DELAY 4 // S2 is slower than normal esp32, and only has one core #else - #define MIN_SHOW_DELAY (_frametime < 16 ? 8 : 15) // legacy MIN_SHOW_DELAY - creates more idle loops, but reduces framerates + #define MIN_FRAME_DELAY 8 // 8266 legacy MIN_SHOW_DELAY #endif #define FPS_UNLIMITED 0 @@ -842,7 +844,7 @@ class WS2812FX { // 96 bytes getMappedPixelIndex(uint16_t index) const; inline uint16_t getFrameTime() const { return _frametime; } // returns amount of time a frame should take (in ms) - inline uint16_t getMinShowDelay() const { return MIN_SHOW_DELAY; } // returns minimum amount of time strip.service() can be delayed (constant) + inline uint16_t getMinShowDelay() const { return MIN_FRAME_DELAY; } // returns minimum amount of time strip.service() can be delayed (constant) inline uint16_t getLength() const { return _length; } // returns actual amount of LEDs on a strip (2D matrix may have less LEDs than W*H) inline uint16_t getTransition() const { return _transitionDur; } // returns currently set transition time (in ms) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 6bc61d10d..f4b46dade 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1312,7 +1312,7 @@ void WS2812FX::service() { if (_suspend) return; unsigned long elapsed = nowUp - _lastServiceShow; - if (elapsed < 2) return; // keep wifi alive - no matter if triggered or unlimited + if (elapsed <= MIN_FRAME_DELAY) return; // keep wifi alive - no matter if triggered or unlimited if ( !_triggered && (_targetFps != FPS_UNLIMITED)) { // unlimited mode = no frametime if (elapsed < _frametime) return; // too early for service } @@ -1369,7 +1369,6 @@ void WS2812FX::service() { Segment::modeBlend(false); // unset semaphore } #endif - frameDelay = max(frameDelay, unsigned(MIN_SHOW_DELAY)); // limit effects that want to go faster than target FPS seg.call++; if (seg.isInTransition() && frameDelay > FRAMETIME) frameDelay = FRAMETIME; // force faster updates during transition BusManager::setSegmentCCT(oldCCT); // restore old CCT for ABL adjustments @@ -1390,7 +1389,7 @@ void WS2812FX::service() { yield(); Segment::handleRandomPalette(); // slowly transition random palette; move it into for loop when each segment has individual random palette show(); - _lastServiceShow = nowUp; // correct timestamp, for better FPS control + _lastServiceShow = nowUp; // update timestamp, for precise FPS control } #ifdef WLED_DEBUG if ((_targetFps != FPS_UNLIMITED) && (millis() - nowUp > _frametime)) DEBUG_PRINTF_P(PSTR("Slow strip %u/%d.\n"), (unsigned)(millis()-nowUp), (int)_frametime); @@ -1445,9 +1444,9 @@ uint16_t WS2812FX::getFps() const { } void WS2812FX::setTargetFps(uint8_t fps) { - if (fps <= 120) _targetFps = fps; + if (fps <= 250) _targetFps = fps; if (_targetFps > 0) _frametime = 1000 / _targetFps; - else _frametime = 3; // unlimited mode + else _frametime = MIN_FRAME_DELAY; // unlimited mode } void WS2812FX::setMode(uint8_t segid, uint8_t m) { @@ -1495,7 +1494,7 @@ void WS2812FX::setBrightness(uint8_t b, bool direct) { BusManager::setBrightness(b); if (!direct) { unsigned long t = millis(); - if (_segments[0].next_time > t + 22 && t - _lastShow > MIN_SHOW_DELAY) trigger(); //apply brightness change immediately if no refresh soon + if (_segments[0].next_time > t + 22 && t - _lastShow > MIN_FRAME_DELAY) trigger(); //apply brightness change immediately if no refresh soon } } diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index 55f8122e8..b467d5b9b 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -875,7 +875,7 @@ Swap:
- Target refresh rate: FPS + Target refresh rate: FPS From 001e2ad2875742b51861ba826baa9a9727698ea0 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Fri, 8 Nov 2024 19:35:42 +0100 Subject: [PATCH 084/287] adjust audioreactive for the new FRAME_DELAY logic minor --- usermods/audioreactive/audio_reactive.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h index ad449fc83..9c463e0a1 100644 --- a/usermods/audioreactive/audio_reactive.h +++ b/usermods/audioreactive/audio_reactive.h @@ -75,7 +75,7 @@ static uint8_t soundAgc = 0; // Automagic gain control: 0 - n //static float volumeSmth = 0.0f; // either sampleAvg or sampleAgc depending on soundAgc; smoothed sample static float FFT_MajorPeak = 1.0f; // FFT: strongest (peak) frequency static float FFT_Magnitude = 0.0f; // FFT: volume (magnitude) of peak frequency -static bool samplePeak = false; // Boolean flag for peak - used in effects. Responding routine may reset this flag. Auto-reset after strip.getMinShowDelay() +static bool samplePeak = false; // Boolean flag for peak - used in effects. Responding routine may reset this flag. Auto-reset after strip.getFrameTime() static bool udpSamplePeak = false; // Boolean flag for peak. Set at the same time as samplePeak, but reset by transmitAudioData static unsigned long timeOfPeak = 0; // time of last sample peak detection. static uint8_t fftResult[NUM_GEQ_CHANNELS]= {0};// Our calculated freq. channel result table to be used by effects @@ -536,8 +536,8 @@ static void detectSamplePeak(void) { #endif static void autoResetPeak(void) { - uint16_t MinShowDelay = MAX(50, strip.getMinShowDelay()); // Fixes private class variable compiler error. Unsure if this is the correct way of fixing the root problem. -THATDONFC - if (millis() - timeOfPeak > MinShowDelay) { // Auto-reset of samplePeak after a complete frame has passed. + uint16_t peakDelay = max(uint16_t(50), strip.getFrameTime()); + if (millis() - timeOfPeak > peakDelay) { // Auto-reset of samplePeak after at least one complete frame has passed. samplePeak = false; if (audioSyncEnabled == 0) udpSamplePeak = false; // this is normally reset by transmitAudioData } From ef1e24cec26cdb05b7a342e7a432f03692ca521e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Kristan?= Date: Sat, 9 Nov 2024 10:42:49 +0100 Subject: [PATCH 085/287] Bugfix & code reduction - correctly clear segment spacing change - renamed Segment::setUp() to Segment::setGeometry() - removed WS2812FX::setSegment() - removed obsolete/unfunctional word clock usermod .cpp file --- .../usermod_word_clock_matrix.h | 128 ++++---- .../word-clock-matrix/word-clock-matrix.cpp | 305 ------------------ wled00/FX.h | 6 +- wled00/FX_fcn.cpp | 34 +- wled00/json.cpp | 16 +- wled00/set.cpp | 4 +- wled00/udp.cpp | 15 +- 7 files changed, 99 insertions(+), 409 deletions(-) delete mode 100644 usermods/word-clock-matrix/word-clock-matrix.cpp diff --git a/usermods/word-clock-matrix/usermod_word_clock_matrix.h b/usermods/word-clock-matrix/usermod_word_clock_matrix.h index 506c1275e..82499c0ce 100644 --- a/usermods/word-clock-matrix/usermod_word_clock_matrix.h +++ b/usermods/word-clock-matrix/usermod_word_clock_matrix.h @@ -31,14 +31,14 @@ public: //strip.getSegment(1).setOption(SEG_OPTION_SELECTED, true); //select first two segments (background color + FX settable) - WS2812FX::Segment &seg = strip.getSegment(0); + Segment &seg = strip.getSegment(0); seg.colors[0] = ((0 << 24) | ((0 & 0xFF) << 16) | ((0 & 0xFF) << 8) | ((0 & 0xFF))); strip.getSegment(0).setOption(0, false); strip.getSegment(0).setOption(2, false); //other segments are text for (int i = 1; i < 10; i++) { - WS2812FX::Segment &seg = strip.getSegment(i); + Segment &seg = strip.getSegment(i); seg.colors[0] = ((0 << 24) | ((0 & 0xFF) << 16) | ((190 & 0xFF) << 8) | ((180 & 0xFF))); strip.getSegment(i).setOption(0, true); strip.setBrightness(64); @@ -80,61 +80,61 @@ public: void displayTime(byte hour, byte minute) { bool isToHour = false; //true if minute > 30 - strip.setSegment(0, 0, 64); // background - strip.setSegment(1, 0, 2); //It is + strip.getSegment(0).setGeometry(0, 64); // background + strip.getSegment(1).setGeometry(0, 2); //It is - strip.setSegment(2, 0, 0); - strip.setSegment(3, 0, 0); //disable minutes - strip.setSegment(4, 0, 0); //past - strip.setSegment(6, 0, 0); //to - strip.setSegment(8, 0, 0); //disable o'clock + strip.getSegment(2).setGeometry(0, 0); + strip.getSegment(3).setGeometry(0, 0); //disable minutes + strip.getSegment(4).setGeometry(0, 0); //past + strip.getSegment(6).setGeometry(0, 0); //to + strip.getSegment(8).setGeometry(0, 0); //disable o'clock if (hour < 24) //valid time, display { if (minute == 30) { - strip.setSegment(2, 3, 6); //half - strip.setSegment(3, 0, 0); //minutes + strip.getSegment(2).setGeometry(3, 6); //half + strip.getSegment(3).setGeometry(0, 0); //minutes } else if (minute == 15 || minute == 45) { - strip.setSegment(3, 0, 0); //minutes + strip.getSegment(3).setGeometry(0, 0); //minutes } else if (minute == 10) { - //strip.setSegment(5, 6, 8); //ten + //strip.getSegment(5).setGeometry(6, 8); //ten } else if (minute == 5) { - //strip.setSegment(5, 16, 18); //five + //strip.getSegment(5).setGeometry(16, 18); //five } else if (minute == 0) { - strip.setSegment(3, 0, 0); //minutes + strip.getSegment(3).setGeometry(0, 0); //minutes //hourChime(); } else { - strip.setSegment(3, 18, 22); //minutes + strip.getSegment(3).setGeometry(18, 22); //minutes } //past or to? if (minute == 0) { //full hour - strip.setSegment(3, 0, 0); //disable minutes - strip.setSegment(4, 0, 0); //disable past - strip.setSegment(6, 0, 0); //disable to - strip.setSegment(8, 60, 64); //o'clock + strip.getSegment(3).setGeometry(0, 0); //disable minutes + strip.getSegment(4).setGeometry(0, 0); //disable past + strip.getSegment(6).setGeometry(0, 0); //disable to + strip.getSegment(8).setGeometry(60, 64); //o'clock } else if (minute > 34) { - //strip.setSegment(6, 22, 24); //to + //strip.getSegment(6).setGeometry(22, 24); //to //minute = 60 - minute; isToHour = true; } else { - //strip.setSegment(4, 24, 27); //past + //strip.getSegment(4).setGeometry(24, 27); //past //isToHour = false; } } @@ -143,68 +143,68 @@ public: if (minute <= 4) { - strip.setSegment(3, 0, 0); //nothing - strip.setSegment(5, 0, 0); //nothing - strip.setSegment(6, 0, 0); //nothing - strip.setSegment(8, 60, 64); //o'clock + strip.getSegment(3).setGeometry(0, 0); //nothing + strip.getSegment(5).setGeometry(0, 0); //nothing + strip.getSegment(6).setGeometry(0, 0); //nothing + strip.getSegment(8).setGeometry(60, 64); //o'clock } else if (minute <= 9) { - strip.setSegment(5, 16, 18); // five past - strip.setSegment(4, 24, 27); //past + strip.getSegment(5).setGeometry(16, 18); // five past + strip.getSegment(4).setGeometry(24, 27); //past } else if (minute <= 14) { - strip.setSegment(5, 6, 8); // ten past - strip.setSegment(4, 24, 27); //past + strip.getSegment(5).setGeometry(6, 8); // ten past + strip.getSegment(4).setGeometry(24, 27); //past } else if (minute <= 19) { - strip.setSegment(5, 8, 12); // quarter past - strip.setSegment(3, 0, 0); //minutes - strip.setSegment(4, 24, 27); //past + strip.getSegment(5).setGeometry(8, 12); // quarter past + strip.getSegment(3).setGeometry(0, 0); //minutes + strip.getSegment(4).setGeometry(24, 27); //past } else if (minute <= 24) { - strip.setSegment(5, 12, 16); // twenty past - strip.setSegment(4, 24, 27); //past + strip.getSegment(5).setGeometry(12, 16); // twenty past + strip.getSegment(4).setGeometry(24, 27); //past } else if (minute <= 29) { - strip.setSegment(5, 12, 18); // twenty-five past - strip.setSegment(4, 24, 27); //past + strip.getSegment(5).setGeometry(12, 18); // twenty-five past + strip.getSegment(4).setGeometry(24, 27); //past } else if (minute <= 34) { - strip.setSegment(5, 3, 6); // half past - strip.setSegment(3, 0, 0); //minutes - strip.setSegment(4, 24, 27); //past + strip.getSegment(5).setGeometry(3, 6); // half past + strip.getSegment(3).setGeometry(0, 0); //minutes + strip.getSegment(4).setGeometry(24, 27); //past } else if (minute <= 39) { - strip.setSegment(5, 12, 18); // twenty-five to - strip.setSegment(6, 22, 24); //to + strip.getSegment(5).setGeometry(12, 18); // twenty-five to + strip.getSegment(6).setGeometry(22, 24); //to } else if (minute <= 44) { - strip.setSegment(5, 12, 16); // twenty to - strip.setSegment(6, 22, 24); //to + strip.getSegment(5).setGeometry(12, 16); // twenty to + strip.getSegment(6).setGeometry(22, 24); //to } else if (minute <= 49) { - strip.setSegment(5, 8, 12); // quarter to - strip.setSegment(3, 0, 0); //minutes - strip.setSegment(6, 22, 24); //to + strip.getSegment(5).setGeometry(8, 12); // quarter to + strip.getSegment(3).setGeometry(0, 0); //minutes + strip.getSegment(6).setGeometry(22, 24); //to } else if (minute <= 54) { - strip.setSegment(5, 6, 8); // ten to - strip.setSegment(6, 22, 24); //to + strip.getSegment(5).setGeometry(6, 8); // ten to + strip.getSegment(6).setGeometry(22, 24); //to } else if (minute <= 59) { - strip.setSegment(5, 16, 18); // five to - strip.setSegment(6, 22, 24); //to + strip.getSegment(5).setGeometry(16, 18); // five to + strip.getSegment(6).setGeometry(22, 24); //to } //hours @@ -220,45 +220,45 @@ public: switch (hour) { case 1: - strip.setSegment(7, 27, 29); + strip.getSegment(7).setGeometry(27, 29); break; //one case 2: - strip.setSegment(7, 35, 37); + strip.getSegment(7).setGeometry(35, 37); break; //two case 3: - strip.setSegment(7, 29, 32); + strip.getSegment(7).setGeometry(29, 32); break; //three case 4: - strip.setSegment(7, 32, 35); + strip.getSegment(7).setGeometry(32, 35); break; //four case 5: - strip.setSegment(7, 37, 40); + strip.getSegment(7).setGeometry(37, 40); break; //five case 6: - strip.setSegment(7, 43, 45); + strip.getSegment(7).setGeometry(43, 45); break; //six case 7: - strip.setSegment(7, 40, 43); + strip.getSegment(7).setGeometry(40, 43); break; //seven case 8: - strip.setSegment(7, 45, 48); + strip.getSegment(7).setGeometry(45, 48); break; //eight case 9: - strip.setSegment(7, 48, 50); + strip.getSegment(7).setGeometry(48, 50); break; //nine case 10: - strip.setSegment(7, 54, 56); + strip.getSegment(7).setGeometry(54, 56); break; //ten case 11: - strip.setSegment(7, 50, 54); + strip.getSegment(7).setGeometry(50, 54); break; //eleven case 12: - strip.setSegment(7, 56, 60); + strip.getSegment(7).setGeometry(56, 60); break; //twelve } selectWordSegments(true); - applyMacro(1); + applyPreset(1); } void timeOfDay() diff --git a/usermods/word-clock-matrix/word-clock-matrix.cpp b/usermods/word-clock-matrix/word-clock-matrix.cpp deleted file mode 100644 index 67c5b1e47..000000000 --- a/usermods/word-clock-matrix/word-clock-matrix.cpp +++ /dev/null @@ -1,305 +0,0 @@ -#include "wled.h" -/* - * This v1 usermod file allows you to add own functionality to WLED more easily - * See: https://github.com/Aircoookie/WLED/wiki/Add-own-functionality - * EEPROM bytes 2750+ are reserved for your custom use case. (if you extend #define EEPSIZE in const.h) - * If you just need 8 bytes, use 2551-2559 (you do not need to increase EEPSIZE) - * - * Consider the v2 usermod API if you need a more advanced feature set! - */ - - -uint8_t minuteLast = 99; -int dayBrightness = 128; -int nightBrightness = 16; - -//Use userVar0 and userVar1 (API calls &U0=,&U1=, uint16_t) - -//gets called once at boot. Do all initialization that doesn't depend on network here -void userSetup() -{ -saveMacro(14, "A=128", false); -saveMacro(15, "A=64", false); -saveMacro(16, "A=16", false); - -saveMacro(1, "&FX=0&R=255&G=255&B=255", false); - -//strip.getSegment(1).setOption(SEG_OPTION_SELECTED, true); - - //select first two segments (background color + FX settable) - Segment &seg = strip.getSegment(0); - seg.colors[0] = ((0 << 24) | ((0 & 0xFF) << 16) | ((0 & 0xFF) << 8) | ((0 & 0xFF))); - strip.getSegment(0).setOption(0, false); - strip.getSegment(0).setOption(2, false); - //other segments are text - for (int i = 1; i < 10; i++) - { - Segment &seg = strip.getSegment(i); - seg.colors[0] = ((0 << 24) | ((0 & 0xFF) << 16) | ((190 & 0xFF) << 8) | ((180 & 0xFF))); - strip.getSegment(i).setOption(0, true); - strip.setBrightness(128); - } -} - -//gets called every time WiFi is (re-)connected. Initialize own network interfaces here -void userConnected() -{ -} - -void selectWordSegments(bool state) -{ - for (int i = 1; i < 10; i++) - { - //Segment &seg = strip.getSegment(i); - strip.getSegment(i).setOption(0, state); - // strip.getSegment(1).setOption(SEG_OPTION_SELECTED, true); - //seg.mode = 12; - //seg.palette = 1; - //strip.setBrightness(255); - } - strip.getSegment(0).setOption(0, !state); -} - -void hourChime() -{ - //strip.resetSegments(); - selectWordSegments(true); - colorUpdated(CALL_MODE_FX_CHANGED); - //savePreset(255); - selectWordSegments(false); - //strip.getSegment(0).setOption(0, true); - strip.getSegment(0).setOption(2, true); - applyPreset(12); - colorUpdated(CALL_MODE_FX_CHANGED); -} - -void displayTime(byte hour, byte minute) -{ - bool isToHour = false; //true if minute > 30 - strip.setSegment(0, 0, 64); // background - strip.setSegment(1, 0, 2); //It is - - strip.setSegment(2, 0, 0); - strip.setSegment(3, 0, 0); //disable minutes - strip.setSegment(4, 0, 0); //past - strip.setSegment(6, 0, 0); //to - strip.setSegment(8, 0, 0); //disable o'clock - - if (hour < 24) //valid time, display - { - if (minute == 30) - { - strip.setSegment(2, 3, 6); //half - strip.setSegment(3, 0, 0); //minutes - } - else if (minute == 15 || minute == 45) - { - strip.setSegment(3, 0, 0); //minutes - } - else if (minute == 10) - { - //strip.setSegment(5, 6, 8); //ten - } - else if (minute == 5) - { - //strip.setSegment(5, 16, 18); //five - } - else if (minute == 0) - { - strip.setSegment(3, 0, 0); //minutes - //hourChime(); - } - else - { - strip.setSegment(3, 18, 22); //minutes - } - - //past or to? - if (minute == 0) - { //full hour - strip.setSegment(3, 0, 0); //disable minutes - strip.setSegment(4, 0, 0); //disable past - strip.setSegment(6, 0, 0); //disable to - strip.setSegment(8, 60, 64); //o'clock - } - else if (minute > 34) - { - //strip.setSegment(6, 22, 24); //to - //minute = 60 - minute; - isToHour = true; - } - else - { - //strip.setSegment(4, 24, 27); //past - //isToHour = false; - } - } - else - { //temperature display - } - - //byte minuteRem = minute %10; - - if (minute <= 4) - { - strip.setSegment(3, 0, 0); //nothing - strip.setSegment(5, 0, 0); //nothing - strip.setSegment(6, 0, 0); //nothing - strip.setSegment(8, 60, 64); //o'clock - } - else if (minute <= 9) - { - strip.setSegment(5, 16, 18); // five past - strip.setSegment(4, 24, 27); //past - } - else if (minute <= 14) - { - strip.setSegment(5, 6, 8); // ten past - strip.setSegment(4, 24, 27); //past - } - else if (minute <= 19) - { - strip.setSegment(5, 8, 12); // quarter past - strip.setSegment(3, 0, 0); //minutes - strip.setSegment(4, 24, 27); //past - } - else if (minute <= 24) - { - strip.setSegment(5, 12, 16); // twenty past - strip.setSegment(4, 24, 27); //past - } - else if (minute <= 29) - { - strip.setSegment(5, 12, 18); // twenty-five past - strip.setSegment(4, 24, 27); //past - } - else if (minute <= 34) - { - strip.setSegment(5, 3, 6); // half past - strip.setSegment(3, 0, 0); //minutes - strip.setSegment(4, 24, 27); //past - } - else if (minute <= 39) - { - strip.setSegment(5, 12, 18); // twenty-five to - strip.setSegment(6, 22, 24); //to - } - else if (minute <= 44) - { - strip.setSegment(5, 12, 16); // twenty to - strip.setSegment(6, 22, 24); //to - } - else if (minute <= 49) - { - strip.setSegment(5, 8, 12); // quarter to - strip.setSegment(3, 0, 0); //minutes - strip.setSegment(6, 22, 24); //to - } - else if (minute <= 54) - { - strip.setSegment(5, 6, 8); // ten to - strip.setSegment(6, 22, 24); //to - } - else if (minute <= 59) - { - strip.setSegment(5, 16, 18); // five to - strip.setSegment(6, 22, 24); //to - } - - //hours - if (hour > 23) - return; - if (isToHour) - hour++; - if (hour > 12) - hour -= 12; - if (hour == 0) - hour = 12; - - switch (hour) - { - case 1: - strip.setSegment(7, 27, 29); - break; //one - case 2: - strip.setSegment(7, 35, 37); - break; //two - case 3: - strip.setSegment(7, 29, 32); - break; //three - case 4: - strip.setSegment(7, 32, 35); - break; //four - case 5: - strip.setSegment(7, 37, 40); - break; //five - case 6: - strip.setSegment(7, 43, 45); - break; //six - case 7: - strip.setSegment(7, 40, 43); - break; //seven - case 8: - strip.setSegment(7, 45, 48); - break; //eight - case 9: - strip.setSegment(7, 48, 50); - break; //nine - case 10: - strip.setSegment(7, 54, 56); - break; //ten - case 11: - strip.setSegment(7, 50, 54); - break; //eleven - case 12: - strip.setSegment(7, 56, 60); - break; //twelve - } - -selectWordSegments(true); -applyMacro(1); -} - -void timeOfDay() { -// NOT USED: use timed macros instead - //Used to set brightness dependant of time of day - lights dimmed at night - - //monday to thursday and sunday - - if ((weekday(localTime) == 6) | (weekday(localTime) == 7)) { - if (hour(localTime) > 0 | hour(localTime) < 8) { - strip.setBrightness(nightBrightness); - } - else { - strip.setBrightness(dayBrightness); - } - } - else { - if (hour(localTime) < 6 | hour(localTime) >= 22) { - strip.setBrightness(nightBrightness); - } - else { - strip.setBrightness(dayBrightness); - } - } -} - -//loop. You can use "if (WLED_CONNECTED)" to check for successful connection -void userLoop() -{ - if (minute(localTime) != minuteLast) - { - updateLocalTime(); - //timeOfDay(); - minuteLast = minute(localTime); - displayTime(hour(localTime), minute(localTime)); - if (minute(localTime) == 0){ - hourChime(); - } - if (minute(localTime) == 1){ - //turn off background segment; - strip.getSegment(0).setOption(2, false); - //applyPreset(255); - } - } -} diff --git a/wled00/FX.h b/wled00/FX.h index c06332c76..d56c0fa99 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -539,6 +539,7 @@ typedef struct Segment { inline uint16_t length() const { return width() * height(); } // segment length (count) in physical pixels inline uint16_t groupLength() const { return grouping + spacing; } inline uint8_t getLightCapabilities() const { return _capabilities; } + inline void deactivate() { setGeometry(0,0); } inline static unsigned getUsedSegmentData() { return Segment::_usedSegmentData; } inline static void addUsedSegmentData(int len) { Segment::_usedSegmentData += len; } @@ -554,14 +555,14 @@ typedef struct Segment { static void handleRandomPalette(); void beginDraw(); // set up parameters for current effect - void setUp(uint16_t i1, uint16_t i2, uint8_t grp=1, uint8_t spc=0, uint16_t ofs=UINT16_MAX, uint16_t i1Y=0, uint16_t i2Y=1); + void setGeometry(uint16_t i1, uint16_t i2, uint8_t grp=1, uint8_t spc=0, uint16_t ofs=UINT16_MAX, uint16_t i1Y=0, uint16_t i2Y=1, uint8_t m12=0); bool setColor(uint8_t slot, uint32_t c); //returns true if changed void setCCT(uint16_t k); void setOpacity(uint8_t o); void setOption(uint8_t n, bool val); void setMode(uint8_t fx, bool loadDefaults = false); void setPalette(uint8_t pal); - uint8_t differs(Segment& b) const; + uint8_t differs(const Segment& b) const; void refreshLightCapabilities(); // runtime data functions @@ -783,7 +784,6 @@ class WS2812FX { // 96 bytes setBrightness(uint8_t b, bool direct = false), // sets strip brightness setRange(uint16_t i, uint16_t i2, uint32_t col), // used for clock overlay purgeSegments(), // removes inactive segments from RAM (may incure penalty and memory fragmentation but reduces vector footprint) - setSegment(uint8_t n, uint16_t start, uint16_t stop, uint8_t grouping = 1, uint8_t spacing = 0, uint16_t offset = UINT16_MAX, uint16_t startY=0, uint16_t stopY=1), setMainSegmentId(unsigned n = 0), resetSegments(), // marks all segments for reset makeAutoSegments(bool forceReset = false), // will create segments based on configured outputs diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 613b41fad..69b0c028a 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -489,8 +489,10 @@ void Segment::handleRandomPalette() { nblendPaletteTowardPalette(_randomPalette, _newRandomPalette, 48); } -// segId is given when called from network callback, changes are queued if that segment is currently in its effect function -void Segment::setUp(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, uint16_t ofs, uint16_t i1Y, uint16_t i2Y) { +// sets Segment geometry (length or width/height and grouping, spacing and offset as well as 2D mapping) +// strip must be suspended (strip.suspend()) before calling this function +// this function may call fill() to clear pixels if spacing or mapping changed (which requires setting _vWidth, _vHeight, _vLength or beginDraw()) +void Segment::setGeometry(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, uint16_t ofs, uint16_t i1Y, uint16_t i2Y, uint8_t m12) { // return if neither bounds nor grouping have changed bool boundsUnchanged = (start == i1 && stop == i2); #ifndef WLED_DISABLE_2D @@ -498,11 +500,19 @@ void Segment::setUp(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, uint16_t #endif if (boundsUnchanged && (!grp || (grouping == grp && spacing == spc)) - && (ofs == UINT16_MAX || ofs == offset)) return; + && (ofs == UINT16_MAX || ofs == offset) + && (m12 == map1D2D) + ) return; stateChanged = true; // send UDP/WS broadcast - if (stop) fill(BLACK); // turn old segment range off (clears pixels if changing spacing) + if (stop || spc != spacing || m12 != map1D2D) { + _vWidth = virtualWidth(); + _vHeight = virtualHeight(); + _vLength = virtualLength(); + _segBri = currentBri(); + fill(BLACK); // turn old segment range off or clears pixels if changing spacing (requires _vWidth/_vHeight/_vLength/_segBri) + } if (grp) { // prevent assignment of 0 grouping = grp; spacing = spc; @@ -511,6 +521,7 @@ void Segment::setUp(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, uint16_t spacing = 0; } if (ofs < UINT16_MAX) offset = ofs; + map1D2D = constrain(m12, 0, 7); DEBUG_PRINT(F("setUp segment: ")); DEBUG_PRINT(i1); DEBUG_PRINT(','); DEBUG_PRINT(i2); @@ -993,7 +1004,7 @@ uint32_t IRAM_ATTR_YN Segment::getPixelColor(int i) const return strip.getPixelColor(i); } -uint8_t Segment::differs(Segment& b) const { +uint8_t Segment::differs(const Segment& b) const { uint8_t d = 0; if (start != b.start) d |= SEG_DIFFERS_BOUNDS; if (stop != b.stop) d |= SEG_DIFFERS_BOUNDS; @@ -1595,19 +1606,6 @@ Segment& WS2812FX::getSegment(unsigned id) { return _segments[id >= _segments.size() ? getMainSegmentId() : id]; // vectors } -// sets new segment bounds, queues if that segment is currently running -void WS2812FX::setSegment(uint8_t segId, uint16_t i1, uint16_t i2, uint8_t grouping, uint8_t spacing, uint16_t offset, uint16_t startY, uint16_t stopY) { - if (segId >= getSegmentsNum()) { - if (i2 <= i1) return; // do not append empty/inactive segments - appendSegment(Segment(0, strip.getLengthTotal())); - segId = getSegmentsNum()-1; // segments are added at the end of list - } - suspend(); - _segments[segId].setUp(i1, i2, grouping, spacing, offset, startY, stopY); - resume(); - if (segId > 0 && segId == getSegmentsNum()-1 && i2 <= i1) _segments.pop_back(); // if last segment was deleted remove it from vector -} - void WS2812FX::resetSegments() { _segments.clear(); // destructs all Segment as part of clearing #ifndef WLED_DISABLE_2D diff --git a/wled00/json.cpp b/wled00/json.cpp index 0df7294c8..64b9ddd8d 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -34,7 +34,7 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId) //DEBUG_PRINTLN(F("-- JSON deserialize segment.")); Segment& seg = strip.getSegment(id); //DEBUG_PRINTF_P(PSTR("-- Original segment: %p (%p)\n"), &seg, seg.data); - Segment prev = seg; //make a backup so we can tell if something changed (calling copy constructor) + const Segment prev = seg; //make a backup so we can tell if something changed (calling copy constructor) //DEBUG_PRINTF_P(PSTR("-- Duplicate segment: %p (%p)\n"), &prev, prev.data); int start = elem["start"] | seg.start; @@ -96,17 +96,11 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId) uint16_t of = seg.offset; uint8_t soundSim = elem["si"] | seg.soundSim; uint8_t map1D2D = elem["m12"] | seg.map1D2D; - - if ((spc>0 && spc!=seg.spacing) || seg.map1D2D!=map1D2D) seg.fill(BLACK); // clear spacing gaps - - seg.map1D2D = constrain(map1D2D, 0, 7); + uint8_t set = elem[F("set")] | seg.set; + seg.set = constrain(set, 0, 3); seg.soundSim = constrain(soundSim, 0, 3); - uint8_t set = elem[F("set")] | seg.set; - seg.set = constrain(set, 0, 3); - - int len = 1; - if (stop > start) len = stop - start; + int len = (stop > start) ? stop - start : 1; int offset = elem[F("of")] | INT32_MAX; if (offset != INT32_MAX) { int offsetAbs = abs(offset); @@ -117,7 +111,7 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId) if (stop > start && of > len -1) of = len -1; // update segment (delete if necessary) - seg.setUp(start, stop, grp, spc, of, startY, stopY); // strip needs to be suspended for this to work without issues + seg.setGeometry(start, stop, grp, spc, of, startY, stopY, map1D2D); // strip needs to be suspended for this to work without issues if (newSeg) seg.refreshLightCapabilities(); // fix for #3403 diff --git a/wled00/set.cpp b/wled00/set.cpp index cf3a07dd0..15981d30d 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -874,7 +874,9 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply) if (pos > 0) { spcI = std::max(0,getNumVal(&req, pos)); } - strip.setSegment(selectedSeg, startI, stopI, grpI, spcI, UINT16_MAX, startY, stopY); + strip.suspend(); // must suspend strip operations before changing geometry + selseg.setGeometry(startI, stopI, grpI, spcI, UINT16_MAX, startY, stopY, selseg.map1D2D); + strip.resume(); pos = req.indexOf(F("RV=")); //Segment reverse if (pos > 0) selseg.reverse = req.charAt(pos+3) != '0'; diff --git a/wled00/udp.cpp b/wled00/udp.cpp index a6a0f6aa2..47398bc8a 100644 --- a/wled00/udp.cpp +++ b/wled00/udp.cpp @@ -260,11 +260,12 @@ void parseNotifyPacket(uint8_t *udpIn) { // are we syncing bounds and slave has more active segments than master? if (receiveSegmentBounds && numSrcSegs < strip.getActiveSegmentsNum()) { DEBUG_PRINTLN(F("Removing excessive segments.")); - for (size_t i=strip.getSegmentsNum(); i>numSrcSegs; i--) { - if (strip.getSegment(i).isActive()) { - strip.setSegment(i-1,0,0); // delete segment - } + strip.suspend(); //should not be needed as UDP handling is not done in ISR callbacks but still added "just in case" + for (size_t i=strip.getSegmentsNum(); i>numSrcSegs && i>0; i--) { + Segment &seg = strip.getSegment(i-1); + if (seg.isActive()) seg.deactivate(); // delete segment } + strip.resume(); } size_t inactiveSegs = 0; for (size_t i = 0; i < numSrcSegs && i < strip.getMaxSegments(); i++) { @@ -300,7 +301,7 @@ void parseNotifyPacket(uint8_t *udpIn) { if (!receiveSegmentOptions) { DEBUG_PRINTF_P(PSTR("Set segment w/o options: %d [%d,%d;%d,%d]\n"), id, (int)start, (int)stop, (int)startY, (int)stopY); strip.suspend(); //should not be needed as UDP handling is not done in ISR callbacks but still added "just in case" - selseg.setUp(start, stop, selseg.grouping, selseg.spacing, offset, startY, stopY); + selseg.setGeometry(start, stop, selseg.grouping, selseg.spacing, offset, startY, stopY, selseg.map1D2D); strip.resume(); continue; // we do receive bounds, but not options } @@ -342,12 +343,12 @@ void parseNotifyPacket(uint8_t *udpIn) { if (receiveSegmentBounds) { DEBUG_PRINTF_P(PSTR("Set segment w/ options: %d [%d,%d;%d,%d]\n"), id, (int)start, (int)stop, (int)startY, (int)stopY); strip.suspend(); //should not be needed as UDP handling is not done in ISR callbacks but still added "just in case" - selseg.setUp(start, stop, udpIn[5+ofs], udpIn[6+ofs], offset, startY, stopY); + selseg.setGeometry(start, stop, udpIn[5+ofs], udpIn[6+ofs], offset, startY, stopY, selseg.map1D2D); strip.resume(); } else { DEBUG_PRINTF_P(PSTR("Set segment grouping: %d [%d,%d]\n"), id, (int)udpIn[5+ofs], (int)udpIn[6+ofs]); strip.suspend(); //should not be needed as UDP handling is not done in ISR callbacks but still added "just in case" - selseg.setUp(selseg.start, selseg.stop, udpIn[5+ofs], udpIn[6+ofs], selseg.offset, selseg.startY, selseg.stopY); + selseg.setGeometry(selseg.start, selseg.stop, udpIn[5+ofs], udpIn[6+ofs], selseg.offset, selseg.startY, selseg.stopY, selseg.map1D2D); strip.resume(); } } From 536444f9d1fae4c092a6cfd2f3d944e31cf30055 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Sat, 9 Nov 2024 16:38:18 +0100 Subject: [PATCH 086/287] fixed palette FX to more closely match original 1D version MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - rotation scale is now exactly 180° (divide slider input by 255 instead of 256) - removed shift offset: offset is now zero at slider 0, to hit 128 on touch input devices is really hard - added a 90° shift to input rotation, enabling to rotate from 0 to 180° instead of +90 to -90 (which is not useful in 1D) - changed default settings values to more closely match the old 1D effect --- wled00/FX.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 2f24f745a..47200655d 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -1940,7 +1940,7 @@ uint16_t mode_palette() { using angleType = unsigned; constexpr mathType sInt16Scale = 0x7FFF; constexpr mathType maxAngle = 0x8000; - constexpr mathType staticRotationScale = 256; + constexpr mathType staticRotationScale = 255; constexpr mathType animatedRotationScale = 1; constexpr int16_t (*sinFunction)(uint16_t) = &sin16; constexpr int16_t (*cosFunction)(uint16_t) = &cos16; @@ -1949,7 +1949,7 @@ uint16_t mode_palette() { using wideMathType = float; using angleType = float; constexpr mathType sInt16Scale = 1.0f; - constexpr mathType maxAngle = M_PI / 256.0; + constexpr mathType maxAngle = M_PI / 255.0; constexpr mathType staticRotationScale = 1.0f; constexpr mathType animatedRotationScale = M_TWOPI / double(0xFFFF); constexpr float (*sinFunction)(float) = &sin_t; @@ -1961,7 +1961,7 @@ uint16_t mode_palette() { const int inputShift = SEGMENT.speed; const int inputSize = SEGMENT.intensity; - const int inputRotation = SEGMENT.custom1; + const int inputRotation = SEGMENT.custom1 + 128; const bool inputAnimateShift = SEGMENT.check1; const bool inputAnimateRotation = SEGMENT.check2; const bool inputAssumeSquare = SEGMENT.check3; @@ -1985,7 +1985,7 @@ uint16_t mode_palette() { // So the rectangle needs to have exactly the right size. That size depends on the rotation. // This scale computation here only considers one dimension. You can think of it like the rectangle is always scaled so that // the left and right most points always match the left and right side of the display. - const mathType scale = std::abs(sinTheta) + (std::abs(cosTheta) * maxYOut / maxXOut); + const mathType scale = std::abs(sinTheta) + (std::abs(cosTheta) * maxYOut / maxXOut); // 2D simulation: // If we are dealing with a 1D setup, we assume that each segment represents one line on a 2-dimensional display. // The function is called once per segments, so we need to handle one line at a time. @@ -2016,7 +2016,7 @@ uint16_t mode_palette() { colorIndex = ((inputSize - 112) * colorIndex) / 16; } // Finally, shift the palette a bit. - const int paletteOffset = (!inputAnimateShift) ? (inputShift-128) : (((strip.now * ((inputShift >> 3) +1)) & 0xFFFF) >> 8); + const int paletteOffset = (!inputAnimateShift) ? (inputShift) : (((strip.now * ((inputShift >> 3) +1)) & 0xFFFF) >> 8); colorIndex += paletteOffset; const uint32_t color = SEGMENT.color_wheel((uint8_t)colorIndex); if (isMatrix) { @@ -2028,7 +2028,7 @@ uint16_t mode_palette() { } return FRAMETIME; } -static const char _data_FX_MODE_PALETTE[] PROGMEM = "Palette@Shift,Size,Rotation,,,Animate Shift,Animate Rotation,Anamorphic;;!;12;c1=128,c2=128,c3=128,o1=1,o2=1,o3=0"; +static const char _data_FX_MODE_PALETTE[] PROGMEM = "Palette@Shift,Size,Rotation,,,Animate Shift,Animate Rotation,Anamorphic;;!;12;ix=112,c1=0,o1=1,o2=0,o3=1"; // WLED limitation: Analog Clock overlay will NOT work when Fire2012 is active From 5de86d3d9166e1d629d5fdcd128008360378dccd Mon Sep 17 00:00:00 2001 From: Woody <27882680+w00000dy@users.noreply.github.com> Date: Sat, 9 Nov 2024 23:55:09 +0100 Subject: [PATCH 087/287] fix problem with spaces --- tools/cdata.js | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/cdata.js b/tools/cdata.js index d65573a8e..c5d3c6aa5 100644 --- a/tools/cdata.js +++ b/tools/cdata.js @@ -101,6 +101,7 @@ function adoptVersionAndRepo(html) { async function minify(str, type = "plain") { const options = { collapseWhitespace: true, + conservativeCollapse: true, // preserve spaces in text collapseBooleanAttributes: true, collapseInlineTagWhitespace: true, minifyCSS: true, From d37ee89e845ef83b75b1ad42345f73ad82c71629 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Sat, 9 Nov 2024 19:53:47 -0500 Subject: [PATCH 088/287] ESP8266PWM: Fix phase shift glitches In some cases it was possible for the computed phase shift to skip a cycle. Update the shift calculation logic to prevent this. --- .../src/core_esp8266_waveform_phase.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/ESP8266PWM/src/core_esp8266_waveform_phase.cpp b/lib/ESP8266PWM/src/core_esp8266_waveform_phase.cpp index b89ec8bc1..68cb9010e 100644 --- a/lib/ESP8266PWM/src/core_esp8266_waveform_phase.cpp +++ b/lib/ESP8266PWM/src/core_esp8266_waveform_phase.cpp @@ -324,7 +324,7 @@ static IRAM_ATTR void timer1Interrupt() { case WaveformMode::INIT: waveform.states &= ~waveform.toSetBits; // Clear the state of any just started if (waveform.alignPhase >= 0 && waveform.enabled & (1UL << waveform.alignPhase)) { - wave.nextPeriodCcy = waveform.pins[waveform.alignPhase].nextPeriodCcy + wave.nextPeriodCcy; + wave.nextPeriodCcy = waveform.pins[waveform.alignPhase].nextPeriodCcy + scaleCcys(waveform.phaseCcy, isCPU2X); } else { wave.nextPeriodCcy = waveform.nextEventCcy; @@ -342,15 +342,15 @@ static IRAM_ATTR void timer1Interrupt() { // @willmmiles new feature case WaveformMode::UPDATEPHASE: // in WaveformMode::UPDATEPHASE, we recalculate the targets - if (waveform.alignPhase >= 0 && waveform.enabled & (1UL << waveform.alignPhase)) { + if ((waveform.alignPhase >= 0) && (waveform.enabled & (1UL << waveform.alignPhase))) { // Compute phase shift to realign with target - auto& align_wave = waveform.pins[waveform.alignPhase]; - int32_t shift = static_cast(align_wave.nextPeriodCcy + scaleCcys(waveform.phaseCcy, isCPU2X) - wave.nextPeriodCcy); - const int32_t periodCcys = scaleCcys(wave.periodCcys, isCPU2X); - if (shift > periodCcys/2) shift -= periodCcys; - else if (shift <= -periodCcys/2) shift += periodCcys; - wave.nextPeriodCcy += shift; - wave.endDutyCcy += shift; + auto const newPeriodCcy = waveform.pins[waveform.alignPhase].nextPeriodCcy + scaleCcys(waveform.phaseCcy, isCPU2X); + auto const period = scaleCcys(wave.periodCcys, isCPU2X); + auto shift = ((static_cast (newPeriodCcy - wave.nextPeriodCcy) + period/2) % period) - (period/2); + wave.nextPeriodCcy += static_cast(shift); + if (static_cast(wave.endDutyCcy - wave.nextPeriodCcy) > 0) { + wave.endDutyCcy = wave.nextPeriodCcy; + } } default: break; From 9a564ee20457664e09f2d12aeceac874d1edef53 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Sun, 10 Nov 2024 14:45:55 +0100 Subject: [PATCH 089/287] readme.md - link to multi-strip KB page --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 595b9ee0b..0a02826af 100644 --- a/readme.md +++ b/readme.md @@ -21,7 +21,7 @@ A fast and feature-rich implementation of an ESP32 and ESP8266 webserver to cont - Segments to set different effects and colors to user defined parts of the LED string - Settings page - configuration via the network - Access Point and station mode - automatic failsafe AP -- Up to 10 LED outputs per instance +- [Up to 10 LED outputs](https://kno.wled.ge/features/multi-strip/#esp32) per instance - Support for RGBW strips - Up to 250 user presets to save and load colors/effects easily, supports cycling through them. - Presets can be used to automatically execute API calls From d437027f2630c88ea660fa4e4ad199f537b1abea Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Sun, 10 Nov 2024 22:39:52 +0100 Subject: [PATCH 090/287] Replaced single palette cases with an array to consolidate code - all palettes are defined in palettes.h - access to fastled palettes as an array to remove the switch cases - palette createn in json.cpp in a loop instead of repeaded calls to save flash --- wled00/FX_fcn.cpp | 16 ++------------- wled00/json.cpp | 52 ++++++++--------------------------------------- wled00/palettes.h | 11 ++++++++++ 3 files changed, 21 insertions(+), 58 deletions(-) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index e706f2b43..d666513ae 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -236,23 +236,11 @@ CRGBPalette16 &Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) { targetPalette = CRGBPalette16(prim,prim,prim,prim,prim,prim,prim,prim,sec,sec,sec,sec,sec,sec,sec,sec); } break;} - case 6: //Party colors - targetPalette = PartyColors_p; break; - case 7: //Cloud colors - targetPalette = CloudColors_p; break; - case 8: //Lava colors - targetPalette = LavaColors_p; break; - case 9: //Ocean colors - targetPalette = OceanColors_p; break; - case 10: //Forest colors - targetPalette = ForestColors_p; break; - case 11: //Rainbow colors - targetPalette = RainbowColors_p; break; - case 12: //Rainbow stripe colors - targetPalette = RainbowStripeColors_p; break; default: //progmem palettes if (pal>245) { targetPalette = strip.customPalettes[255-pal]; // we checked bounds above + } else if (pal < 13) { // palette 6 - 12, fastled palettes + targetPalette = *fastledPalettes[pal-6]; } else { byte tcp[72]; memcpy_P(tcp, (byte*)pgm_read_dword(&(gGradientPalettes[pal-13])), 72); diff --git a/wled00/json.cpp b/wled00/json.cpp index 288059653..c74bf6a8d 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -902,10 +902,7 @@ void serializePalettes(JsonObject root, int page) setPaletteColors(curPalette, PartyColors_p); break; case 1: //random - curPalette.add("r"); - curPalette.add("r"); - curPalette.add("r"); - curPalette.add("r"); + for (int j = 0; j < 4; j++) curPalette.add("r"); break; case 2: //primary color only curPalette.add("c1"); @@ -922,53 +919,20 @@ void serializePalettes(JsonObject root, int page) curPalette.add("c1"); break; case 5: //primary + secondary (+tertiary if not off), more distinct + for (int j = 0; j < 5; j++) curPalette.add("c1"); + for (int j = 0; j < 5; j++) curPalette.add("c2"); + for (int j = 0; j < 5; j++) curPalette.add("c3"); curPalette.add("c1"); - curPalette.add("c1"); - curPalette.add("c1"); - curPalette.add("c1"); - curPalette.add("c1"); - curPalette.add("c2"); - curPalette.add("c2"); - curPalette.add("c2"); - curPalette.add("c2"); - curPalette.add("c2"); - curPalette.add("c3"); - curPalette.add("c3"); - curPalette.add("c3"); - curPalette.add("c3"); - curPalette.add("c3"); - curPalette.add("c1"); - break; - case 6: //Party colors - setPaletteColors(curPalette, PartyColors_p); - break; - case 7: //Cloud colors - setPaletteColors(curPalette, CloudColors_p); - break; - case 8: //Lava colors - setPaletteColors(curPalette, LavaColors_p); - break; - case 9: //Ocean colors - setPaletteColors(curPalette, OceanColors_p); - break; - case 10: //Forest colors - setPaletteColors(curPalette, ForestColors_p); - break; - case 11: //Rainbow colors - setPaletteColors(curPalette, RainbowColors_p); - break; - case 12: //Rainbow stripe colors - setPaletteColors(curPalette, RainbowStripeColors_p); break; default: - { - if (i>=palettesCount) { + if (i >= palettesCount) setPaletteColors(curPalette, strip.customPalettes[i - palettesCount]); - } else { + else if (i < 13) // palette 6 - 12, fastled palettes + setPaletteColors(curPalette, *fastledPalettes[i-6]); + else { memcpy_P(tcp, (byte*)pgm_read_dword(&(gGradientPalettes[i - 13])), 72); setPaletteColors(curPalette, tcp); } - } break; } } diff --git a/wled00/palettes.h b/wled00/palettes.h index 41dfbbc16..1ead342bb 100644 --- a/wled00/palettes.h +++ b/wled00/palettes.h @@ -844,6 +844,17 @@ const byte candy2_gp[] PROGMEM = { 211, 39, 33, 34, 255, 1, 1, 1}; +// array of fastled palettes (palette 6 - 12) +const TProgmemRGBPalette16 *const fastledPalettes[] PROGMEM = { + &PartyColors_p, //06-00 Party + &CloudColors_p, //07-01 Cloud + &LavaColors_p, //08-02 Lava + &OceanColors_p, //09-03 Ocean + &ForestColors_p, //10-04 Forest + &RainbowColors_p, //11-05 Rainbow + &RainbowStripeColors_p //12-06 Rainbow Bands +}; + // Single array of defined cpt-city color palettes. // This will let us programmatically choose one based on // a number, rather than having to activate each explicitly From 223b97b8848d8f44c891ea511dadc20e40270cf7 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Sun, 10 Nov 2024 18:04:07 -0500 Subject: [PATCH 091/287] handleSet: Fix incorrect response generation Don't generate a response if there's no HTTP request. Fixes #4269 --- wled00/set.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/set.cpp b/wled00/set.cpp index c446a2eff..712e5f254 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -1191,7 +1191,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply) // internal call, does not send XML response pos = req.indexOf(F("IN")); - if (pos < 1) { + if ((request != nullptr) && (pos < 1)) { auto response = request->beginResponseStream("text/xml"); XML_response(*response); request->send(response); From 8c5e0cd4e94ebc4a2d59c24fce620f6e3540c903 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Kristan?= Date: Fri, 15 Nov 2024 16:01:58 +0100 Subject: [PATCH 092/287] Keep selected FX while searching --- wled00/data/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wled00/data/index.js b/wled00/data/index.js index d9c64bdfb..cbc389391 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -2850,7 +2850,7 @@ function search(field, listId = null) { if (listId!=='pcont' && i===0) return; const listItemName = listItem.querySelector('.lstIname').innerText.toUpperCase(); const searchIndex = listItemName.indexOf(field.value.toUpperCase()); - listItem.style.display = (searchIndex < 0) ? 'none' : ''; + listItem.style.display = (searchIndex < 0) && !listItem.classList.contains("selected") ? 'none' : ''; listItem.dataset.searchIndex = searchIndex; }); @@ -2924,7 +2924,7 @@ function filterFx() { const listItemName = listItem.querySelector('.lstIname').innerText; let hide = false; gId("filters").querySelectorAll("input[type=checkbox]").forEach((e) => { if (e.checked && !listItemName.includes(e.dataset.flt)) hide = i>0 /*true*/; }); - listItem.style.display = hide ? 'none' : ''; + listItem.style.display = hide && !listItem.classList.contains("selected") ? 'none' : ''; }); } From 86be5df475468824cee3654efc6175170d3dbf83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Kristan?= Date: Fri, 15 Nov 2024 19:03:46 +0100 Subject: [PATCH 093/287] xml comma bugfix + few optimisations --- wled00/xml.cpp | 45 +++++++++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/wled00/xml.cpp b/wled00/xml.cpp index dc2673271..a4a72abea 100644 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -83,7 +83,7 @@ void appendGPIOinfo(Print& settingsScript) { // usermod pin reservations will become unnecessary when settings pages will read cfg.json directly if (requestJSONBufferLock(6)) { // if we can't allocate JSON buffer ignore usermod pins - JsonObject mods = pDoc->createNestedObject(F("um")); + JsonObject mods = pDoc->createNestedObject("um"); UsermodManager::addToConfig(mods); if (!mods.isNull()) fillUMPins(settingsScript, mods); releaseJSONBufferLock(); @@ -91,35 +91,42 @@ void appendGPIOinfo(Print& settingsScript) { settingsScript.print(F("];")); // add reserved (unusable) pins + bool firstPin = true; settingsScript.print(F("d.rsvd=[")); for (unsigned i = 0; i < WLED_NUM_PINS; i++) { if (!PinManager::isPinOk(i, false)) { // include readonly pins - settingsScript.print(i); settingsScript.print(","); + if (!firstPin) settingsScript.print(','); + settingsScript.print(i); + firstPin = false; } } #ifdef WLED_ENABLE_DMX - settingsScript.print(F("2,")); // DMX hardcoded pin + if (!firstPin) settingsScript.print(','); + settingsScript.print(2); // DMX hardcoded pin + firstPin = false; #endif #if defined(WLED_DEBUG) && !defined(WLED_DEBUG_HOST) - settingsScript.printf_P(PSTR(",%d"),hardwareTX); // debug output (TX) pin + if (!firstPin) settingsScript.print(','); + settingsScript.print(hardwareTX); // debug output (TX) pin + firstPin = false; #endif - //Note: Using pin 3 (RX) disables Adalight / Serial JSON #ifdef WLED_USE_ETHERNET if (ethernetType != WLED_ETH_NONE && ethernetType < WLED_NUM_ETH_TYPES) { - for (unsigned p=0; p=0) { settingsScript.printf(",%d", ethernetBoards[ethernetType].eth_power); } - if (ethernetBoards[ethernetType].eth_mdc>=0) { settingsScript.printf(",%d", ethernetBoards[ethernetType].eth_mdc); } - if (ethernetBoards[ethernetType].eth_mdio>=0) { settingsScript.printf(",%d", ethernetBoards[ethernetType].eth_mdio); } - switch (ethernetBoards[ethernetType].eth_clk_mode) { + if (!firstPin) settingsScript.print(','); + for (unsigned p=0; p= 0) { settingsScript.printf("%d,",ethernetBoards[ethernetType].eth_power); } + if (ethernetBoards[ethernetType].eth_mdc >= 0) { settingsScript.printf("%d,",ethernetBoards[ethernetType].eth_mdc); } + if (ethernetBoards[ethernetType].eth_mdio >= 0) { settingsScript.printf("%d,",ethernetBoards[ethernetType].eth_mdio); } + switch (ethernetBoards[ethernetType].eth_clk_mode) { case ETH_CLOCK_GPIO0_IN: case ETH_CLOCK_GPIO0_OUT: - settingsScript.print(F("0")); + settingsScript.print(0); break; case ETH_CLOCK_GPIO16_OUT: - settingsScript.print(F("16")); + settingsScript.print(16); break; case ETH_CLOCK_GPIO17_OUT: - settingsScript.print(F("17")); + settingsScript.print(17); break; } } @@ -128,11 +135,11 @@ void appendGPIOinfo(Print& settingsScript) { // add info for read-only GPIO settingsScript.print(F("d.ro_gpio=[")); - bool firstPin = true; + firstPin = true; for (unsigned i = 0; i < WLED_NUM_PINS; i++) { if (PinManager::isReadOnlyPin(i)) { // No comma before the first pin - if (!firstPin) settingsScript.print(F(",")); + if (!firstPin) settingsScript.print(','); settingsScript.print(i); firstPin = false; } @@ -142,7 +149,7 @@ void appendGPIOinfo(Print& settingsScript) { // add info about max. # of pins settingsScript.print(F("d.max_gpio=")); settingsScript.print(WLED_NUM_PINS); - settingsScript.print(F(";")); + settingsScript.print(';'); } //get values for settings form in javascript @@ -152,6 +159,7 @@ void getSettingsJS(byte subPage, Print& settingsScript) DEBUG_PRINTF_P(PSTR("settings resp %u\n"), (unsigned)subPage); if (subPage <0 || subPage >10) return; + char nS[32]; if (subPage == SUBPAGE_MENU) { @@ -259,8 +267,6 @@ void getSettingsJS(byte subPage, Print& settingsScript) if (subPage == SUBPAGE_LEDS) { - char nS[32]; - appendGPIOinfo(settingsScript); settingsScript.print(F("d.ledTypes=")); settingsScript.print(BusManager::getLEDTypesJSONString().c_str()); settingsScript.print(";"); @@ -399,7 +405,6 @@ void getSettingsJS(byte subPage, Print& settingsScript) if (subPage == SUBPAGE_SYNC) { - [[maybe_unused]] char nS[32]; printSetFormValue(settingsScript,PSTR("UP"),udpPort); printSetFormValue(settingsScript,PSTR("U2"),udpPort2); #ifndef WLED_DISABLE_ESPNOW @@ -637,7 +642,7 @@ void getSettingsJS(byte subPage, Print& settingsScript) #if defined(ARDUINO_ARCH_ESP32) ESP.getChipModel(), #else - F("esp8266"), + "esp8266", #endif VERSION); From 7f69a0bc5ec42c507f3923b456257c517b65e1ef Mon Sep 17 00:00:00 2001 From: maxi4329 Date: Sat, 16 Nov 2024 12:37:24 +0100 Subject: [PATCH 094/287] removed obsolete code as of #4267 --- wled00/data/style.css | 3 --- wled00/data/update.htm | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/wled00/data/style.css b/wled00/data/style.css index 42e49d304..b6cb0f9e6 100644 --- a/wled00/data/style.css +++ b/wled00/data/style.css @@ -44,9 +44,6 @@ button.sml { min-width: 40px; margin: 0 0 0 10px; } -span:before, b:before, b:after, i:after{ - content: "\00A0"; -} #scan { margin-top: -10px; } diff --git a/wled00/data/update.htm b/wled00/data/update.htm index b68645a52..23a6a866e 100644 --- a/wled00/data/update.htm +++ b/wled00/data/update.htm @@ -17,7 +17,7 @@

WLED Software Update

Installed version: ##VERSION##
- Download the latest binary: 

From 6fe2024542b12fc17af98fa6402ca5f557a6d958 Mon Sep 17 00:00:00 2001 From: Woody <27882680+w00000dy@users.noreply.github.com> Date: Sat, 16 Nov 2024 19:53:10 +0100 Subject: [PATCH 095/287] move selected to the bottom in search result --- wled00/data/index.js | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/wled00/data/index.js b/wled00/data/index.js index cbc389391..f93511aaf 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -2827,7 +2827,7 @@ function search(field, listId = null) { // restore default preset sorting if no search term is entered if (!search) { - if (listId === 'pcont') { populatePresets(); return; } + if (listId === 'pcont') { populatePresets(); return; } if (listId === 'pallist') { let id = parseInt(d.querySelector('#pallist input[name="palette"]:checked').value); // preserve selected palette populatePalettes(); @@ -2846,12 +2846,17 @@ function search(field, listId = null) { // filter list items but leave (Default & Solid) always visible const listItems = gId(listId).querySelectorAll('.lstI'); - listItems.forEach((listItem,i)=>{ - if (listId!=='pcont' && i===0) return; + listItems.forEach((listItem, i) => { const listItemName = listItem.querySelector('.lstIname').innerText.toUpperCase(); const searchIndex = listItemName.indexOf(field.value.toUpperCase()); - listItem.style.display = (searchIndex < 0) && !listItem.classList.contains("selected") ? 'none' : ''; - listItem.dataset.searchIndex = searchIndex; + if (searchIndex < 0) { + listItem.dataset.searchIndex = Number.MAX_SAFE_INTEGER; + } else { + listItem.dataset.searchIndex = searchIndex; + } + + if ((listId !== 'pcont' && i === 0) || listItem.classList.contains("selected")) return; + listItem.style.display = (searchIndex < 0) ? 'none' : ''; }); // sort list items by search index and name @@ -2920,10 +2925,10 @@ function filterFx() { inputField.value = ''; inputField.focus(); clean(inputField.nextElementSibling); - gId("fxlist").querySelectorAll('.lstI').forEach((listItem,i) => { + gId("fxlist").querySelectorAll('.lstI').forEach((listItem, i) => { const listItemName = listItem.querySelector('.lstIname').innerText; let hide = false; - gId("filters").querySelectorAll("input[type=checkbox]").forEach((e) => { if (e.checked && !listItemName.includes(e.dataset.flt)) hide = i>0 /*true*/; }); + gId("filters").querySelectorAll("input[type=checkbox]").forEach((e) => { if (e.checked && !listItemName.includes(e.dataset.flt)) hide = i > 0 /*true*/; }); listItem.style.display = hide && !listItem.classList.contains("selected") ? 'none' : ''; }); } From 4d3df5d98fb283e936db9eb7f19406a3a5a38b24 Mon Sep 17 00:00:00 2001 From: Woody <27882680+w00000dy@users.noreply.github.com> Date: Sat, 16 Nov 2024 20:12:29 +0100 Subject: [PATCH 096/287] Fix default/solid not being first --- wled00/data/index.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/wled00/data/index.js b/wled00/data/index.js index f93511aaf..1482c27b5 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -2847,6 +2847,7 @@ function search(field, listId = null) { // filter list items but leave (Default & Solid) always visible const listItems = gId(listId).querySelectorAll('.lstI'); listItems.forEach((listItem, i) => { + if (listId !== 'pcont' && i === 0) return; const listItemName = listItem.querySelector('.lstIname').innerText.toUpperCase(); const searchIndex = listItemName.indexOf(field.value.toUpperCase()); if (searchIndex < 0) { @@ -2854,9 +2855,7 @@ function search(field, listId = null) { } else { listItem.dataset.searchIndex = searchIndex; } - - if ((listId !== 'pcont' && i === 0) || listItem.classList.contains("selected")) return; - listItem.style.display = (searchIndex < 0) ? 'none' : ''; + listItem.style.display = (searchIndex < 0) && !listItem.classList.contains("selected") ? 'none' : ''; }); // sort list items by search index and name From a765903a41d3272c166ff5351da5c0338947c183 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Sat, 16 Nov 2024 17:10:08 -0500 Subject: [PATCH 097/287] Fix array overflow in exploding_fireworks Attempt to allocate enough room for the "minimum" sparks; and ensure that we never overrun the allocated array size. Fixes #4120 --- wled00/FX.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 2f24f745a..947788fee 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -3547,7 +3547,7 @@ uint16_t mode_exploding_fireworks(void) if (segs <= (strip.getMaxSegments() /4)) maxData *= 2; //ESP8266: 1024 if <= 4 segs ESP32: 2560 if <= 8 segs int maxSparks = maxData / sizeof(spark); //ESP8266: max. 21/42/85 sparks/seg, ESP32: max. 53/106/213 sparks/seg - unsigned numSparks = min(2 + ((rows*cols) >> 1), maxSparks); + unsigned numSparks = min(5 + ((rows*cols) >> 1), maxSparks); unsigned dataSize = sizeof(spark) * numSparks; if (!SEGENV.allocateData(dataSize + sizeof(float))) return mode_static(); //allocation failed float *dying_gravity = reinterpret_cast(SEGENV.data + dataSize); @@ -3602,7 +3602,8 @@ uint16_t mode_exploding_fireworks(void) * Size is proportional to the height. */ unsigned nSparks = flare->pos + random8(4); - nSparks = constrain(nSparks, 4, numSparks); + nSparks = std::max(nSparks, 4U); // This is not a standard constrain; numSparks is not guaranteed to be at least 4 + nSparks = std::min(nSparks, numSparks); // initialize sparks if (SEGENV.aux0 == 2) { From 84dd26c1b7d90b2f4466253bc9e6b9faa545451a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Kristan?= Date: Sun, 17 Nov 2024 10:47:09 +0100 Subject: [PATCH 098/287] Some more optimisations. --- wled00/xml.cpp | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/wled00/xml.cpp b/wled00/xml.cpp index a4a72abea..df52b6248 100644 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -147,9 +147,7 @@ void appendGPIOinfo(Print& settingsScript) { settingsScript.print(F("];")); // add info about max. # of pins - settingsScript.print(F("d.max_gpio=")); - settingsScript.print(WLED_NUM_PINS); - settingsScript.print(';'); + settingsScript.printf_P(PSTR("d.max_gpio=%d;"),WLED_NUM_PINS); } //get values for settings form in javascript @@ -269,7 +267,7 @@ void getSettingsJS(byte subPage, Print& settingsScript) { appendGPIOinfo(settingsScript); - settingsScript.print(F("d.ledTypes=")); settingsScript.print(BusManager::getLEDTypesJSONString().c_str()); settingsScript.print(";"); + settingsScript.printf_P(PSTR("d.ledTypes=%s;"), BusManager::getLEDTypesJSONString().c_str()); // set limits settingsScript.printf_P(PSTR("bLimits(%d,%d,%d,%d,%d,%d,%d,%d);"), @@ -653,8 +651,7 @@ void getSettingsJS(byte subPage, Print& settingsScript) { printSetFormValue(settingsScript,PSTR("SOMP"),strip.isMatrix); #ifndef WLED_DISABLE_2D - settingsScript.printf_P(PSTR("maxPanels=%d;"),WLED_MAX_PANELS); - settingsScript.print(F("resetPanels();")); + settingsScript.printf_P(PSTR("maxPanels=%d;resetPanels();"),WLED_MAX_PANELS); if (strip.isMatrix) { if(strip.panels>0){ printSetFormValue(settingsScript,PSTR("PW"),strip.panel[0].width); //Set generator Width and Height to first panel size for convenience @@ -664,11 +661,9 @@ void getSettingsJS(byte subPage, Print& settingsScript) // panels for (unsigned i=0; i Date: Sun, 17 Nov 2024 14:31:23 +0100 Subject: [PATCH 099/287] Bugfix --- wled00/xml.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/wled00/xml.cpp b/wled00/xml.cpp index df52b6248..597db73f2 100644 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -660,8 +660,7 @@ void getSettingsJS(byte subPage, Print& settingsScript) printSetFormValue(settingsScript,PSTR("MPC"),strip.panels); // panels for (unsigned i=0; i Date: Wed, 20 Nov 2024 12:39:39 +0100 Subject: [PATCH 100/287] Use MQTT_MAX_TOPIC_LEN in places where it was not used before to avoid buffer overflows when value is increased --- wled00/button.cpp | 8 ++++---- wled00/mqtt.cpp | 22 +++++++++++----------- wled00/wled.h | 8 ++++---- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/wled00/button.cpp b/wled00/button.cpp index 4d6f954f6..6f9c84560 100644 --- a/wled00/button.cpp +++ b/wled00/button.cpp @@ -29,7 +29,7 @@ void shortPressAction(uint8_t b) #ifndef WLED_DISABLE_MQTT // publish MQTT message if (buttonPublishMqtt && WLED_MQTT_CONNECTED) { - char subuf[64]; + char subuf[MQTT_MAX_TOPIC_LEN + 32]; sprintf_P(subuf, _mqtt_topic_button, mqttDeviceTopic, (int)b); mqtt->publish(subuf, 0, false, "short"); } @@ -62,7 +62,7 @@ void longPressAction(uint8_t b) #ifndef WLED_DISABLE_MQTT // publish MQTT message if (buttonPublishMqtt && WLED_MQTT_CONNECTED) { - char subuf[64]; + char subuf[MQTT_MAX_TOPIC_LEN + 32]; sprintf_P(subuf, _mqtt_topic_button, mqttDeviceTopic, (int)b); mqtt->publish(subuf, 0, false, "long"); } @@ -83,7 +83,7 @@ void doublePressAction(uint8_t b) #ifndef WLED_DISABLE_MQTT // publish MQTT message if (buttonPublishMqtt && WLED_MQTT_CONNECTED) { - char subuf[64]; + char subuf[MQTT_MAX_TOPIC_LEN + 32]; sprintf_P(subuf, _mqtt_topic_button, mqttDeviceTopic, (int)b); mqtt->publish(subuf, 0, false, "double"); } @@ -151,7 +151,7 @@ void handleSwitch(uint8_t b) #ifndef WLED_DISABLE_MQTT // publish MQTT message if (buttonPublishMqtt && WLED_MQTT_CONNECTED) { - char subuf[64]; + char subuf[MQTT_MAX_TOPIC_LEN + 32]; if (buttonType[b] == BTN_TYPE_PIR_SENSOR) sprintf_P(subuf, PSTR("%s/motion/%d"), mqttDeviceTopic, (int)b); else sprintf_P(subuf, _mqtt_topic_button, mqttDeviceTopic, (int)b); mqtt->publish(subuf, 0, false, !buttonPressedBefore[b] ? "off" : "on"); diff --git a/wled00/mqtt.cpp b/wled00/mqtt.cpp index a476db87a..d909494ee 100644 --- a/wled00/mqtt.cpp +++ b/wled00/mqtt.cpp @@ -23,24 +23,24 @@ static void parseMQTTBriPayload(char* payload) static void onMqttConnect(bool sessionPresent) { //(re)subscribe to required topics - char subuf[38]; + char subuf[MQTT_MAX_TOPIC_LEN + 6]; if (mqttDeviceTopic[0] != 0) { - strlcpy(subuf, mqttDeviceTopic, 33); + strlcpy(subuf, mqttDeviceTopic, MQTT_MAX_TOPIC_LEN + 1); mqtt->subscribe(subuf, 0); strcat_P(subuf, PSTR("/col")); mqtt->subscribe(subuf, 0); - strlcpy(subuf, mqttDeviceTopic, 33); + strlcpy(subuf, mqttDeviceTopic, MQTT_MAX_TOPIC_LEN + 1); strcat_P(subuf, PSTR("/api")); mqtt->subscribe(subuf, 0); } if (mqttGroupTopic[0] != 0) { - strlcpy(subuf, mqttGroupTopic, 33); + strlcpy(subuf, mqttGroupTopic, MQTT_MAX_TOPIC_LEN + 1); mqtt->subscribe(subuf, 0); strcat_P(subuf, PSTR("/col")); mqtt->subscribe(subuf, 0); - strlcpy(subuf, mqttGroupTopic, 33); + strlcpy(subuf, mqttGroupTopic, MQTT_MAX_TOPIC_LEN + 1); strcat_P(subuf, PSTR("/api")); mqtt->subscribe(subuf, 0); } @@ -158,19 +158,19 @@ void publishMqtt() #ifndef USERMOD_SMARTNEST char s[10]; - char subuf[48]; + char subuf[MQTT_MAX_TOPIC_LEN + 16]; sprintf_P(s, PSTR("%u"), bri); - strlcpy(subuf, mqttDeviceTopic, 33); + strlcpy(subuf, mqttDeviceTopic, MQTT_MAX_TOPIC_LEN + 1); strcat_P(subuf, PSTR("/g")); mqtt->publish(subuf, 0, retainMqttMsg, s); // optionally retain message (#2263) sprintf_P(s, PSTR("#%06X"), (col[3] << 24) | (col[0] << 16) | (col[1] << 8) | (col[2])); - strlcpy(subuf, mqttDeviceTopic, 33); + strlcpy(subuf, mqttDeviceTopic, MQTT_MAX_TOPIC_LEN + 1); strcat_P(subuf, PSTR("/c")); mqtt->publish(subuf, 0, retainMqttMsg, s); // optionally retain message (#2263) - strlcpy(subuf, mqttDeviceTopic, 33); + strlcpy(subuf, mqttDeviceTopic, MQTT_MAX_TOPIC_LEN + 1); strcat_P(subuf, PSTR("/status")); mqtt->publish(subuf, 0, true, "online"); // retain message for a LWT @@ -178,7 +178,7 @@ void publishMqtt() DynamicBuffer buf(1024); bufferPrint pbuf(buf.data(), buf.size()); XML_response(pbuf); - strlcpy(subuf, mqttDeviceTopic, 33); + strlcpy(subuf, mqttDeviceTopic, MQTT_MAX_TOPIC_LEN + 1); strcat_P(subuf, PSTR("/v")); mqtt->publish(subuf, 0, retainMqttMsg, buf.data(), pbuf.size()); // optionally retain message (#2263) #endif @@ -211,7 +211,7 @@ bool initMqtt() if (mqttUser[0] && mqttPass[0]) mqtt->setCredentials(mqttUser, mqttPass); #ifndef USERMOD_SMARTNEST - strlcpy(mqttStatusTopic, mqttDeviceTopic, 33); + strlcpy(mqttStatusTopic, mqttDeviceTopic, MQTT_MAX_TOPIC_LEN + 1); strcat_P(mqttStatusTopic, PSTR("/status")); mqtt->setWill(mqttStatusTopic, 0, true, "offline"); // LWT message #endif diff --git a/wled00/wled.h b/wled00/wled.h index 2b3a77d24..29b43753a 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -483,10 +483,10 @@ WLED_GLOBAL unsigned long lastMqttReconnectAttempt _INIT(0); // used for other #endif WLED_GLOBAL AsyncMqttClient *mqtt _INIT(NULL); WLED_GLOBAL bool mqttEnabled _INIT(false); -WLED_GLOBAL char mqttStatusTopic[40] _INIT(""); // this must be global because of async handlers -WLED_GLOBAL char mqttDeviceTopic[MQTT_MAX_TOPIC_LEN+1] _INIT(""); // main MQTT topic (individual per device, default is wled/mac) -WLED_GLOBAL char mqttGroupTopic[MQTT_MAX_TOPIC_LEN+1] _INIT("wled/all"); // second MQTT topic (for example to group devices) -WLED_GLOBAL char mqttServer[MQTT_MAX_SERVER_LEN+1] _INIT(""); // both domains and IPs should work (no SSL) +WLED_GLOBAL char mqttStatusTopic[MQTT_MAX_TOPIC_LEN + 8] _INIT(""); // this must be global because of async handlers +WLED_GLOBAL char mqttDeviceTopic[MQTT_MAX_TOPIC_LEN + 1] _INIT(""); // main MQTT topic (individual per device, default is wled/mac) +WLED_GLOBAL char mqttGroupTopic[MQTT_MAX_TOPIC_LEN + 1] _INIT("wled/all"); // second MQTT topic (for example to group devices) +WLED_GLOBAL char mqttServer[MQTT_MAX_SERVER_LEN + 1] _INIT(""); // both domains and IPs should work (no SSL) WLED_GLOBAL char mqttUser[41] _INIT(""); // optional: username for MQTT auth WLED_GLOBAL char mqttPass[65] _INIT(""); // optional: password for MQTT auth WLED_GLOBAL char mqttClientID[41] _INIT(""); // override the client ID From cec89788865bf29aae0799f03ba0c8c43638d297 Mon Sep 17 00:00:00 2001 From: Wouter Gritter Date: Wed, 20 Nov 2024 12:45:39 +0100 Subject: [PATCH 101/287] Fix comment alignment --- wled00/wled.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/wled.h b/wled00/wled.h index 29b43753a..3630170f9 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -483,7 +483,7 @@ WLED_GLOBAL unsigned long lastMqttReconnectAttempt _INIT(0); // used for other #endif WLED_GLOBAL AsyncMqttClient *mqtt _INIT(NULL); WLED_GLOBAL bool mqttEnabled _INIT(false); -WLED_GLOBAL char mqttStatusTopic[MQTT_MAX_TOPIC_LEN + 8] _INIT(""); // this must be global because of async handlers +WLED_GLOBAL char mqttStatusTopic[MQTT_MAX_TOPIC_LEN + 8] _INIT(""); // this must be global because of async handlers WLED_GLOBAL char mqttDeviceTopic[MQTT_MAX_TOPIC_LEN + 1] _INIT(""); // main MQTT topic (individual per device, default is wled/mac) WLED_GLOBAL char mqttGroupTopic[MQTT_MAX_TOPIC_LEN + 1] _INIT("wled/all"); // second MQTT topic (for example to group devices) WLED_GLOBAL char mqttServer[MQTT_MAX_SERVER_LEN + 1] _INIT(""); // both domains and IPs should work (no SSL) From 0db47a8586526541b0cdd810b466923c64b80cd6 Mon Sep 17 00:00:00 2001 From: Wouter Gritter Date: Thu, 21 Nov 2024 09:51:13 +0100 Subject: [PATCH 102/287] Add comment warning about modification of MQTT_MAX_TOPIC_LEN --- wled00/wled.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/wled.h b/wled00/wled.h index 3630170f9..5dbc013d9 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -476,7 +476,7 @@ WLED_GLOBAL uint16_t pollReplyCount _INIT(0); // count numbe WLED_GLOBAL unsigned long lastMqttReconnectAttempt _INIT(0); // used for other periodic tasks too #ifndef WLED_DISABLE_MQTT #ifndef MQTT_MAX_TOPIC_LEN - #define MQTT_MAX_TOPIC_LEN 32 + #define MQTT_MAX_TOPIC_LEN 32 // should not be less than 32. might cause trouble when increased with usermods active that do not handle this correctly. #endif #ifndef MQTT_MAX_SERVER_LEN #define MQTT_MAX_SERVER_LEN 32 From 49fb16e2c6528ddc98cccc352cc8f0d4a5f62a13 Mon Sep 17 00:00:00 2001 From: Wouter Gritter Date: Thu, 21 Nov 2024 10:52:22 +0100 Subject: [PATCH 103/287] Introduce printSetInputMaxlength to properly set an inputs maxlength variable --- wled00/fcn_declare.h | 1 + wled00/util.cpp | 4 ++++ wled00/xml.cpp | 5 +++-- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 1855a8b63..91176160f 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -380,6 +380,7 @@ size_t printSetFormValue(Print& settingsScript, const char* key, int val); size_t printSetFormValue(Print& settingsScript, const char* key, const char* val); size_t printSetFormIndex(Print& settingsScript, const char* key, int index); size_t printSetClassElementHTML(Print& settingsScript, const char* key, const int index, const char* val); +size_t printSetInputMaxLength(Print& settingsScript, const char* key, int val); void prepareHostname(char* hostname); bool isAsterisksOnly(const char* str, byte maxLen); bool requestJSONBufferLock(uint8_t module=255); diff --git a/wled00/util.cpp b/wled00/util.cpp index 0b78a4646..1c1282eda 100644 --- a/wled00/util.cpp +++ b/wled00/util.cpp @@ -109,6 +109,10 @@ size_t printSetClassElementHTML(Print& settingsScript, const char* key, const in return settingsScript.printf_P(PSTR("d.getElementsByClassName(\"%s\")[%d].innerHTML=\"%s\";"), key, index, val); } +size_t printSetInputMaxLength(Print& settingsScript, const char* key, int val) { + return settingsScript.printf_P(PSTR("d.Sf.%s.setAttribute(\"maxlength\", %d);"),key,val); +} + void prepareHostname(char* hostname) diff --git a/wled00/xml.cpp b/wled00/xml.cpp index dc2673271..6f837a9b6 100644 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -465,8 +465,9 @@ void getSettingsJS(byte subPage, Print& settingsScript) printSetFormValue(settingsScript,PSTR("MG"),mqttGroupTopic); printSetFormCheckbox(settingsScript,PSTR("BM"),buttonPublishMqtt); printSetFormCheckbox(settingsScript,PSTR("RT"),retainMqttMsg); - settingsScript.printf_P(PSTR("d.Sf.MD.maxlength=%d;d.Sf.MG.maxlength=%d;d.Sf.MS.maxlength=%d;"), - MQTT_MAX_TOPIC_LEN, MQTT_MAX_TOPIC_LEN, MQTT_MAX_SERVER_LEN); + printSetInputMaxLength(settingsScript, PSTR("MD"), MQTT_MAX_TOPIC_LEN); + printSetInputMaxLength(settingsScript, PSTR("MG"), MQTT_MAX_TOPIC_LEN); + printSetInputMaxLength(settingsScript, PSTR("MS"), MQTT_MAX_SERVER_LEN); #else settingsScript.print(F("toggle('MQTT');")); // hide MQTT settings #endif From 8f8afd98a5dc5982271babbcce84ffff28c32fab Mon Sep 17 00:00:00 2001 From: Wouter Gritter Date: Thu, 21 Nov 2024 11:20:42 +0100 Subject: [PATCH 104/287] Replace comment with compile-time error and warning --- wled00/mqtt.cpp | 4 ++++ wled00/wled.h | 2 +- wled00/wled_eeprom.cpp | 4 ++++ 3 files changed, 9 insertions(+), 1 deletion(-) mode change 100755 => 100644 wled00/wled_eeprom.cpp diff --git a/wled00/mqtt.cpp b/wled00/mqtt.cpp index d909494ee..38afeffe3 100644 --- a/wled00/mqtt.cpp +++ b/wled00/mqtt.cpp @@ -7,6 +7,10 @@ #ifndef WLED_DISABLE_MQTT #define MQTT_KEEP_ALIVE_TIME 60 // contact the MQTT broker every 60 seconds +#if MQTT_MAX_TOPIC_LEN > 32 +#warning "MQTT topics length > 32 is not recommended for compatibility with usermods!" +#endif + static void parseMQTTBriPayload(char* payload) { if (strstr(payload, "ON") || strstr(payload, "on") || strstr(payload, "true")) {bri = briLast; stateUpdated(CALL_MODE_DIRECT_CHANGE);} diff --git a/wled00/wled.h b/wled00/wled.h index 5dbc013d9..3630170f9 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -476,7 +476,7 @@ WLED_GLOBAL uint16_t pollReplyCount _INIT(0); // count numbe WLED_GLOBAL unsigned long lastMqttReconnectAttempt _INIT(0); // used for other periodic tasks too #ifndef WLED_DISABLE_MQTT #ifndef MQTT_MAX_TOPIC_LEN - #define MQTT_MAX_TOPIC_LEN 32 // should not be less than 32. might cause trouble when increased with usermods active that do not handle this correctly. + #define MQTT_MAX_TOPIC_LEN 32 #endif #ifndef MQTT_MAX_SERVER_LEN #define MQTT_MAX_SERVER_LEN 32 diff --git a/wled00/wled_eeprom.cpp b/wled00/wled_eeprom.cpp old mode 100755 new mode 100644 index 4f2c14d47..8582b49df --- a/wled00/wled_eeprom.cpp +++ b/wled00/wled_eeprom.cpp @@ -2,6 +2,10 @@ #include #include "wled.h" +#if defined(WLED_ENABLE_MQTT) && MQTT_MAX_TOPIC_LEN < 32 +#error "MQTT topics length < 32 is not supported by the EEPROM module!" +#endif + /* * DEPRECATED, do not use for new settings * Only used to restore config from pre-0.11 installations using the deEEP() methods From 5ac8ba9bae80688ab9c9151e8775447d8e86cbf1 Mon Sep 17 00:00:00 2001 From: Wouter Gritter Date: Thu, 21 Nov 2024 12:02:55 +0100 Subject: [PATCH 105/287] Revert "Introduce printSetInputMaxlength to properly set an inputs maxlength variable" This reverts commit 49fb16e2c6528ddc98cccc352cc8f0d4a5f62a13. --- wled00/fcn_declare.h | 1 - wled00/util.cpp | 4 ---- wled00/xml.cpp | 5 ++--- 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 91176160f..1855a8b63 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -380,7 +380,6 @@ size_t printSetFormValue(Print& settingsScript, const char* key, int val); size_t printSetFormValue(Print& settingsScript, const char* key, const char* val); size_t printSetFormIndex(Print& settingsScript, const char* key, int index); size_t printSetClassElementHTML(Print& settingsScript, const char* key, const int index, const char* val); -size_t printSetInputMaxLength(Print& settingsScript, const char* key, int val); void prepareHostname(char* hostname); bool isAsterisksOnly(const char* str, byte maxLen); bool requestJSONBufferLock(uint8_t module=255); diff --git a/wled00/util.cpp b/wled00/util.cpp index 1c1282eda..0b78a4646 100644 --- a/wled00/util.cpp +++ b/wled00/util.cpp @@ -109,10 +109,6 @@ size_t printSetClassElementHTML(Print& settingsScript, const char* key, const in return settingsScript.printf_P(PSTR("d.getElementsByClassName(\"%s\")[%d].innerHTML=\"%s\";"), key, index, val); } -size_t printSetInputMaxLength(Print& settingsScript, const char* key, int val) { - return settingsScript.printf_P(PSTR("d.Sf.%s.setAttribute(\"maxlength\", %d);"),key,val); -} - void prepareHostname(char* hostname) diff --git a/wled00/xml.cpp b/wled00/xml.cpp index 6f837a9b6..dc2673271 100644 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -465,9 +465,8 @@ void getSettingsJS(byte subPage, Print& settingsScript) printSetFormValue(settingsScript,PSTR("MG"),mqttGroupTopic); printSetFormCheckbox(settingsScript,PSTR("BM"),buttonPublishMqtt); printSetFormCheckbox(settingsScript,PSTR("RT"),retainMqttMsg); - printSetInputMaxLength(settingsScript, PSTR("MD"), MQTT_MAX_TOPIC_LEN); - printSetInputMaxLength(settingsScript, PSTR("MG"), MQTT_MAX_TOPIC_LEN); - printSetInputMaxLength(settingsScript, PSTR("MS"), MQTT_MAX_SERVER_LEN); + settingsScript.printf_P(PSTR("d.Sf.MD.maxlength=%d;d.Sf.MG.maxlength=%d;d.Sf.MS.maxlength=%d;"), + MQTT_MAX_TOPIC_LEN, MQTT_MAX_TOPIC_LEN, MQTT_MAX_SERVER_LEN); #else settingsScript.print(F("toggle('MQTT');")); // hide MQTT settings #endif From 5c8b2ebf7a015983890daecabe0db1cfcc7628d0 Mon Sep 17 00:00:00 2001 From: Wouter Gritter Date: Thu, 21 Nov 2024 12:04:01 +0100 Subject: [PATCH 106/287] maxlength -> maxLength to fix this attribute not being modified correctly --- wled00/xml.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/xml.cpp b/wled00/xml.cpp index dc2673271..0893e5d25 100644 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -465,7 +465,7 @@ void getSettingsJS(byte subPage, Print& settingsScript) printSetFormValue(settingsScript,PSTR("MG"),mqttGroupTopic); printSetFormCheckbox(settingsScript,PSTR("BM"),buttonPublishMqtt); printSetFormCheckbox(settingsScript,PSTR("RT"),retainMqttMsg); - settingsScript.printf_P(PSTR("d.Sf.MD.maxlength=%d;d.Sf.MG.maxlength=%d;d.Sf.MS.maxlength=%d;"), + settingsScript.printf_P(PSTR("d.Sf.MD.maxLength=%d;d.Sf.MG.maxLength=%d;d.Sf.MS.maxLength=%d;"), MQTT_MAX_TOPIC_LEN, MQTT_MAX_TOPIC_LEN, MQTT_MAX_SERVER_LEN); #else settingsScript.print(F("toggle('MQTT');")); // hide MQTT settings From 6790f8af084a4f4fe04672d82c6f55744d6d095d Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Thu, 21 Nov 2024 22:16:03 +0100 Subject: [PATCH 107/287] Same MIN_FRAME_DELAY=3 for -C3 and -S2 --- wled00/FX.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wled00/FX.h b/wled00/FX.h index 56a0c9bd0..c15500aa3 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -48,8 +48,8 @@ #define FRAMETIME strip.getFrameTime() #if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S2) #define MIN_FRAME_DELAY 2 // minimum wait between repaints, to keep other functions like WiFi alive -#elif defined(CONFIG_IDF_TARGET_ESP32S2) - #define MIN_FRAME_DELAY 4 // S2 is slower than normal esp32, and only has one core +#elif defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3) + #define MIN_FRAME_DELAY 3 // S2/C3 are slower than normal esp32, and only have one core #else #define MIN_FRAME_DELAY 8 // 8266 legacy MIN_SHOW_DELAY #endif From 548736f432d5c1484b85045816ce1cfbafaa4e46 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Thu, 21 Nov 2024 22:50:55 +0100 Subject: [PATCH 108/287] SparkFunDMX fix for possible array bounds violation in DMX.write Align with code in espdmx.cpp --- wled00/src/dependencies/dmx/SparkFunDMX.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wled00/src/dependencies/dmx/SparkFunDMX.cpp b/wled00/src/dependencies/dmx/SparkFunDMX.cpp index dbc9b1590..064b9ff62 100644 --- a/wled00/src/dependencies/dmx/SparkFunDMX.cpp +++ b/wled00/src/dependencies/dmx/SparkFunDMX.cpp @@ -34,8 +34,8 @@ static const int enablePin = -1; // disable the enable pin because it is not ne static const int rxPin = -1; // disable the receiving pin because it is not needed - softhack007: Pin=-1 means "use default" not "disable" static const int txPin = 2; // transmit DMX data over this pin (default is pin 2) -//DMX value array and size. Entry 0 will hold startbyte -static uint8_t dmxData[dmxMaxChannel] = { 0 }; +//DMX value array and size. Entry 0 will hold startbyte, so we need 512+1 elements +static uint8_t dmxData[dmxMaxChannel+1] = { 0 }; static int chanSize = 0; #if !defined(DMX_SEND_ONLY) static int currentChannel = 0; From 32dc54ce7239c9947260db272750fb5c5cfacb19 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Fri, 22 Nov 2024 08:43:45 +0100 Subject: [PATCH 109/287] reverted rotation scale, offset only on static rotation, inverted shift direction - inverting the shift direction in signed int is computationally safe as it is cast into an uint8_t and it matches the original FX in 1D --- wled00/FX.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 47200655d..1463121eb 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -1940,7 +1940,7 @@ uint16_t mode_palette() { using angleType = unsigned; constexpr mathType sInt16Scale = 0x7FFF; constexpr mathType maxAngle = 0x8000; - constexpr mathType staticRotationScale = 255; + constexpr mathType staticRotationScale = 256; constexpr mathType animatedRotationScale = 1; constexpr int16_t (*sinFunction)(uint16_t) = &sin16; constexpr int16_t (*cosFunction)(uint16_t) = &cos16; @@ -1949,7 +1949,7 @@ uint16_t mode_palette() { using wideMathType = float; using angleType = float; constexpr mathType sInt16Scale = 1.0f; - constexpr mathType maxAngle = M_PI / 255.0; + constexpr mathType maxAngle = M_PI / 256.0; constexpr mathType staticRotationScale = 1.0f; constexpr mathType animatedRotationScale = M_TWOPI / double(0xFFFF); constexpr float (*sinFunction)(float) = &sin_t; @@ -1961,12 +1961,12 @@ uint16_t mode_palette() { const int inputShift = SEGMENT.speed; const int inputSize = SEGMENT.intensity; - const int inputRotation = SEGMENT.custom1 + 128; + const int inputRotation = SEGMENT.custom1; const bool inputAnimateShift = SEGMENT.check1; const bool inputAnimateRotation = SEGMENT.check2; const bool inputAssumeSquare = SEGMENT.check3; - const angleType theta = (!inputAnimateRotation) ? (inputRotation * maxAngle / staticRotationScale) : (((strip.now * ((inputRotation >> 4) +1)) & 0xFFFF) * animatedRotationScale); + const angleType theta = (!inputAnimateRotation) ? ((inputRotation + 128) * maxAngle / staticRotationScale) : (((strip.now * ((inputRotation >> 4) +1)) & 0xFFFF) * animatedRotationScale); const mathType sinTheta = sinFunction(theta); const mathType cosTheta = cosFunction(theta); @@ -2017,7 +2017,7 @@ uint16_t mode_palette() { } // Finally, shift the palette a bit. const int paletteOffset = (!inputAnimateShift) ? (inputShift) : (((strip.now * ((inputShift >> 3) +1)) & 0xFFFF) >> 8); - colorIndex += paletteOffset; + colorIndex -= paletteOffset; const uint32_t color = SEGMENT.color_wheel((uint8_t)colorIndex); if (isMatrix) { SEGMENT.setPixelColorXY(x, y, color); From 6aef0e145c234cabb1129feed4068830de45a409 Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Sat, 23 Nov 2024 16:31:07 +0000 Subject: [PATCH 110/287] Remove TOSTRING for releaseString and add quotes to WLED_RELEASE_NAME --- platformio.ini | 42 +++++++++++++++++++++--------------------- wled00/wled.h | 2 +- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/platformio.ini b/platformio.ini index 6d4aa1dc1..fe02213ff 100644 --- a/platformio.ini +++ b/platformio.ini @@ -345,7 +345,7 @@ platform = ${common.platform_wled_default} platform_packages = ${common.platform_packages} board_build.ldscript = ${common.ldscript_4m1m} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=ESP8266 #-DWLED_DISABLE_2D +build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP8266\" #-DWLED_DISABLE_2D lib_deps = ${esp8266.lib_deps} monitor_filters = esp8266_exception_decoder @@ -354,13 +354,13 @@ extends = env:nodemcuv2 ;; using platform version and build options from WLED 0.14.0 platform = ${esp8266.platform_compat} platform_packages = ${esp8266.platform_packages_compat} -build_flags = ${common.build_flags} ${esp8266.build_flags_compat} -D WLED_RELEASE_NAME=ESP8266_compat #-DWLED_DISABLE_2D +build_flags = ${common.build_flags} ${esp8266.build_flags_compat} -D WLED_RELEASE_NAME=\"ESP8266_compat\" #-DWLED_DISABLE_2D ;; lib_deps = ${esp8266.lib_deps_compat} ;; experimental - use older NeoPixelBus 2.7.9 [env:nodemcuv2_160] extends = env:nodemcuv2 board_build.f_cpu = 160000000L -build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=ESP8266_160 #-DWLED_DISABLE_2D +build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP8266_160\" #-DWLED_DISABLE_2D -D USERMOD_AUDIOREACTIVE [env:esp8266_2m] @@ -369,7 +369,7 @@ platform = ${common.platform_wled_default} platform_packages = ${common.platform_packages} board_build.ldscript = ${common.ldscript_2m512k} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=ESP02 +build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP02\" lib_deps = ${esp8266.lib_deps} [env:esp8266_2m_compat] @@ -377,12 +377,12 @@ extends = env:esp8266_2m ;; using platform version and build options from WLED 0.14.0 platform = ${esp8266.platform_compat} platform_packages = ${esp8266.platform_packages_compat} -build_flags = ${common.build_flags} ${esp8266.build_flags_compat} -D WLED_RELEASE_NAME=ESP02_compat #-DWLED_DISABLE_2D +build_flags = ${common.build_flags} ${esp8266.build_flags_compat} -D WLED_RELEASE_NAME=\"ESP02_compat\" #-DWLED_DISABLE_2D [env:esp8266_2m_160] extends = env:esp8266_2m board_build.f_cpu = 160000000L -build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=ESP02_160 +build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP02_160\" -D USERMOD_AUDIOREACTIVE [env:esp01_1m_full] @@ -391,7 +391,7 @@ platform = ${common.platform_wled_default} platform_packages = ${common.platform_packages} board_build.ldscript = ${common.ldscript_1m128k} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=ESP01 -D WLED_DISABLE_OTA +build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP01\" -D WLED_DISABLE_OTA ; -D WLED_USE_REAL_MATH ;; may fix wrong sunset/sunrise times, at the cost of 7064 bytes FLASH and 975 bytes RAM lib_deps = ${esp8266.lib_deps} @@ -400,12 +400,12 @@ extends = env:esp01_1m_full ;; using platform version and build options from WLED 0.14.0 platform = ${esp8266.platform_compat} platform_packages = ${esp8266.platform_packages_compat} -build_flags = ${common.build_flags} ${esp8266.build_flags_compat} -D WLED_RELEASE_NAME=ESP01_compat -D WLED_DISABLE_OTA #-DWLED_DISABLE_2D +build_flags = ${common.build_flags} ${esp8266.build_flags_compat} -D WLED_RELEASE_NAME=\"ESP01_compat\" -D WLED_DISABLE_OTA #-DWLED_DISABLE_2D [env:esp01_1m_full_160] extends = env:esp01_1m_full board_build.f_cpu = 160000000L -build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=ESP01_160 -D WLED_DISABLE_OTA +build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME=\"ESP01_160\" -D WLED_DISABLE_OTA -D USERMOD_AUDIOREACTIVE ; -D WLED_USE_REAL_MATH ;; may fix wrong sunset/sunrise times, at the cost of 7064 bytes FLASH and 975 bytes RAM @@ -414,7 +414,7 @@ board = esp32dev platform = ${esp32.platform} platform_packages = ${esp32.platform_packages} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_RELEASE_NAME=ESP32 #-D WLED_DISABLE_BROWNOUT_DET +build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_RELEASE_NAME=\"ESP32\" #-D WLED_DISABLE_BROWNOUT_DET ${esp32.AR_build_flags} lib_deps = ${esp32.lib_deps} ${esp32.AR_lib_deps} @@ -426,7 +426,7 @@ board = esp32dev platform = ${esp32_idf_V4.platform} platform_packages = ${esp32_idf_V4.platform_packages} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=ESP32_8M #-D WLED_DISABLE_BROWNOUT_DET +build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=\"ESP32_8M\" #-D WLED_DISABLE_BROWNOUT_DET ${esp32.AR_build_flags} lib_deps = ${esp32_idf_V4.lib_deps} ${esp32.AR_lib_deps} @@ -442,7 +442,7 @@ board = esp32dev platform = ${esp32_idf_V4.platform} platform_packages = ${esp32_idf_V4.platform_packages} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=ESP32_16M #-D WLED_DISABLE_BROWNOUT_DET +build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=\"ESP32_16M\" #-D WLED_DISABLE_BROWNOUT_DET ${esp32.AR_build_flags} lib_deps = ${esp32_idf_V4.lib_deps} ${esp32.AR_lib_deps} @@ -458,7 +458,7 @@ board_build.flash_mode = dio ;platform = ${esp32.platform} ;platform_packages = ${esp32.platform_packages} ;build_unflags = ${common.build_unflags} -;build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_RELEASE_NAME=ESP32_audioreactive #-D WLED_DISABLE_BROWNOUT_DET +;build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_RELEASE_NAME=\"ESP32_audioreactive\" #-D WLED_DISABLE_BROWNOUT_DET ; ${esp32.AR_build_flags} ;lib_deps = ${esp32.lib_deps} ; ${esp32.AR_lib_deps} @@ -473,7 +473,7 @@ platform = ${esp32.platform} platform_packages = ${esp32.platform_packages} upload_speed = 921600 build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_RELEASE_NAME=ESP32_Ethernet -D RLYPIN=-1 -D WLED_USE_ETHERNET -D BTNPIN=-1 +build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_RELEASE_NAME=\"ESP32_Ethernet\" -D RLYPIN=-1 -D WLED_USE_ETHERNET -D BTNPIN=-1 ; -D WLED_DISABLE_ESPNOW ;; ESP-NOW requires wifi, may crash with ethernet only ${esp32.AR_build_flags} lib_deps = ${esp32.lib_deps} @@ -489,7 +489,7 @@ board_build.f_flash = 80000000L board_build.flash_mode = qio board_build.partitions = ${esp32.extended_partitions} build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=ESP32_WROVER +build_flags = ${common.build_flags} ${esp32_idf_V4.build_flags} -D WLED_RELEASE_NAME=\"ESP32_WROVER\" -DBOARD_HAS_PSRAM -mfix-esp32-psram-cache-issue ;; Older ESP32 (rev.<3) need a PSRAM fix (increases static RAM used) https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/external-ram.html -D DATA_PINS=25 ${esp32.AR_build_flags} @@ -503,7 +503,7 @@ platform_packages = ${esp32c3.platform_packages} framework = arduino board = esp32-c3-devkitm-1 board_build.partitions = ${esp32.default_partitions} -build_flags = ${common.build_flags} ${esp32c3.build_flags} -D WLED_RELEASE_NAME=ESP32-C3 +build_flags = ${common.build_flags} ${esp32c3.build_flags} -D WLED_RELEASE_NAME=\"ESP32-C3\" -D WLED_WATCHDOG_TIMEOUT=0 -DLOLIN_WIFI_FIX ; seems to work much better with this -DARDUINO_USB_CDC_ON_BOOT=1 ;; for virtual CDC USB @@ -520,7 +520,7 @@ platform = ${esp32s3.platform} platform_packages = ${esp32s3.platform_packages} upload_speed = 921600 build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=ESP32-S3_16MB_opi +build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S3_16MB_opi\" -D CONFIG_LITTLEFS_FOR_IDF_3_2 -D WLED_WATCHDOG_TIMEOUT=0 ;-D ARDUINO_USB_CDC_ON_BOOT=0 ;; -D ARDUINO_USB_MODE=1 ;; for boards with serial-to-USB chip -D ARDUINO_USB_CDC_ON_BOOT=1 -D ARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") @@ -543,7 +543,7 @@ platform = ${esp32s3.platform} platform_packages = ${esp32s3.platform_packages} upload_speed = 921600 build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=ESP32-S3_8MB_opi +build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S3_8MB_opi\" -D CONFIG_LITTLEFS_FOR_IDF_3_2 -D WLED_WATCHDOG_TIMEOUT=0 ;-D ARDUINO_USB_CDC_ON_BOOT=0 ;; -D ARDUINO_USB_MODE=1 ;; for boards with serial-to-USB chip -D ARDUINO_USB_CDC_ON_BOOT=1 -D ARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") @@ -565,7 +565,7 @@ board = esp32s3camlcd ;; this is the only standard board with "opi_opi" board_build.arduino.memory_type = opi_opi upload_speed = 921600 build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=ESP32-S3_WROOM-2 +build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S3_WROOM-2\" -D CONFIG_LITTLEFS_FOR_IDF_3_2 -D WLED_WATCHDOG_TIMEOUT=0 -D ARDUINO_USB_CDC_ON_BOOT=0 ;; -D ARDUINO_USB_MODE=1 ;; for boards with serial-to-USB chip ;; -D ARDUINO_USB_CDC_ON_BOOT=1 -D ARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") @@ -590,7 +590,7 @@ platform = ${esp32s3.platform} platform_packages = ${esp32s3.platform_packages} upload_speed = 921600 build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=ESP32-S3_4M_qspi +build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S3_4M_qspi\" -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_MODE=1 ;; for boards with USB-OTG connector only (USBCDC or "TinyUSB") -DBOARD_HAS_PSRAM -DLOLIN_WIFI_FIX ; seems to work much better with this @@ -611,7 +611,7 @@ board_build.partitions = ${esp32.default_partitions} board_build.flash_mode = qio board_build.f_flash = 80000000L build_unflags = ${common.build_unflags} -build_flags = ${common.build_flags} ${esp32s2.build_flags} -D WLED_RELEASE_NAME=ESP32-S2 +build_flags = ${common.build_flags} ${esp32s2.build_flags} -D WLED_RELEASE_NAME=\"ESP32-S2\" -DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_USB_DFU_ON_BOOT=0 diff --git a/wled00/wled.h b/wled00/wled.h index 2b3a77d24..163ce6b04 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -269,7 +269,7 @@ using PSRAMDynamicJsonDocument = BasicJsonDocument; // Global Variable definitions WLED_GLOBAL char versionString[] _INIT(TOSTRING(WLED_VERSION)); -WLED_GLOBAL char releaseString[] _INIT(TOSTRING(WLED_RELEASE_NAME)); // somehow this will not work if using "const char releaseString[] +WLED_GLOBAL char releaseString[] _INIT(WLED_RELEASE_NAME); // somehow this will not work if using "const char releaseString[] #define WLED_CODENAME "Kōsen" // AP and OTA default passwords (for maximum security change them!) From 0be1df7ee89d01a53b0043dd4c4b66c0e741ccd4 Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Sat, 23 Nov 2024 16:46:41 +0000 Subject: [PATCH 111/287] Stip \" from WLED_RELEASE_NAME --- pio-scripts/output_bins.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pio-scripts/output_bins.py b/pio-scripts/output_bins.py index 633654008..bdd6fc941 100644 --- a/pio-scripts/output_bins.py +++ b/pio-scripts/output_bins.py @@ -19,7 +19,7 @@ def _create_dirs(dirs=["map", "release", "firmware"]): os.makedirs(os.path.join(OUTPUT_DIR, d), exist_ok=True) def create_release(source): - release_name = _get_cpp_define_value(env, "WLED_RELEASE_NAME") + release_name = _get_cpp_define_value(env, "WLED_RELEASE_NAME").replace("\\\"", "") if release_name: version = _get_cpp_define_value(env, "WLED_VERSION") release_file = os.path.join(OUTPUT_DIR, "release", f"WLED_{version}_{release_name}.bin") From d53d7aa2e2d22085979e0134fb7ce1c5dd6684df Mon Sep 17 00:00:00 2001 From: Will Miles Date: Sat, 23 Nov 2024 12:29:46 -0500 Subject: [PATCH 112/287] Defer calling begin() on digital buses NeoPixelBus requires that all parallel I2S bus members be constructed before any of them call Begin(). Implement this by deferring the call to the end of bus construction. Fixes #4301. --- wled00/FX_fcn.cpp | 11 +++-------- wled00/bus_manager.cpp | 4 ++-- wled00/bus_manager.h | 3 ++- wled00/bus_wrapper.h | 2 +- 4 files changed, 8 insertions(+), 12 deletions(-) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index e706f2b43..7177ca89e 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -1286,14 +1286,9 @@ void WS2812FX::finalizeInit() { _isOffRefreshRequired |= bus->isOffRefreshRequired() && !bus->isPWM(); // use refresh bit for phase shift with analog unsigned busEnd = bus->getStart() + bus->getLength(); if (busEnd > _length) _length = busEnd; - #ifdef ESP8266 - // why do we need to reinitialise GPIO3??? - //if (!bus->isDigital() || bus->is2Pin()) continue; - //uint8_t pins[5]; - //if (!bus->getPins(pins)) continue; - //BusDigital* bd = static_cast(bus); - //if (pins[0] == 3) bd->reinit(); - #endif + + // This must be done after all buses have been created, as some kinds (parallel I2S) interact + bus->begin(); } Segment::maxWidth = _length; diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index 5b948b9c4..8281211c1 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -410,7 +410,7 @@ std::vector BusDigital::getLEDTypes() { }; } -void BusDigital::reinit() { +void BusDigital::begin() { if (!_valid) return; PolyBus::begin(_busPtr, _iType, _pins); } @@ -910,7 +910,7 @@ void BusManager::on() { if (busses[i]->isDigital() && busses[i]->getPins(pins)) { if (pins[0] == LED_BUILTIN || pins[1] == LED_BUILTIN) { BusDigital *bus = static_cast(busses[i]); - bus->reinit(); + bus->begin(); break; } } diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h index e96b9de71..ecebc120e 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -79,6 +79,7 @@ class Bus { virtual ~Bus() {} //throw the bus under the bus + virtual void begin() {}; virtual void show() = 0; virtual bool canShow() const { return true; } virtual void setStatusPixel(uint32_t c) {} @@ -213,7 +214,7 @@ class BusDigital : public Bus { uint16_t getLEDCurrent() const override { return _milliAmpsPerLed; } uint16_t getUsedCurrent() const override { return _milliAmpsTotal; } uint16_t getMaxCurrent() const override { return _milliAmpsMax; } - void reinit(); + void begin() override; void cleanup(); static std::vector getLEDTypes(); diff --git a/wled00/bus_wrapper.h b/wled00/bus_wrapper.h index 84c32f46b..7e6e74d85 100644 --- a/wled00/bus_wrapper.h +++ b/wled00/bus_wrapper.h @@ -597,7 +597,7 @@ class PolyBus { case I_HS_P98_3: busPtr = new B_HS_P98_3(len, pins[1], pins[0]); break; case I_SS_P98_3: busPtr = new B_SS_P98_3(len, pins[1], pins[0]); break; } - begin(busPtr, busType, pins, clock_kHz); + return busPtr; } From 0c77dbb7ea63c0b228d5d0f576d6eb0dcb7daeb1 Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Sun, 24 Nov 2024 13:55:23 +0000 Subject: [PATCH 113/287] cleanup WLED_RELEASE_NAME only if present --- pio-scripts/output_bins.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pio-scripts/output_bins.py b/pio-scripts/output_bins.py index bdd6fc941..4d1594d84 100644 --- a/pio-scripts/output_bins.py +++ b/pio-scripts/output_bins.py @@ -19,8 +19,9 @@ def _create_dirs(dirs=["map", "release", "firmware"]): os.makedirs(os.path.join(OUTPUT_DIR, d), exist_ok=True) def create_release(source): - release_name = _get_cpp_define_value(env, "WLED_RELEASE_NAME").replace("\\\"", "") - if release_name: + release_name_def = _get_cpp_define_value(env, "WLED_RELEASE_NAME") + if release_name_def: + release_name = release_name_def.replace("\\\"", "") version = _get_cpp_define_value(env, "WLED_VERSION") release_file = os.path.join(OUTPUT_DIR, "release", f"WLED_{version}_{release_name}.bin") release_gz_file = release_file + ".gz" From 20f8d3c8a9cc0812261089714baaf08538aabb19 Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Sun, 24 Nov 2024 15:26:30 +0000 Subject: [PATCH 114/287] Builds without WLED_RELEASE_NAME should be called Custom --- wled00/wled.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/wled.h b/wled00/wled.h index 163ce6b04..bb647340b 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -264,7 +264,7 @@ using PSRAMDynamicJsonDocument = BasicJsonDocument; #define WLED_VERSION dev #endif #ifndef WLED_RELEASE_NAME - #define WLED_RELEASE_NAME dev_release + #define WLED_RELEASE_NAME "Custom" #endif // Global Variable definitions From 5b989adebc666f5d6314e0ece81c1141a0ae454f Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Mon, 25 Nov 2024 15:27:53 +0100 Subject: [PATCH 115/287] Allow TV Simulator on single LED segments I've checked the code - nothing preventing the effect to run with SEGLEN=1 --- wled00/FX.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 947788fee..80be13c62 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -4631,7 +4631,7 @@ uint16_t mode_tv_simulator(void) { return FRAMETIME; } -static const char _data_FX_MODE_TV_SIMULATOR[] PROGMEM = "TV Simulator@!,!;;"; +static const char _data_FX_MODE_TV_SIMULATOR[] PROGMEM = "TV Simulator@!,!;;!;01"; /* From dd533a9ab4f37c8bc271f4a7cdbd7c0fe51456a3 Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Mon, 25 Nov 2024 23:00:31 +0000 Subject: [PATCH 116/287] Update sample WLED_RELEASE_NAME --- platformio_override.sample.ini | 2 +- wled00/wled.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/platformio_override.sample.ini b/platformio_override.sample.ini index 8e5fdf003..a4dea6677 100644 --- a/platformio_override.sample.ini +++ b/platformio_override.sample.ini @@ -37,7 +37,7 @@ build_flags = ${common.build_flags} ${esp8266.build_flags} ; *** To use the below defines/overrides, copy and paste each onto it's own line just below build_flags in the section above. ; ; Set a release name that may be used to distinguish required binary for flashing -; -D WLED_RELEASE_NAME=ESP32_MULTI_USREMODS +; -D WLED_RELEASE_NAME=\"ESP32_MULTI_USREMODS\" ; ; disable specific features ; -D WLED_DISABLE_OTA diff --git a/wled00/wled.h b/wled00/wled.h index bb647340b..fcbc11978 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -269,7 +269,7 @@ using PSRAMDynamicJsonDocument = BasicJsonDocument; // Global Variable definitions WLED_GLOBAL char versionString[] _INIT(TOSTRING(WLED_VERSION)); -WLED_GLOBAL char releaseString[] _INIT(WLED_RELEASE_NAME); // somehow this will not work if using "const char releaseString[] +WLED_GLOBAL char releaseString[] _INIT(WLED_RELEASE_NAME); // must include the quotes when defining, e.g -D WLED_RELEASE_NAME=\"ESP32_MULTI_USREMODS\" #define WLED_CODENAME "Kōsen" // AP and OTA default passwords (for maximum security change them!) From d87c5035dd0ac1c908c6a4680640999ab52bbddd Mon Sep 17 00:00:00 2001 From: AlDIY <87589371+dosipod@users.noreply.github.com> Date: Tue, 26 Nov 2024 19:33:22 +0300 Subject: [PATCH 117/287] Update settings_sec.htm --- wled00/data/settings_sec.htm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/wled00/data/settings_sec.htm b/wled00/data/settings_sec.htm index fa75882c0..7fe0fe62e 100644 --- a/wled00/data/settings_sec.htm +++ b/wled00/data/settings_sec.htm @@ -60,8 +60,8 @@

Backup & Restore

⚠ Restoring presets/configuration will OVERWRITE your current presets/configuration.
- Incorrect upload or configuration may require a factory reset or re-flashing of your ESP.
- For security reasons, passwords are not backed up. + Incorrect upload or configuration may require a factory reset or re-flashing of your ESP.
+ For security reasons, passwords are not backed up. Backup presets
Restore presets


Backup configuration
@@ -78,4 +78,4 @@ - \ No newline at end of file + From cd1c13b4b1e242e736cad086abcb7a9dcc727b2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Kristan?= Date: Tue, 26 Nov 2024 19:35:15 +0100 Subject: [PATCH 118/287] Fix for #4300 --- wled00/set.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/wled00/set.cpp b/wled00/set.cpp index 712e5f254..2a6fdd3fb 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -215,6 +215,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) //doInitBusses = busesChanged; // we will do that below to ensure all input data is processed // we will not bother with pre-allocating ColorOrderMappings vector + BusManager::getColorOrderMap().reset(); for (int s = 0; s < WLED_MAX_COLOR_ORDER_MAPPINGS; s++) { int offset = s < 10 ? 48 : 55; char xs[4] = "XS"; xs[2] = offset+s; xs[3] = 0; //start LED From 1a8aaa3b26feba106259b6842ad9f8998fb3233d Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Tue, 26 Nov 2024 20:32:39 +0100 Subject: [PATCH 119/287] Speed improvements, commented legacy _t trig functions - speed improvement: by default M_TWOPI is treated as a double float - directly calling sin16_t in cos_approx() saves a lot of overhead --- wled00/wled_math.cpp | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/wled00/wled_math.cpp b/wled00/wled_math.cpp index a191968e1..583381d66 100644 --- a/wled00/wled_math.cpp +++ b/wled00/wled_math.cpp @@ -10,14 +10,17 @@ //#define WLED_DEBUG_MATH -#define modd(x, y) ((x) - (int)((x) / (y)) * (y)) - -// Note: cos_t, sin_t and tan_t are very accurate but may be slow +// Note: cos_t, sin_t and tan_t are very accurate but slow // the math.h functions use several kB of flash and are to be avoided if possible // sin16_t / cos16_t are faster and much more accurate than the fastled variants // sin_approx and cos_approx are float wrappers for sin16_t/cos16_t and have an accuracy better than +/-0.0015 compared to sinf() // sin8_t / cos8_t are fastled replacements and use sin16_t / cos16_t. Slightly slower than fastled version but very accurate + +// Taylor series approximations, replaced with Bhaskara I's approximation +/* +#define modd(x, y) ((x) - (int)((x) / (y)) * (y)) + float cos_t(float phi) { float x = modd(phi, M_TWOPI); @@ -54,6 +57,7 @@ float tan_t(float x) { #endif return res; } +*/ // 16-bit, integer based Bhaskara I's sine approximation: 16*x*(pi - x) / (5*pi^2 - 4*x*(pi - x)) // input is 16bit unsigned (0-65535), output is 16bit signed (-32767 to +32767) @@ -85,17 +89,18 @@ uint8_t cos8_t(uint8_t theta) { return sin8_t(theta + 64); //cos(x) = sin(x+pi/2) } -float sin_approx(float theta) -{ - uint16_t scaled_theta = (int)(theta * (0xFFFF / M_TWOPI)); // note: do not cast negative float to uint! cast to int first (undefined on C3) +float sin_approx(float theta) { + uint16_t scaled_theta = (int)(theta * (float)(0xFFFF / M_TWOPI)); // note: do not cast negative float to uint! cast to int first (undefined on C3) int32_t result = sin16_t(scaled_theta); float sin = float(result) / 0x7FFF; return sin; } -float cos_approx(float theta) -{ - return sin_approx(theta + M_PI_2); +float cos_approx(float theta) { + uint16_t scaled_theta = (int)(theta * (float)(0xFFFF / M_TWOPI)); // note: do not cast negative float to uint! cast to int first (undefined on C3) + int32_t result = sin16_t(scaled_theta + 16384); + float cos = float(result) / 0x7FFF; + return cos; } float tan_approx(float x) { From 0a05611e1d5a8dcccf515ba616042692197258f6 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Tue, 26 Nov 2024 20:59:36 +0100 Subject: [PATCH 120/287] more improvements to setPixelColor - code is a bit cleaner and faster as well - chaning array access to pointer access in bus_manager makes it a few instructions faster - changed getNumberOfPins and getNumberOfChannels to return 32bit values, saving the unnecessary 8bit conversion --- wled00/FX_2Dfcn.cpp | 41 +++++++++++++++++++++-------------------- wled00/bus_manager.cpp | 26 ++++++++++++++++---------- wled00/bus_manager.h | 6 +++--- 3 files changed, 40 insertions(+), 33 deletions(-) diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index adb9f8bca..ba0c69322 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -156,21 +156,26 @@ uint16_t IRAM_ATTR_YN Segment::XY(int x, int y) // raw setColor function without checks (checks are done in setPixelColorXY()) void IRAM_ATTR_YN Segment::_setPixelColorXY_raw(int& x, int& y, uint32_t& col) { + const int baseX = start + x; + const int baseY = startY + y; #ifndef WLED_DISABLE_MODE_BLEND // if blending modes, blend with underlying pixel - if (_modeBlend) col = color_blend(strip.getPixelColorXY(start + x, startY + y), col, 0xFFFFU - progress(), true); + if (_modeBlend) col = color_blend(strip.getPixelColorXY(baseX, baseY), col, 0xFFFFU - progress(), true); #endif - strip.setPixelColorXY(start + x, startY + y, col); - if (mirror) { //set the corresponding horizontally mirrored pixel - if (transpose) strip.setPixelColorXY(start + x, startY + height() - y - 1, col); - else strip.setPixelColorXY(start + width() - x - 1, startY + y, col); - } - if (mirror_y) { //set the corresponding vertically mirrored pixel - if (transpose) strip.setPixelColorXY(start + width() - x - 1, startY + y, col); - else strip.setPixelColorXY(start + x, startY + height() - y - 1, col); - } - if (mirror_y && mirror) { //set the corresponding vertically AND horizontally mirrored pixel - strip.setPixelColorXY(start + width() - x - 1, startY + height() - y - 1, col); + strip.setPixelColorXY(baseX, baseY, col); + + // Apply mirroring + if (mirror || mirror_y) { + auto setMirroredPixel = [&](int mx, int my) { + strip.setPixelColorXY(mx, my, col); + }; + + const int mirrorX = start + width() - x - 1; + const int mirrorY = startY + height() - y - 1; + + if (mirror) setMirroredPixel(transpose ? baseX : mirrorX, transpose ? mirrorY : baseY); + if (mirror_y) setMirroredPixel(transpose ? mirrorX : baseX, transpose ? baseY : mirrorY); + if (mirror && mirror_y) setMirroredPixel(mirrorX, mirrorY); } } @@ -196,16 +201,12 @@ void IRAM_ATTR_YN Segment::setPixelColorXY(int x, int y, uint32_t col) int H = height(); x *= groupLen; // expand to physical pixels y *= groupLen; // expand to physical pixels - int yY = y; - for (int j = 0; j < grouping; j++) { // groupping vertically - if (yY >= H) break; - int xX = x; - for (int g = 0; g < grouping; g++) { // groupping horizontally - if (xX >= W) break; // we have reached X dimension's end + const int maxY = std::min(y + grouping, H); + const int maxX = std::min(x + grouping, W); + for (int yY = y; yY < maxY; yY++) { + for (int xX = x; xX < maxX; xX++) { _setPixelColorXY_raw(xX, yY, col); - xX++; } - yY++; } } else { _setPixelColorXY_raw(x, y, col); diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index 404c33449..941135497 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -308,20 +308,20 @@ void BusDigital::setStatusPixel(uint32_t c) { void IRAM_ATTR BusDigital::setPixelColor(unsigned pix, uint32_t c) { if (!_valid) return; - uint8_t cctWW = 0, cctCW = 0; if (hasWhite()) c = autoWhiteCalc(c); if (Bus::_cct >= 1900) c = colorBalanceFromKelvin(Bus::_cct, c); //color correction from CCT if (_data) { size_t offset = pix * getNumberOfChannels(); + uint8_t* dataptr = _data + offset; if (hasRGB()) { - _data[offset++] = R(c); - _data[offset++] = G(c); - _data[offset++] = B(c); + *dataptr++ = R(c); + *dataptr++ = G(c); + *dataptr++ = B(c); } - if (hasWhite()) _data[offset++] = W(c); + if (hasWhite()) *dataptr++ = W(c); // unfortunately as a segment may span multiple buses or a bus may contain multiple segments and each segment may have different CCT // we need to store CCT value for each pixel (if there is a color correction in play, convert K in CCT ratio) - if (hasCCT()) _data[offset] = Bus::_cct >= 1900 ? (Bus::_cct - 1900) >> 5 : (Bus::_cct < 0 ? 127 : Bus::_cct); // TODO: if _cct == -1 we simply ignore it + if (hasCCT()) *dataptr = Bus::_cct >= 1900 ? (Bus::_cct - 1900) >> 5 : (Bus::_cct < 0 ? 127 : Bus::_cct); // TODO: if _cct == -1 we simply ignore it } else { if (_reversed) pix = _len - pix -1; pix += _skip; @@ -336,8 +336,14 @@ void IRAM_ATTR BusDigital::setPixelColor(unsigned pix, uint32_t c) { case 2: c = RGBW32(R(cOld), G(cOld), W(c) , 0); break; } } - if (hasCCT()) Bus::calculateCCT(c, cctWW, cctCW); - PolyBus::setPixelColor(_busPtr, _iType, pix, c, co, (cctCW<<8) | cctWW); + uint16_t wwcw = 0; + if (hasCCT()) { + uint8_t cctWW = 0, cctCW = 0; + Bus::calculateCCT(c, cctWW, cctCW); + wwcw = (cctCW<<8) | cctWW; + } + + PolyBus::setPixelColor(_busPtr, _iType, pix, c, co, wwcw); } } @@ -345,7 +351,7 @@ void IRAM_ATTR BusDigital::setPixelColor(unsigned pix, uint32_t c) { uint32_t IRAM_ATTR BusDigital::getPixelColor(unsigned pix) const { if (!_valid) return 0; if (_data) { - size_t offset = pix * getNumberOfChannels(); + const size_t offset = pix * getNumberOfChannels(); uint32_t c; if (!hasRGB()) { c = RGBW32(_data[offset], _data[offset], _data[offset], _data[offset]); @@ -356,7 +362,7 @@ uint32_t IRAM_ATTR BusDigital::getPixelColor(unsigned pix) const { } else { if (_reversed) pix = _len - pix -1; pix += _skip; - unsigned co = _colorOrderMap.getPixelColorOrder(pix+_start, _colorOrder); + const unsigned co = _colorOrderMap.getPixelColorOrder(pix+_start, _colorOrder); uint32_t c = restoreColorLossy(PolyBus::getPixelColor(_busPtr, _iType, (_type==TYPE_WS2812_1CH_X3) ? IC_INDEX_WS2812_1CH_3X(pix) : pix, co),_bri); if (_type == TYPE_WS2812_1CH_X3) { // map to correct IC, each controls 3 LEDs unsigned r = R(c); diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h index e25a06849..49077f27c 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -110,7 +110,7 @@ class Bus { inline void setStart(uint16_t start) { _start = start; } inline void setAutoWhiteMode(uint8_t m) { if (m < 5) _autoWhiteMode = m; } inline uint8_t getAutoWhiteMode() const { return _autoWhiteMode; } - inline uint8_t getNumberOfChannels() const { return hasWhite() + 3*hasRGB() + hasCCT(); } + inline uint32_t getNumberOfChannels() const { return hasWhite() + 3*hasRGB() + hasCCT(); } inline uint16_t getStart() const { return _start; } inline uint8_t getType() const { return _type; } inline bool isOk() const { return _valid; } @@ -119,8 +119,8 @@ class Bus { inline bool containsPixel(uint16_t pix) const { return pix >= _start && pix < _start + _len; } static inline std::vector getLEDTypes() { return {{TYPE_NONE, "", PSTR("None")}}; } // not used. just for reference for derived classes - static constexpr uint8_t getNumberOfPins(uint8_t type) { return isVirtual(type) ? 4 : isPWM(type) ? numPWMPins(type) : is2Pin(type) + 1; } // credit @PaoloTK - static constexpr uint8_t getNumberOfChannels(uint8_t type) { return hasWhite(type) + 3*hasRGB(type) + hasCCT(type); } + static constexpr uint32_t getNumberOfPins(uint8_t type) { return isVirtual(type) ? 4 : isPWM(type) ? numPWMPins(type) : is2Pin(type) + 1; } // credit @PaoloTK + static constexpr uint32_t getNumberOfChannels(uint8_t type) { return hasWhite(type) + 3*hasRGB(type) + hasCCT(type); } static constexpr bool hasRGB(uint8_t type) { return !((type >= TYPE_WS2812_1CH && type <= TYPE_WS2812_WWA) || type == TYPE_ANALOG_1CH || type == TYPE_ANALOG_2CH || type == TYPE_ONOFF); } From ca176c7549f92ae6ba611b9b6f9e9edbf13bad15 Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Wed, 27 Nov 2024 10:31:41 +0000 Subject: [PATCH 121/287] rename workflow ready for workflow refactor --- .github/workflows/{wled-ci.yml => build.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{wled-ci.yml => build.yml} (100%) diff --git a/.github/workflows/wled-ci.yml b/.github/workflows/build.yml similarity index 100% rename from .github/workflows/wled-ci.yml rename to .github/workflows/build.yml From feab27295db32f9ad367ec3a68d3d7ad9b3779eb Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Wed, 27 Nov 2024 10:33:44 +0000 Subject: [PATCH 122/287] workflow refactor to used shared build file --- .github/workflows/build.yml | 8 +++++--- .github/workflows/wled-ci.yml | 11 +++++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/wled-ci.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1dcab26ab..80fce38c3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,7 +1,9 @@ -name: WLED CI - -on: [push, pull_request] +name: WLED Build +# Only included into other workflows +on: + workflow_call: + jobs: get_default_envs: diff --git a/.github/workflows/wled-ci.yml b/.github/workflows/wled-ci.yml new file mode 100644 index 000000000..3c862c185 --- /dev/null +++ b/.github/workflows/wled-ci.yml @@ -0,0 +1,11 @@ +name: WLED CI + +on: + push: + branches: + - '*' + pull_request: + +jobs: + wled_build: + uses: ./.github/workflows/build.yml From 95718ab6ecb830b57f74d7cbed0b142d70b33c63 Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Wed, 27 Nov 2024 10:35:04 +0000 Subject: [PATCH 123/287] Dedicated release workflow --- .github/workflows/build.yml | 16 ---------------- .github/workflows/release.yml | 28 ++++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 16 deletions(-) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 80fce38c3..e5fdfc5a3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -64,22 +64,6 @@ jobs: path: | build_output/release/*.bin build_output/release/*_ESP02*.bin.gz - release: - name: Create Release - runs-on: ubuntu-latest - needs: build - if: startsWith(github.ref, 'refs/tags/') - steps: - - uses: actions/download-artifact@v4 - with: - merge-multiple: true - - name: Create draft release - uses: softprops/action-gh-release@v1 - with: - draft: True - files: | - *.bin - *.bin.gz testCdata: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..27beec99c --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,28 @@ +name: WLED Release CI + +on: + push: + tags: + - '*' + +jobs: + + wled_build: + uses: ./.github/workflows/build.yml + + release: + name: Create Release + runs-on: ubuntu-latest + needs: wled_build + steps: + - uses: actions/download-artifact@v4 + with: + merge-multiple: true + - name: Create draft release + uses: softprops/action-gh-release@v1 + with: + draft: True + files: | + *.bin + *.bin.gz + From cc55f6015dfff436cb4ff3f595ae53695393f8b3 Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Wed, 27 Nov 2024 10:56:53 +0000 Subject: [PATCH 124/287] Cache always misses for tags, so save space and do not save back --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e5fdfc5a3..ac1db6445 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -42,6 +42,7 @@ jobs: - run: npm ci - name: Cache PlatformIO uses: actions/cache@v4 + if: !startsWith(github.ref, 'refs/tags/') with: path: | ~/.platformio/.cache From 6cbdd825eb6cea9c2cc5c6219f4fd7db567cd8ff Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Wed, 27 Nov 2024 10:59:23 +0000 Subject: [PATCH 125/287] Revert "Cache always misses for tags, so save space and do not save back" This reverts commit cc55f6015dfff436cb4ff3f595ae53695393f8b3. --- .github/workflows/build.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ac1db6445..e5fdfc5a3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -42,7 +42,6 @@ jobs: - run: npm ci - name: Cache PlatformIO uses: actions/cache@v4 - if: !startsWith(github.ref, 'refs/tags/') with: path: | ~/.platformio/.cache From acc8b9cdbc22086762d5c05e6d2ff419c96fc074 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Wed, 27 Nov 2024 09:00:25 -0500 Subject: [PATCH 126/287] BusDigital::begin: Pass clock rate argument Fixes bug introduced by #4312. --- wled00/bus_manager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index 8281211c1..715470800 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -412,7 +412,7 @@ std::vector BusDigital::getLEDTypes() { void BusDigital::begin() { if (!_valid) return; - PolyBus::begin(_busPtr, _iType, _pins); + PolyBus::begin(_busPtr, _iType, _pins, _frequencykHz); } void BusDigital::cleanup() { From dcba1aad109c29b9594973d99270f62006dd0289 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Wed, 27 Nov 2024 09:05:10 -0500 Subject: [PATCH 127/287] PolyBus: Clarify use of clock_kHz While not used by most bus types, it's not an optional parameter. --- wled00/bus_manager.cpp | 2 +- wled00/bus_wrapper.h | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index 715470800..5b031bebb 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -150,7 +150,7 @@ BusDigital::BusDigital(BusConfig &bc, uint8_t nr, const ColorOrderMap &com) //_buffering = bc.doubleBuffer; uint16_t lenToCreate = bc.count; if (bc.type == TYPE_WS2812_1CH_X3) lenToCreate = NUM_ICS_WS2812_1CH_3X(bc.count); // only needs a third of "RGB" LEDs for NeoPixelBus - _busPtr = PolyBus::create(_iType, _pins, lenToCreate + _skip, nr, _frequencykHz); + _busPtr = PolyBus::create(_iType, _pins, lenToCreate + _skip, nr); _valid = (_busPtr != nullptr); DEBUG_PRINTF_P(PSTR("%successfully inited strip %u (len %u) with type %u and pins %u,%u (itype %u). mA=%d/%d\n"), _valid?"S":"Uns", nr, bc.count, bc.type, _pins[0], is2Pin(bc.type)?_pins[1]:255, _iType, _milliAmpsPerLed, _milliAmpsMax); } diff --git a/wled00/bus_wrapper.h b/wled00/bus_wrapper.h index 7e6e74d85..d2a18c9d8 100644 --- a/wled00/bus_wrapper.h +++ b/wled00/bus_wrapper.h @@ -336,7 +336,7 @@ class PolyBus { // initialize SPI bus speed for DotStar methods template - static void beginDotStar(void* busPtr, int8_t sck, int8_t miso, int8_t mosi, int8_t ss, uint16_t clock_kHz = 0U) { + static void beginDotStar(void* busPtr, int8_t sck, int8_t miso, int8_t mosi, int8_t ss, uint16_t clock_kHz /* 0 == use default */) { T dotStar_strip = static_cast(busPtr); #ifdef ESP8266 dotStar_strip->Begin(); @@ -363,7 +363,7 @@ class PolyBus { tm1914_strip->SetPixelSettings(NeoTm1914Settings()); //NeoTm1914_Mode_DinFdinAutoSwitch, NeoTm1914_Mode_DinOnly, NeoTm1914_Mode_FdinOnly } - static void begin(void* busPtr, uint8_t busType, uint8_t* pins, uint16_t clock_kHz = 0U) { + static void begin(void* busPtr, uint8_t busType, uint8_t* pins, uint16_t clock_kHz /* only used by DotStar */) { switch (busType) { case I_NONE: break; #ifdef ESP8266 @@ -480,7 +480,7 @@ class PolyBus { } } - static void* create(uint8_t busType, uint8_t* pins, uint16_t len, uint8_t channel, uint16_t clock_kHz = 0U) { + static void* create(uint8_t busType, uint8_t* pins, uint16_t len, uint8_t channel) { #if defined(ARDUINO_ARCH_ESP32) && !(defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C3)) // NOTE: "channel" is only used on ESP32 (and its variants) for RMT channel allocation // since 0.15.0-b3 I2S1 is favoured for classic ESP32 and moved to position 0 (channel 0) so we need to subtract 1 for correct RMT allocation From b1dd27b516fa93919edd967c85bd8b287b9b914d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Kristan?= Date: Wed, 27 Nov 2024 16:08:10 +0100 Subject: [PATCH 128/287] Fix for #4321 --- wled00/data/settings_leds.htm | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index 6be5becd1..baf80a5d7 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -42,15 +42,14 @@ if (loc) d.Sf.action = getURL('/settings/leds'); } function bLimits(b,v,p,m,l,o=5,d=2,a=6) { - // maxB - max buses (can be changed if using ESP32 parallel I2S) - // maxD - max digital channels (can be changed if using ESP32 parallel I2S) - // maxA - max analog channels - // maxV - min virtual buses - // maxPB - max LEDs per bus - // maxM - max LED memory - // maxL - max LEDs (will serve to determine ESP >1664 == ESP32) - // maxCO - max Color Order mappings - oMaxB = maxB = b; maxD = d, maxA = a, maxV = v; maxM = m; maxPB = p; maxL = l; maxCO = o; + oMaxB = maxB = b; // maxB - max buses (can be changed if using ESP32 parallel I2S) + maxD = d; // maxD - max digital channels (can be changed if using ESP32 parallel I2S) + maxA = a; // maxA - max analog channels + maxV = v; // maxV - min virtual buses + maxPB = p; // maxPB - max LEDs per bus + maxM = m; // maxM - max LED memory + maxL = l; // maxL - max LEDs (will serve to determine ESP >1664 == ESP32) + maxCO = o; // maxCO - max Color Order mappings } function pinsOK() { var ok = true; From 4cd0563a9398812f65f8dbbe22d0ab48799f4de8 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Wed, 27 Nov 2024 21:53:32 +0100 Subject: [PATCH 129/287] =?UTF-8?q?changed=2090=C2=B0=20offset=20to=20hex,?= =?UTF-8?q?=20fixed=20potential=20bug=20in=20FX=20using=20sin/cos=20with?= =?UTF-8?q?=20incrementing=20number?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - sin/cos calls with incrementing numbers can lead to bad outcomes, the functions (_approx or original sinf/cosf) return bad values for very large float inputs --- wled00/FX.cpp | 5 +++-- wled00/wled_math.cpp | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 5b4513dc7..da2967a3c 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -5314,8 +5314,8 @@ uint16_t mode_2DJulia(void) { // An animated Julia set reAl = -0.94299f; // PixelBlaze example imAg = 0.3162f; - reAl += sin_t((float)strip.now/305.f)/20.f; - imAg += sin_t((float)strip.now/405.f)/20.f; + reAl += (float)sin16_t(strip.now * 34) / 655340.f; + imAg += (float)sin16_t(strip.now * 26) / 655340.f; dx = (xmax - xmin) / (cols); // Scale the delta x and y values to our matrix size. dy = (ymax - ymin) / (rows); @@ -6279,6 +6279,7 @@ uint16_t mode_2Dplasmarotozoom() { } } *a -= 0.03f + float(SEGENV.speed-128)*0.0002f; // rotation speed + if(*a < -6283.18530718f) *a += 6283.18530718f; // 1000*2*PI, protect sin/cos from very large input float values (will give wrong results) return FRAMETIME; } diff --git a/wled00/wled_math.cpp b/wled00/wled_math.cpp index 583381d66..a8ec55400 100644 --- a/wled00/wled_math.cpp +++ b/wled00/wled_math.cpp @@ -76,7 +76,7 @@ int16_t sin16_t(uint16_t theta) { } int16_t cos16_t(uint16_t theta) { - return sin16_t(theta + 16384); //cos(x) = sin(x+pi/2) + return sin16_t(theta + 0x4000); //cos(x) = sin(x+pi/2) } uint8_t sin8_t(uint8_t theta) { @@ -98,7 +98,7 @@ float sin_approx(float theta) { float cos_approx(float theta) { uint16_t scaled_theta = (int)(theta * (float)(0xFFFF / M_TWOPI)); // note: do not cast negative float to uint! cast to int first (undefined on C3) - int32_t result = sin16_t(scaled_theta + 16384); + int32_t result = sin16_t(scaled_theta + 0x4000); float cos = float(result) / 0x7FFF; return cos; } From fa4c23b76ec7843aff6a8f3e5b9bc7a46a41858e Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Wed, 27 Nov 2024 22:23:48 +0100 Subject: [PATCH 130/287] minor improvement --- wled00/FX.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index a2ceb591a..a0d080818 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -7737,9 +7737,9 @@ uint16_t mode_2Doctopus() { const int C_Y = (rows / 2) + ((SEGMENT.custom2 - 128)*rows)/255; for (int x = 0; x < cols; x++) { for (int y = 0; y < rows; y++) { - rMap[XY(x, y)].angle = int(40.7436f * atan2_t((y - C_Y), (x - C_X))); // avoid 128*atan2()/PI int dx = (x - C_X); int dy = (y - C_Y); + rMap[XY(x, y)].angle = int(40.7436f * atan2_t(dy, dx)); // avoid 128*atan2()/PI rMap[XY(x, y)].radius = sqrtf(dx * dx + dy * dy) * mapp; //thanks Sutaburosu } } From 8b1d712e1e04a696086b7e5b9df06a4dff97b289 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Sun, 1 Dec 2024 13:00:37 -0500 Subject: [PATCH 131/287] settings_leds: Initialize current limiter field When adding a new bus, the numeric current limit field was not being initialized; this was causing it to save 0 when saved instead of the default 55mA value. --- wled00/data/settings_leds.htm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index baf80a5d7..ff5087aed 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -422,7 +422,7 @@ mA/LED:
- +
PSU: mA
Color Order: From ae8c3b02d04064cb809af2f3f10413563fae2dd7 Mon Sep 17 00:00:00 2001 From: Frank <91616163+softhack007@users.noreply.github.com> Date: Mon, 2 Dec 2024 21:35:48 +0100 Subject: [PATCH 132/287] blends FX - hotfix for black pixels fixing an off-by-one error to solve #4335 --- wled00/FX.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index a0d080818..61d7a66a8 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -4497,7 +4497,7 @@ uint16_t mode_blends(void) { unsigned offset = 0; for (unsigned i = 0; i < SEGLEN; i++) { SEGMENT.setPixelColor(i, pixels[offset++]); - if (offset > pixelLen) offset = 0; + if (offset >= pixelLen) offset = 0; } return FRAMETIME; From 8db8ecfef3cbbf8cd70862d856742874e2a9306f Mon Sep 17 00:00:00 2001 From: Will Miles Date: Mon, 2 Dec 2024 21:50:29 +0000 Subject: [PATCH 133/287] settings_leds: Fix quotes on LA value --- wled00/data/settings_leds.htm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index ff5087aed..ce9c8ceed 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -422,7 +422,7 @@ mA/LED:
- +
PSU: mA
Color Order: From d620930f10f06bce563d95c94e6513739959a012 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Mon, 2 Dec 2024 21:52:41 +0000 Subject: [PATCH 134/287] settings_leds: Remove unused variables Remove a couple of leftover variables from previous revisions. --- wled00/data/settings_leds.htm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index ce9c8ceed..71fec2798 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -6,7 +6,7 @@ LED Settings diff --git a/wled00/dmx_input.cpp b/wled00/dmx_input.cpp index afbc9f0d0..3197375f1 100644 --- a/wled00/dmx_input.cpp +++ b/wled00/dmx_input.cpp @@ -151,9 +151,9 @@ void DMXInput::init(uint8_t rxPin, uint8_t txPin, uint8_t enPin, uint8_t inputPo const bool pinsAllocated = PinManager::allocateMultiplePins(pins, 3, PinOwner::DMX_INPUT); if (!pinsAllocated) { DEBUG_PRINTF("DMXInput: Error: Failed to allocate pins for DMX_INPUT. Pins already in use:\n"); - DEBUG_PRINTF("rx in use by: %s\n", pinManager.getPinOwnerText(rxPin).c_str()); - DEBUG_PRINTF("tx in use by: %s\n", pinManager.getPinOwnerText(txPin).c_str()); - DEBUG_PRINTF("en in use by: %s\n", pinManager.getPinOwnerText(enPin).c_str()); + DEBUG_PRINTF("rx in use by: %s\n", PinManager::getPinOwner(rxPin)); + DEBUG_PRINTF("tx in use by: %s\n", PinManager::getPinOwner(txPin)); + DEBUG_PRINTF("en in use by: %s\n", PinManager::getPinOwner(enPin)); return; } From 1df717084b944f570ab5b5dde81d412def785b0c Mon Sep 17 00:00:00 2001 From: Will Miles Date: Tue, 27 Aug 2024 13:45:33 -0400 Subject: [PATCH 279/287] Update to AsyncWebServer v2.4.0 Includes update to use matching newer AsyncTCP on ESP32 --- platformio.ini | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/platformio.ini b/platformio.ini index 5e0c01db7..6f1d923d7 100644 --- a/platformio.ini +++ b/platformio.ini @@ -140,7 +140,7 @@ lib_deps = IRremoteESP8266 @ 2.8.2 makuna/NeoPixelBus @ 2.8.0 #https://github.com/makuna/NeoPixelBus.git#CoreShaderBeta - https://github.com/Aircoookie/ESPAsyncWebServer.git#v2.2.1 + https://github.com/Aircoookie/ESPAsyncWebServer.git#v2.4.0 # for I2C interface ;Wire # ESP-NOW library @@ -236,7 +236,7 @@ lib_deps_compat = IRremoteESP8266 @ 2.8.2 makuna/NeoPixelBus @ 2.7.9 https://github.com/blazoncek/QuickESPNow.git#optional-debug - https://github.com/Aircoookie/ESPAsyncWebServer.git#v2.2.1 + https://github.com/Aircoookie/ESPAsyncWebServer.git#v2.4.0 [esp32] @@ -259,7 +259,7 @@ large_partitions = tools/WLED_ESP32_8MB.csv extreme_partitions = tools/WLED_ESP32_16MB_9MB_FS.csv lib_deps = https://github.com/lorol/LITTLEFS.git - https://github.com/pbolduc/AsyncTCP.git @ 1.2.0 + willmmiles/AsyncTCP @ 1.3.1 ${env.lib_deps} # additional build flags for audioreactive AR_build_flags = -D USERMOD_AUDIOREACTIVE @@ -284,7 +284,7 @@ build_flags = -g -DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3 -D WLED_ENABLE_DMX_INPUT lib_deps = - https://github.com/pbolduc/AsyncTCP.git @ 1.2.0 + willmmiles/AsyncTCP @ 1.3.1 https://github.com/someweisguy/esp_dmx.git#47db25d ${env.lib_deps} board_build.partitions = ${esp32.default_partitions} ;; default partioning for 4MB Flash - can be overridden in build envs @@ -304,7 +304,7 @@ build_flags = -g ;; please make sure that the following flags are properly set (to 0 or 1) by your board.json, or included in your custom platformio_override.ini entry: ;; ARDUINO_USB_CDC_ON_BOOT lib_deps = - https://github.com/pbolduc/AsyncTCP.git @ 1.2.0 + willmmiles/AsyncTCP @ 1.3.1 ${env.lib_deps} board_build.partitions = ${esp32.default_partitions} ;; default partioning for 4MB Flash - can be overridden in build envs @@ -322,7 +322,7 @@ build_flags = -g ;; please make sure that the following flags are properly set (to 0 or 1) by your board.json, or included in your custom platformio_override.ini entry: ;; ARDUINO_USB_CDC_ON_BOOT lib_deps = - https://github.com/pbolduc/AsyncTCP.git @ 1.2.0 + willmmiles/AsyncTCP @ 1.3.1 ${env.lib_deps} board_build.partitions = ${esp32.default_partitions} ;; default partioning for 4MB Flash - can be overridden in build envs board_build.flash_mode = qio @@ -342,7 +342,7 @@ build_flags = -g ;; please make sure that the following flags are properly set (to 0 or 1) by your board.json, or included in your custom platformio_override.ini entry: ;; ARDUINO_USB_MODE, ARDUINO_USB_CDC_ON_BOOT lib_deps = - https://github.com/pbolduc/AsyncTCP.git @ 1.2.0 + willmmiles/AsyncTCP @ 1.3.1 ${env.lib_deps} board_build.partitions = ${esp32.large_partitions} ;; default partioning for 8MB flash - can be overridden in build envs From 981750a48a2e6841b01db12cd8c7333a1b080b64 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Sun, 4 Aug 2024 14:02:05 -0400 Subject: [PATCH 280/287] Enable webserver queue and limits Enable the new concurrent request and queue size limit features of AsyncWebServer. This should improve the handling of burst traffic or many clients, and significantly reduce the likelihood of OOM crashes due to HTTP requests. --- wled00/const.h | 21 +++++++++++++++++++-- wled00/wled.h | 2 +- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/wled00/const.h b/wled00/const.h index 3f82219d3..1ebcb9397 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -560,8 +560,25 @@ #endif #endif -//#define MIN_HEAP_SIZE (8k for AsyncWebServer) -#define MIN_HEAP_SIZE 8192 +//#define MIN_HEAP_SIZE +#define MIN_HEAP_SIZE 2048 + +// Web server limits +#ifdef ESP8266 +// Minimum heap to consider handling a request +#define WLED_REQUEST_MIN_HEAP (8*1024) +// Estimated maximum heap required by any one request +#define WLED_REQUEST_HEAP_USAGE (6*1024) +#else +// ESP32 TCP stack needs much more RAM than ESP8266 +// Minimum heap remaining before queuing a request +#define WLED_REQUEST_MIN_HEAP (12*1024) +// Estimated maximum heap required by any one request +#define WLED_REQUEST_HEAP_USAGE (12*1024) +#endif +// Maximum number of requests in queue; absolute cap on web server resource usage. +// Websockets do not count against this limit. +#define WLED_REQUEST_MAX_QUEUE 6 // Maximum size of node map (list of other WLED instances) #ifdef ESP8266 diff --git a/wled00/wled.h b/wled00/wled.h index 371546613..a18199446 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -874,7 +874,7 @@ WLED_GLOBAL bool ledStatusState _INIT(false); // the current LED state #endif // server library objects -WLED_GLOBAL AsyncWebServer server _INIT_N(((80))); +WLED_GLOBAL AsyncWebServer server _INIT_N(((80, {0, WLED_REQUEST_MAX_QUEUE, WLED_REQUEST_MIN_HEAP, WLED_REQUEST_HEAP_USAGE}))); #ifdef WLED_ENABLE_WEBSOCKETS WLED_GLOBAL AsyncWebSocket ws _INIT_N((("/ws"))); #endif From dc317220b39ae35cba13a84179df96f63489b4cc Mon Sep 17 00:00:00 2001 From: Will Miles Date: Sun, 4 Aug 2024 14:02:05 -0400 Subject: [PATCH 281/287] Debug: Dump web server queue state This can be helpful for debugging web handler related issues. --- wled00/wled.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/wled00/wled.cpp b/wled00/wled.cpp index da1c33044..dd260b1c2 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -303,6 +303,7 @@ void WLED::loop() DEBUG_PRINTF_P(PSTR("Strip time[ms]:%u/%lu\n"), avgStripMillis/loops, maxStripMillis); } strip.printSize(); + server.printStatus(DEBUGOUT); loops = 0; maxLoopMillis = 0; maxUsermodMillis = 0; From bec7e54f7fc56abff2c90ccb66f6bab8f0a49ed5 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Sun, 4 Aug 2024 14:02:05 -0400 Subject: [PATCH 282/287] Defer web requests if JSON lock contended Use the web server's queuing mechanism to call us back later. --- wled00/json.cpp | 2 +- wled00/set.cpp | 5 ++++- wled00/wled_server.cpp | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/wled00/json.cpp b/wled00/json.cpp index 5fae9544e..d4f0d7771 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -1060,7 +1060,7 @@ void serveJson(AsyncWebServerRequest* request) } if (!requestJSONBufferLock(17)) { - serveJsonError(request, 503, ERR_NOBUF); + request->deferResponse(); return; } // releaseJSONBufferLock() will be called when "response" is destroyed (from AsyncWebServer) diff --git a/wled00/set.cpp b/wled00/set.cpp index 88249d3c4..c0977f262 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -628,7 +628,10 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) //USERMODS if (subPage == SUBPAGE_UM) { - if (!requestJSONBufferLock(5)) return; + if (!requestJSONBufferLock(5)) { + request->deferResponse(); + return; + } // global I2C & SPI pins int8_t hw_sda_pin = !request->arg(F("SDA")).length() ? -1 : (int)request->arg(F("SDA")).toInt(); diff --git a/wled00/wled_server.cpp b/wled00/wled_server.cpp index 8768b2b4e..da7fd2a3a 100644 --- a/wled00/wled_server.cpp +++ b/wled00/wled_server.cpp @@ -288,7 +288,7 @@ void initServer() bool isConfig = false; if (!requestJSONBufferLock(14)) { - serveJsonError(request, 503, ERR_NOBUF); + request->deferResponse(); return; } From 21816183572ad363015d973485fa61266c7d6b53 Mon Sep 17 00:00:00 2001 From: Will Miles Date: Wed, 21 Aug 2024 01:06:09 -0400 Subject: [PATCH 283/287] stress_test: Add replicated index as a target No locking contention, but a much larger target --- tools/stress_test.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/stress_test.sh b/tools/stress_test.sh index d7c344c58..d86c50864 100644 --- a/tools/stress_test.sh +++ b/tools/stress_test.sh @@ -27,6 +27,7 @@ read -a JSON_TINY_TARGETS <<< $(replicate "json/nodes") read -a JSON_SMALL_TARGETS <<< $(replicate "json/info") read -a JSON_LARGE_TARGETS <<< $(replicate "json/si") read -a JSON_LARGER_TARGETS <<< $(replicate "json/fxdata") +read -a INDEX_TARGETS <<< $(replicate "") # Expand target URLS to full arguments for curl TARGETS=(${TARGET_STR[@]}) From e7c0ce794b76ec1286a920534be2ff222f4d78c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Kristan?= Date: Fri, 24 Jan 2025 10:10:14 +0100 Subject: [PATCH 284/287] Merge conflict fix - updated blending style constants --- wled00/data/index.htm | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/wled00/data/index.htm b/wled00/data/index.htm index 6027eeb51..aa06b5122 100644 --- a/wled00/data/index.htm +++ b/wled00/data/index.htm @@ -128,7 +128,7 @@
- +

Color palette

@@ -273,20 +273,20 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + + +

From a778ff01f613a92974ba04447adaa0e2490d7e6b Mon Sep 17 00:00:00 2001 From: "Christian W. Zuckschwerdt" Date: Fri, 24 Jan 2025 23:17:01 +0100 Subject: [PATCH 285/287] Nightly release - Add bin.gz artifacts --- .github/workflows/nightly.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 138730058..a5c80f22d 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -37,4 +37,5 @@ jobs: prerelease: true body: ${{ steps.changelog.outputs.changelog }} files: | - ./*.bin \ No newline at end of file + *.bin + *.bin.gz From e27fa882fab85320feec437047a66918dd1bb488 Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Sun, 26 Jan 2025 16:12:08 +0000 Subject: [PATCH 286/287] Move CONFIG_ASYNC_TCP_USE_WDT=0 to new esp32_all_variants --- platformio.ini | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/platformio.ini b/platformio.ini index 56dd2537b..e775c61f5 100644 --- a/platformio.ini +++ b/platformio.ini @@ -244,6 +244,7 @@ lib_deps = bitbank2/AnimatedGIF@^1.4.7 https://github.com/Aircoookie/GifDecoder#bc3af18 build_flags = + -D CONFIG_ASYNC_TCP_USE_WDT=0 -D WLED_ENABLE_GIF [esp32] @@ -254,7 +255,6 @@ build_unflags = ${common.build_unflags} build_flags = -g -DARDUINO_ARCH_ESP32 #-DCONFIG_LITTLEFS_FOR_IDF_3_2 - -D CONFIG_ASYNC_TCP_USE_WDT=0 #use LITTLEFS library by lorol in ESP32 core 1.x.x instead of built-in in 2.x.x -D LOROL_LITTLEFS ; -DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3 @@ -289,7 +289,6 @@ build_unflags = ${common.build_unflags} build_flags = -g -Wshadow=compatible-local ;; emit warning in case a local variable "shadows" another local one -DARDUINO_ARCH_ESP32 -DESP32 - -D CONFIG_ASYNC_TCP_USE_WDT=0 -DARDUINO_USB_CDC_ON_BOOT=0 ;; this flag is mandatory for "classic ESP32" when building with arduino-esp32 >=2.0.3 ${esp32_all_variants.build_flags} -D WLED_ENABLE_DMX_INPUT @@ -307,7 +306,6 @@ build_flags = -g -DARDUINO_ARCH_ESP32 -DARDUINO_ARCH_ESP32S2 -DCONFIG_IDF_TARGET_ESP32S2=1 - -D CONFIG_ASYNC_TCP_USE_WDT=0 -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_USB_DFU_ON_BOOT=0 -DCO -DARDUINO_USB_MODE=0 ;; this flag is mandatory for ESP32-S2 ! @@ -327,7 +325,6 @@ build_flags = -g -DARDUINO_ARCH_ESP32 -DARDUINO_ARCH_ESP32C3 -DCONFIG_IDF_TARGET_ESP32C3=1 - -D CONFIG_ASYNC_TCP_USE_WDT=0 -DCO -DARDUINO_USB_MODE=1 ;; this flag is mandatory for ESP32-C3 ;; please make sure that the following flags are properly set (to 0 or 1) by your board.json, or included in your custom platformio_override.ini entry: @@ -348,7 +345,6 @@ build_flags = -g -DARDUINO_ARCH_ESP32 -DARDUINO_ARCH_ESP32S3 -DCONFIG_IDF_TARGET_ESP32S3=1 - -D CONFIG_ASYNC_TCP_USE_WDT=0 -DARDUINO_USB_MSC_ON_BOOT=0 -DARDUINO_DFU_ON_BOOT=0 -DCO ;; please make sure that the following flags are properly set (to 0 or 1) by your board.json, or included in your custom platformio_override.ini entry: @@ -646,7 +642,6 @@ build_flags = ${common.build_flags} ${esp32s2.build_flags} -D WLED_RELEASE_NAME= -DBOARD_HAS_PSRAM -DLOLIN_WIFI_FIX ; seems to work much better with this -D WLED_WATCHDOG_TIMEOUT=0 - -D CONFIG_ASYNC_TCP_USE_WDT=0 -D DATA_PINS=16 -D HW_PIN_SCL=35 -D HW_PIN_SDA=33 From b64cd36468384bf4f293a72602cbb2b860b25146 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Fri, 31 Jan 2025 14:10:03 +0100 Subject: [PATCH 287/287] fixes trail flickering randomly. thx @blazoncek for discovering --- wled00/FX.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 594822641..b9ead1412 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -2358,12 +2358,14 @@ uint16_t mode_meteor() { for (unsigned i = 0; i < SEGLEN; i++) { uint32_t col; if (hw_random8() <= 255 - SEGMENT.intensity) { - if(meteorSmooth) { - int change = trail[i] + 4 - hw_random8(24); //change each time between -20 and +4 - trail[i] = constrain(change, 0, max); - col = SEGMENT.check1 ? SEGMENT.color_from_palette(i, true, false, 0, trail[i]) : SEGMENT.color_from_palette(trail[i], false, true, 255); + if(meteorSmooth) { + if (trail[i] > 0) { + int change = trail[i] + 4 - hw_random8(24); //change each time between -20 and +4 + trail[i] = constrain(change, 0, max); } - else { + col = SEGMENT.check1 ? SEGMENT.color_from_palette(i, true, false, 0, trail[i]) : SEGMENT.color_from_palette(trail[i], false, true, 255); + } + else { trail[i] = scale8(trail[i], 128 + hw_random8(127)); int index = trail[i]; int idx = 255;