diff --git a/usermods/Analog_Clock/Analog_Clock.cpp b/usermods/Analog_Clock/Analog_Clock.cpp index 970ba72241..d3a2b73b8d 100644 --- a/usermods/Analog_Clock/Analog_Clock.cpp +++ b/usermods/Analog_Clock/Analog_Clock.cpp @@ -102,9 +102,9 @@ class AnalogClockUsermod : public Usermod { void secondsEffectSineFade(int16_t secondLed, Toki::Time const& time) { uint32_t ms = time.ms % 1000; uint8_t b0 = (cos8_t(ms * 64 / 1000) - 128) * 2; - setPixelColor(secondLed, gamma32(scale32(secondColor, b0))); + setPixelColor(secondLed, scale32(secondColor, b0)); uint8_t b1 = (sin8_t(ms * 64 / 1000) - 128) * 2; - setPixelColor(inc(secondLed, 1, secondsSegment), gamma32(scale32(secondColor, b1))); + setPixelColor(inc(secondLed, 1, secondsSegment), scale32(secondColor, b1)); } static inline uint32_t qadd32(uint32_t c1, uint32_t c2) { @@ -191,7 +191,7 @@ class AnalogClockUsermod : public Usermod { // for (uint16_t i = 1; i < secondsTrail + 1; ++i) { // uint16_t trailLed = dec(secondLed, i, secondsSegment); // uint8_t trailBright = 255 / (secondsTrail + 1) * (secondsTrail - i + 1); - // setPixelColor(trailLed, gamma32(scale32(secondColor, trailBright))); + // setPixelColor(trailLed, scale32(secondColor, trailBright)); // } } diff --git a/usermods/Cronixie/Cronixie.cpp b/usermods/Cronixie/Cronixie.cpp index 05557de0f0..e0a3bbee7a 100644 --- a/usermods/Cronixie/Cronixie.cpp +++ b/usermods/Cronixie/Cronixie.cpp @@ -247,7 +247,7 @@ class UsermodCronixie : public Usermod { if (backlight && _digitOut[i] <11) { - uint32_t col = gamma32(strip.getSegment(0).colors[1]); + uint32_t col = strip.getSegment(0).colors[1]; for (uint16_t j=o; j< o+10; j++) { if (j != excl) strip.setPixelColor(j, col); } diff --git a/usermods/audioreactive/audio_reactive.cpp b/usermods/audioreactive/audio_reactive.cpp index 4b3520562d..2587b8be1e 100644 --- a/usermods/audioreactive/audio_reactive.cpp +++ b/usermods/audioreactive/audio_reactive.cpp @@ -1736,7 +1736,7 @@ class AudioReactive : public Usermod { } void onStateChange(uint8_t callMode) override { - if (initDone && enabled && addPalettes && palettes==0 && strip.customPalettes.size()<10) { + if (initDone && enabled && addPalettes && palettes==0 && customPalettes.size()<10) { // if palettes were removed during JSON call re-add them createAudioPalettes(); } @@ -1966,20 +1966,20 @@ class AudioReactive : public Usermod { void AudioReactive::removeAudioPalettes(void) { DEBUG_PRINTLN(F("Removing audio palettes.")); while (palettes>0) { - strip.customPalettes.pop_back(); + customPalettes.pop_back(); DEBUG_PRINTLN(palettes); palettes--; } - DEBUG_PRINT(F("Total # of palettes: ")); DEBUG_PRINTLN(strip.customPalettes.size()); + DEBUG_PRINT(F("Total # of palettes: ")); DEBUG_PRINTLN(customPalettes.size()); } void AudioReactive::createAudioPalettes(void) { - DEBUG_PRINT(F("Total # of palettes: ")); DEBUG_PRINTLN(strip.customPalettes.size()); + DEBUG_PRINT(F("Total # of palettes: ")); DEBUG_PRINTLN(customPalettes.size()); if (palettes) return; DEBUG_PRINTLN(F("Adding audio palettes.")); for (int i=0; i= palettes) lastCustPalette -= palettes; for (int pal=0; palupdateRedrawTime(); #endif - effectPaletteIndex = max(min((unsigned)(increase ? effectPaletteIndex+1 : effectPaletteIndex-1), strip.getPaletteCount()+strip.customPalettes.size()-1), 0U); + effectPaletteIndex = max(min((unsigned)(increase ? effectPaletteIndex+1 : effectPaletteIndex-1), getPaletteCount()+customPalettes.size()-1), 0U); effectPalette = palettes_alpha_indexes[effectPaletteIndex]; stateChanged = true; if (applyToAll) { diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 3daf2c0fdf..4a8e8f71af 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -5053,7 +5053,11 @@ uint16_t mode_2Dfirenoise(void) { // firenoise2d. By Andrew Tuline unsigned yscale = SEGMENT.speed*8; unsigned indexx = 0; - CRGBPalette16 pal = SEGMENT.check1 ? SEGPALETTE : SEGMENT.loadPalette(pal, 35); + //CRGBPalette16 pal = SEGMENT.check1 ? SEGPALETTE : SEGMENT.loadPalette(pal, 35); + CRGBPalette16 pal = SEGMENT.check1 ? SEGPALETTE : CRGBPalette16(CRGB::Black, CRGB::Black, CRGB::Black, CRGB::Black, + CRGB::Red, CRGB::Red, CRGB::Red, CRGB::DarkOrange, + CRGB::DarkOrange,CRGB::DarkOrange, CRGB::Orange, CRGB::Orange, + CRGB::Yellow, CRGB::Orange, CRGB::Yellow, CRGB::Yellow); for (int j=0; j < cols; j++) { for (int i=0; i < rows; i++) { indexx = perlin8(j*yscale*rows/255, i*xscale+strip.now/4); // We're moving along our Perlin map. @@ -6088,7 +6092,8 @@ uint16_t mode_2Dscrollingtext(void) { case 5: letterWidth = 5; letterHeight = 12; break; } // letters are rotated - if (((SEGMENT.custom3+1)>>3) % 2) { + const int8_t rotate = map(SEGMENT.custom3, 0, 31, -2, 2); + if (rotate == 1 || rotate == -1) { rotLH = letterWidth; rotLW = letterHeight; } else { @@ -6126,6 +6131,7 @@ uint16_t mode_2Dscrollingtext(void) { else if (!strncmp_P(text,PSTR("#DD"),3)) sprintf (text, zero? ("%02d") : ("%d"), day(localTime)); else if (!strncmp_P(text,PSTR("#DAY"),4)) sprintf (text, ("%s") , dayShortStr(day(localTime))); else if (!strncmp_P(text,PSTR("#DDDD"),5)) sprintf (text, ("%s") , dayStr(day(localTime))); + else if (!strncmp_P(text,PSTR("#DAYL"),5)) sprintf (text, ("%s") , dayStr(day(localTime))); else if (!strncmp_P(text,PSTR("#MO"),3)) sprintf (text, zero? ("%02d") : ("%d"), month(localTime)); else if (!strncmp_P(text,PSTR("#MON"),4)) sprintf (text, ("%s") , monthShortStr(month(localTime))); else if (!strncmp_P(text,PSTR("#MMMM"),5)) sprintf (text, ("%s") , monthStr(month(localTime))); @@ -6159,27 +6165,28 @@ uint16_t mode_2Dscrollingtext(void) { SEGENV.step = strip.now + map(SEGMENT.speed, 0, 255, 250, 50); // shift letters every ~250ms to ~50ms } - if (!SEGMENT.check2) SEGMENT.fade_out(255 - (SEGMENT.custom1>>4)); // trail - bool usePaletteGradient = false; + SEGMENT.fade_out(255 - (SEGMENT.custom1>>4)); // trail uint32_t col1 = SEGMENT.color_from_palette(SEGENV.aux1, false, PALETTE_SOLID_WRAP, 0); uint32_t col2 = BLACK; + // if gradient is selected and palette is default (0) drawCharacter() uses gradient from SEGCOLOR(0) to SEGCOLOR(2) + // otherwise col2 == BLACK means use currently selected palette for gradient + // if gradient is not selected set both colors the same if (SEGMENT.check1) { // use gradient - if(SEGMENT.palette == 0) { // use colors for gradient - col1 = SEGCOLOR(0); - col2 = SEGCOLOR(2); + if (SEGMENT.palette == 0) { // use colors for gradient + col1 = SEGCOLOR(0); + col2 = SEGCOLOR(2); } - else usePaletteGradient = true; - } + } else col2 = col1; // force characters to use single color (from palette) for (int i = 0; i < numberOfLetters; i++) { int xoffset = int(cols) - int(SEGENV.aux0) + rotLW*i; if (xoffset + rotLW < 0) continue; // don't draw characters off-screen - SEGMENT.drawCharacter(text[i], xoffset, yoffset, letterWidth, letterHeight, col1, col2, map(SEGMENT.custom3, 0, 31, -2, 2), usePaletteGradient); + SEGMENT.drawCharacter(text[i], xoffset, yoffset, letterWidth, letterHeight, col1, col2, rotate); } return FRAMETIME; } -static const char _data_FX_MODE_2DSCROLLTEXT[] PROGMEM = "Scrolling Text@!,Y Offset,Trail,Font size,Rotate,Gradient,Overlay,Reverse;!,!,Gradient;!;2;ix=128,c1=0,rev=0,mi=0,rY=0,mY=0"; +static const char _data_FX_MODE_2DSCROLLTEXT[] PROGMEM = "Scrolling Text@!,Y Offset,Trail,Font size,Rotate,Gradient,,Reverse;!,!,Gradient;!;2;ix=128,c1=0,rev=0,mi=0,rY=0,mY=0"; //////////////////////////// diff --git a/wled00/FX.h b/wled00/FX.h old mode 100644 new mode 100755 index 6481ff7572..9c895c0c05 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -19,8 +19,24 @@ #include #include "wled.h" -#include "const.h" -#include "bus_manager.h" +#ifdef WLED_DEBUG + // enable additional debug output + #if defined(WLED_DEBUG_HOST) + #include "net_debug.h" + #define DEBUGOUT NetDebug + #else + #define DEBUGOUT Serial + #endif + #define DEBUGFX_PRINT(x) DEBUGOUT.print(x) + #define DEBUGFX_PRINTLN(x) DEBUGOUT.println(x) + #define DEBUGFX_PRINTF(x...) DEBUGOUT.printf(x) + #define DEBUGFX_PRINTF_P(x...) DEBUGOUT.printf_P(x) +#else + #define DEBUGFX_PRINT(x) + #define DEBUGFX_PRINTLN(x) + #define DEBUGFX_PRINTF(x...) + #define DEBUGFX_PRINTF_P(x...) +#endif #define FASTLED_INTERNAL //remove annoying pragma messages #define USE_GET_MILLISECOND_TIMER @@ -88,11 +104,13 @@ extern byte realtimeMode; // used in getMappedPixelIndex() /* How much data bytes each segment should max allocate to leave enough space for other segments, 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 FAIR_DATA_PER_SEG (MAX_SEGMENT_DATA / WS2812FX::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()] +#define SEGMENT (*strip._currentSegment) +#define SEGENV (*strip._currentSegment) #define SEGCOLOR(x) Segment::getCurrentColor(x) #define SEGPALETTE Segment::getCurrentPalette() #define SEGLEN Segment::vLength() @@ -143,10 +161,10 @@ extern byte realtimeMode; // used in getMappedPixelIndex() #define FX_MODE_RAINBOW 8 #define FX_MODE_RAINBOW_CYCLE 9 #define FX_MODE_SCAN 10 -#define FX_MODE_DUAL_SCAN 11 +#define FX_MODE_DUAL_SCAN 11 // candidate for removal (use Scan) #define FX_MODE_FADE 12 #define FX_MODE_THEATER_CHASE 13 -#define FX_MODE_THEATER_CHASE_RAINBOW 14 +#define FX_MODE_THEATER_CHASE_RAINBOW 14 // candidate for removal (use Theater) #define FX_MODE_RUNNING_LIGHTS 15 #define FX_MODE_SAW 16 #define FX_MODE_TWINKLE 17 @@ -169,7 +187,7 @@ extern byte realtimeMode; // used in getMappedPixelIndex() #define FX_MODE_COLORFUL 34 #define FX_MODE_TRAFFIC_LIGHT 35 #define FX_MODE_COLOR_SWEEP_RANDOM 36 -#define FX_MODE_RUNNING_COLOR 37 +#define FX_MODE_RUNNING_COLOR 37 // candidate for removal (use Theater) #define FX_MODE_AURORA 38 #define FX_MODE_RUNNING_RANDOM 39 #define FX_MODE_LARSON_SCANNER 40 @@ -184,7 +202,7 @@ extern byte realtimeMode; // used in getMappedPixelIndex() #define FX_MODE_FAIRY 49 //was Police All prior to 0.13.0-b6 (use "Two Dots" with Red/Blue and full intensity) #define FX_MODE_TWO_DOTS 50 #define FX_MODE_FAIRYTWINKLE 51 //was Two Areas prior to 0.13.0-b6 (use "Two Dots" with full intensity) -#define FX_MODE_RUNNING_DUAL 52 +#define FX_MODE_RUNNING_DUAL 52 // candidate for removal (use Running) #define FX_MODE_IMAGE 53 #define FX_MODE_TRICOLOR_CHASE 54 #define FX_MODE_TRICOLOR_WIPE 55 @@ -209,7 +227,7 @@ extern byte realtimeMode; // used in getMappedPixelIndex() #define FX_MODE_COLORTWINKLE 74 #define FX_MODE_LAKE 75 #define FX_MODE_METEOR 76 -//#define FX_MODE_METEOR_SMOOTH 77 // merged with meteor +//#define FX_MODE_METEOR_SMOOTH 77 // replaced by Meteor #define FX_MODE_RAILWAY 78 #define FX_MODE_RIPPLE 79 #define FX_MODE_TWINKLEFOX 80 @@ -225,16 +243,16 @@ extern byte realtimeMode; // used in getMappedPixelIndex() #define FX_MODE_EXPLODING_FIREWORKS 90 #define FX_MODE_BOUNCINGBALLS 91 #define FX_MODE_SINELON 92 -#define FX_MODE_SINELON_DUAL 93 -#define FX_MODE_SINELON_RAINBOW 94 +#define FX_MODE_SINELON_DUAL 93 // candidate for removal (use sinelon) +#define FX_MODE_SINELON_RAINBOW 94 // candidate for removal (use sinelon) #define FX_MODE_POPCORN 95 #define FX_MODE_DRIP 96 #define FX_MODE_PLASMA 97 #define FX_MODE_PERCENT 98 -#define FX_MODE_RIPPLE_RAINBOW 99 +#define FX_MODE_RIPPLE_RAINBOW 99 // candidate for removal (use ripple) #define FX_MODE_HEARTBEAT 100 #define FX_MODE_PACIFICA 101 -#define FX_MODE_CANDLE_MULTI 102 +#define FX_MODE_CANDLE_MULTI 102 // candidate for removal (use candle with multi select) #define FX_MODE_SOLID_GLITTER 103 // candidate for removal (use glitter) #define FX_MODE_SUNRISE 104 #define FX_MODE_PHASED 105 @@ -323,6 +341,7 @@ extern byte realtimeMode; // used in getMappedPixelIndex() #define FX_MODE_WAVESINS 184 #define FX_MODE_ROCKTAVES 185 #define FX_MODE_2DAKEMI 186 + #define FX_MODE_PARTICLEVOLCANO 187 #define FX_MODE_PARTICLEFIRE 188 #define FX_MODE_PARTICLEFIREWORKS 189 @@ -360,12 +379,18 @@ extern byte realtimeMode; // used in getMappedPixelIndex() #define BLEND_STYLE_FAIRY_DUST 0x01 // universal #define BLEND_STYLE_SWIPE_RIGHT 0x02 // 1D or 2D #define BLEND_STYLE_SWIPE_LEFT 0x03 // 1D or 2D -#define BLEND_STYLE_PINCH_OUT 0x04 // 1D or 2D +#define BLEND_STYLE_OUTSIDE_IN 0x04 // 1D or 2D #define BLEND_STYLE_INSIDE_OUT 0x05 // 1D or 2D #define BLEND_STYLE_SWIPE_UP 0x06 // 2D #define BLEND_STYLE_SWIPE_DOWN 0x07 // 2D #define BLEND_STYLE_OPEN_H 0x08 // 2D #define BLEND_STYLE_OPEN_V 0x09 // 2D +#define BLEND_STYLE_SWIPE_TL 0x0A // 2D +#define BLEND_STYLE_SWIPE_TR 0x0B // 2D +#define BLEND_STYLE_SWIPE_BR 0x0C // 2D +#define BLEND_STYLE_SWIPE_BL 0x0D // 2D +#define BLEND_STYLE_CIRCULAR_OUT 0x0E // 2D +#define BLEND_STYLE_CIRCULAR_IN 0x0F // 2D // as there are many push variants to optimise if statements they are groupped together #define BLEND_STYLE_PUSH_RIGHT 0x10 // 1D or 2D (& 0b00010000) #define BLEND_STYLE_PUSH_LEFT 0x11 // 1D or 2D (& 0b00010000) @@ -387,184 +412,200 @@ typedef enum mapping1D2D { M12_sPinwheel = 4 } mapping1D2D_t; -// segment, 68 bytes -typedef struct Segment { +class WS2812FX; + +// segment, 76 bytes +class Segment { public: - uint16_t start; // start index / start X coordinate 2D (left) - uint16_t stop; // stop index / stop X coordinate 2D (right); segment is invalid if stop == 0 - uint16_t offset; - uint8_t speed; - uint8_t intensity; - uint8_t palette; - uint8_t mode; + uint32_t colors[NUM_COLORS]; + uint16_t start; // start index / start X coordinate 2D (left) + uint16_t stop; // stop index / stop X coordinate 2D (right); segment is invalid if stop == 0 + uint16_t startY; // start Y coodrinate 2D (top); there should be no more than 255 rows + uint16_t stopY; // stop Y coordinate 2D (bottom); there should be no more than 255 rows + uint16_t offset; // offset for 1D effects (effect will wrap around) union { - uint16_t options; //bit pattern: msb first: [transposed mirrorY reverseY] transitional (tbd) paused needspixelstate mirrored on reverse selected + mutable uint16_t options; //bit pattern: msb first: [transposed mirrorY reverseY] transitional (tbd) paused needspixelstate mirrored on reverse selected struct { - bool selected : 1; // 0 : selected - bool reverse : 1; // 1 : reversed - bool on : 1; // 2 : is On - bool mirror : 1; // 3 : mirrored - bool freeze : 1; // 4 : paused/frozen - bool reset : 1; // 5 : indicates that Segment runtime requires reset - bool reverse_y : 1; // 6 : reversed Y (2D) - bool mirror_y : 1; // 7 : mirrored Y (2D) - bool transpose : 1; // 8 : transposed (2D, swapped X & Y) - uint8_t map1D2D : 3; // 9-11 : mapping for 1D effect on 2D (0-use as strip, 1-expand vertically, 2-circular/arc, 3-rectangular/corner, ...) - uint8_t soundSim : 2; // 12-13 : 0-3 sound simulation types ("soft" & "hard" or "on"/"off") - uint8_t set : 2; // 14-15 : 0-3 UI segment sets/groups + mutable bool selected : 1; // 0 : selected + bool reverse : 1; // 1 : reversed + mutable bool on : 1; // 2 : is On + bool mirror : 1; // 3 : mirrored + mutable bool freeze : 1; // 4 : paused/frozen + mutable bool reset : 1; // 5 : indicates that Segment runtime requires reset + bool reverse_y : 1; // 6 : reversed Y (2D) + bool mirror_y : 1; // 7 : mirrored Y (2D) + bool transpose : 1; // 8 : transposed (2D, swapped X & Y) + uint8_t map1D2D : 3; // 9-11 : mapping for 1D effect on 2D (0-use as strip, 1-expand vertically, 2-circular/arc, 3-rectangular/corner, ...) + uint8_t soundSim : 2; // 12-13 : 0-3 sound simulation types ("soft" & "hard" or "on"/"off") + mutable uint8_t set : 2; // 14-15 : 0-3 UI segment sets/groups }; }; uint8_t grouping, spacing; - uint8_t opacity; - uint32_t colors[NUM_COLORS]; - uint8_t cct; //0==1900K, 255==10091K - uint8_t custom1, custom2; // custom FX parameters/sliders + uint8_t opacity, cct; // 0==1900K, 255==10091K + // effect data + uint8_t mode; + uint8_t palette; + uint8_t speed; + uint8_t intensity; + uint8_t custom1, custom2; // custom FX parameters/sliders struct { uint8_t custom3 : 5; // reduced range slider (0-31) bool check1 : 1; // checkmark 1 bool check2 : 1; // checkmark 2 bool check3 : 1; // checkmark 3 + //uint8_t blendMode : 4; // segment blending modes: top, bottom, add, subtract, difference, multiply, divide, lighten, darken, screen, overlay, hardlight, softlight, dodge, burn }; - 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: two bytes of padding are added here - char *name; + uint8_t blendMode; // segment blending modes: top, bottom, add, subtract, difference, multiply, divide, lighten, darken, screen, overlay, hardlight, softlight, dodge, burn + char *name; // segment name // runtime data - unsigned long next_time; // millis() of next update - uint32_t step; // custom "step" var - uint32_t call; // call counter - uint16_t aux0; // custom var - uint16_t aux1; // custom var + mutable unsigned long next_time; // millis() of next update + mutable uint32_t step; // custom "step" var + mutable uint32_t call; // call counter + mutable uint16_t aux0; // custom var + mutable uint16_t aux1; // custom var byte *data; // effect data pointer - static uint16_t maxWidth, maxHeight; // these define matrix width & height (max. segment dimensions) - typedef struct TemporarySegmentData { - uint16_t _optionsT; - uint32_t _colorT[NUM_COLORS]; - uint8_t _speedT; - uint8_t _intensityT; - uint8_t _custom1T, _custom2T; // custom FX parameters/sliders - struct { - uint8_t _custom3T : 5; // reduced range slider (0-31) - bool _check1T : 1; // checkmark 1 - bool _check2T : 1; // checkmark 2 - bool _check3T : 1; // checkmark 3 - }; - uint16_t _aux0T; - uint16_t _aux1T; - uint32_t _stepT; - uint32_t _callT; - uint8_t *_dataT; - unsigned _dataLenT; - TemporarySegmentData() - : _dataT(nullptr) // just in case... - , _dataLenT(0) - {} - } tmpsegd_t; + static uint16_t maxWidth, maxHeight; // these define matrix width & height (max. segment dimensions) private: + uint32_t *pixels; // pixel data + unsigned _dataLen; + uint8_t _default_palette; // palette number that gets assigned to pal0 union { - uint8_t _capabilities; + mutable uint8_t _capabilities; // determines segment capabilities in terms of what is available: RGB, W, CCT, manual W, etc. struct { bool _isRGB : 1; bool _hasW : 1; bool _isCCT : 1; bool _manualW : 1; - uint8_t _reserved : 4; }; }; - uint8_t _default_palette; // palette number that gets assigned to pal0 - 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 - static uint32_t _currentColors[NUM_COLORS]; // colors used for current effect - static bool _colorScaled; // color has been scaled prior to setPixelColor() call + + // static variables are use to speed up effect calculations by stashing common pre-calculated values + static unsigned _usedSegmentData; // amount of data used by all segments + 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 (faster access from effect functions) 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 - static uint16_t _lastPaletteChange; // last random palette change time in millis()/1000 - static uint16_t _lastPaletteBlend; // blend palette according to set Transition Delay in millis()%0xFFFF - static uint16_t _transitionprogress; // current transition progress 0 - 0xFFFF - #ifndef WLED_DISABLE_MODE_BLEND + static uint16_t _lastPaletteChange; // last random palette change time (in seconds) + static uint16_t _nextPaletteBlend; // next due time for random palette morph (in millis()) static bool _modeBlend; // mode/effect blending semaphore - // clipping - static uint16_t _clipStart, _clipStop; - static uint8_t _clipStartY, _clipStopY; - #endif + // clipping rectangle used for blending + static uint16_t _clipStart, _clipStop; + static uint8_t _clipStartY, _clipStopY; - // transition data, valid only if transitional==true, holds values during transition (72 bytes) + // transition data, holds values during transition (76 bytes/28 bytes) struct Transition { - #ifndef WLED_DISABLE_MODE_BLEND - tmpsegd_t _segT; // previous segment environment - uint8_t _modeT; // previous mode/effect - #else - uint32_t _colorT[NUM_COLORS]; + Segment *_oldSegment; // previous segment environment (may be nullptr if effect did not change) + unsigned long _start; // must accommodate millis() + uint32_t _colors[NUM_COLORS]; // current colors + #ifndef WLED_SAVE_RAM + CRGBPalette16 _palT; // temporary palette (slowly being morphed from old to new) #endif - uint8_t _palTid; // previous palette - uint8_t _briT; // temporary brightness - uint8_t _cctT; // temporary CCT - CRGBPalette16 _palT; // temporary palette - uint8_t _prevPaletteBlends; // number of previous palette blends (there are max 255 blends possible) - unsigned long _start; // must accommodate millis() - uint16_t _dur; - // -> here is one byte of padding + uint16_t _dur; // duration of transition in ms + uint16_t _progress; // transition progress (0-65535); pre-calculated from _start & _dur in updateTransitionProgress() + uint8_t _prevPaletteBlends; // number of previous palette blends (there are max 255 blends possible) + uint8_t _palette, _bri, _cct; // palette ID, brightness and CCT at the start of transition (brightness will be 0 if segment was off) Transition(uint16_t dur=750) - : _palT(CRGBPalette16(CRGB::Black)) - , _prevPaletteBlends(0) - , _start(millis()) - , _dur(dur) + : _oldSegment(nullptr) + , _start(millis()) + , _colors{0,0,0} + #ifndef WLED_SAVE_RAM + , _palT(CRGBPalette16(CRGB::Black)) + #endif + , _dur(dur) + , _progress(0) + , _prevPaletteBlends(0) + , _palette(0) + , _bri(0) + , _cct(0) {} + ~Transition() { + //DEBUGFX_PRINTF_P(PSTR("-- Destroying transition: %p\n"), this); + if (_oldSegment) delete _oldSegment; + } } *_t; - [[gnu::hot]] void _setPixelColorXY_raw(const int& x, const int& y, uint32_t& col) const; // set pixel without mapping (internal use only) + protected: - public: + inline static unsigned getUsedSegmentData() { return Segment::_usedSegmentData; } + inline static void addUsedSegmentData(int len) { Segment::_usedSegmentData += len; } - Segment(uint16_t sStart=0, uint16_t sStop=30) : - start(sStart), - stop(sStop), - offset(0), - speed(DEFAULT_SPEED), - intensity(DEFAULT_INTENSITY), - palette(0), - mode(DEFAULT_MODE), - options(SELECTED | SEGMENT_ON), - grouping(1), - spacing(0), - opacity(255), - colors{DEFAULT_COLOR,BLACK,BLACK}, - cct(127), - custom1(DEFAULT_C1), - custom2(DEFAULT_C2), - custom3(DEFAULT_C3), - check1(false), - check2(false), - check3(false), - startY(0), - stopY(1), - name(nullptr), - next_time(0), - step(0), - call(0), - aux0(0), - aux1(0), - data(nullptr), - _capabilities(0), - _default_palette(0), - _dataLen(0), - _t(nullptr) - { - #ifdef WLED_DEBUG - //Serial.printf("-- Creating segment: %p\n", this); - #endif + inline uint32_t *getPixels() const { return pixels; } + inline void setPixelColorRaw(unsigned i, uint32_t c) const { pixels[i] = c; } + inline uint32_t getPixelColorRaw(unsigned i) const { return pixels[i]; }; + #ifndef WLED_DISABLE_2D + inline void setPixelColorXYRaw(unsigned x, unsigned y, uint32_t c) const { auto XY = [](unsigned X, unsigned Y){ return X + Y*Segment::vWidth(); }; pixels[XY(x,y)] = c; } + inline uint32_t getPixelColorXYRaw(unsigned x, unsigned y) const { auto XY = [](unsigned X, unsigned Y){ return X + Y*Segment::vWidth(); }; return pixels[XY(x,y)]; }; + #endif + void resetIfRequired(); // sets all SEGENV variables to 0 and clears data buffer + CRGBPalette16 &loadPalette(CRGBPalette16 &tgt, uint8_t pal); + + // transition functions + void stopTransition(); // ends transition mode by destroying transition structure (does nothing if not in transition) + void updateTransitionProgress() const; // sets transition progress (0-65535) based on time passed since transition start + inline void handleTransition() { + updateTransitionProgress(); + if (isInTransition() && progress() == 0xFFFFU) stopTransition(); } + inline uint16_t progress() const { return isInTransition() ? _t->_progress : 0xFFFFU; } // relies on handleTransition()/updateTransitionProgress() to update progression variable + inline Segment *getOldSegment() const { return isInTransition() ? _t->_oldSegment : nullptr; } + + inline static void modeBlend(bool blend) { Segment::_modeBlend = blend; } + inline static void setClippingRect(int startX, int stopX, int startY = 0, int stopY = 1) { _clipStart = startX; _clipStop = stopX; _clipStartY = startY; _clipStopY = stopY; }; + inline static bool isPreviousMode() { return Segment::_modeBlend; } // needed for determining CCT/opacity during non-BLEND_STYLE_FADE transition - Segment(uint16_t sStartX, uint16_t sStopX, uint16_t sStartY, uint16_t sStopY) : Segment(sStartX, sStopX) { - startY = sStartY; - stopY = sStopY; + static void handleRandomPalette(); + + public: + + Segment(uint16_t sStart=0, uint16_t sStop=30, uint16_t sStartY = 0, uint16_t sStopY = 1) + : colors{DEFAULT_COLOR,BLACK,BLACK} + , start(sStart) + , stop(sStop > sStart ? sStop : sStart+1) // minimum length is 1 + , startY(sStartY) + , stopY(sStopY > sStartY ? sStopY : sStartY+1) // minimum height is 1 + , offset(0) + , options(SELECTED | SEGMENT_ON) + , grouping(1) + , spacing(0) + , opacity(255) + , cct(127) + , mode(DEFAULT_MODE) + , palette(0) + , speed(DEFAULT_SPEED) + , intensity(DEFAULT_INTENSITY) + , custom1(DEFAULT_C1) + , custom2(DEFAULT_C2) + , custom3(DEFAULT_C3) + , check1(false) + , check2(false) + , check3(false) + , blendMode(0) + , name(nullptr) + , next_time(0) + , step(0) + , call(0) + , aux0(0) + , aux1(0) + , data(nullptr) + , _dataLen(0) + , _default_palette(6) + , _capabilities(0) + , _t(nullptr) + { + DEBUGFX_PRINTF_P(PSTR("-- Creating segment: %p [%d,%d:%d,%d]\n"), this, (int)start, (int)stop, (int)startY, (int)stopY); + // allocate render buffer (always entire segment) + pixels = static_cast(d_calloc(sizeof(uint32_t), length())); // error handling is also done in isActive() + if (!pixels) { + DEBUGFX_PRINTLN(F("!!! Not enough RAM for pixel buffer !!!")); + extern byte errorFlag; + errorFlag = ERR_NORAM_PX; + stop = 0; // mark segment as inactive/invalid + } } Segment(const Segment &orig); // copy constructor @@ -572,54 +613,49 @@ typedef struct Segment { ~Segment() { #ifdef WLED_DEBUG - //Serial.printf("-- Destroying segment: %p", this); - //if (name) Serial.printf(" %s (%p)", name, name); - //if (data) Serial.printf(" %d->(%p)", (int)_dataLen, data); - //Serial.println(); + DEBUGFX_PRINTF_P(PSTR("-- Destroying segment: %p [%d,%d:%d,%d]"), this, (int)start, (int)stop, (int)startY, (int)stopY); + if (name) DEBUGFX_PRINTF_P(PSTR(" %s (%p)"), name, name); + if (data) DEBUGFX_PRINTF_P(PSTR(" %u->(%p)"), _dataLen, data); + DEBUGFX_PRINTF_P(PSTR(" T[%p]"), _t); + DEBUGFX_PRINTLN(); #endif - if (name) { free(name); name = nullptr; } - stopTransition(); + clearName(); deallocateData(); + d_free(pixels); } Segment& operator= (const Segment &orig); // copy assignment Segment& operator= (Segment &&orig) noexcept; // move assignment #ifdef WLED_DEBUG - size_t getSize() const { return sizeof(Segment) + (data?_dataLen:0) + (name?strlen(name):0) + (_t?sizeof(Transition):0); } + size_t getSize() const { return sizeof(Segment) + (data?_dataLen:0) + (name?strlen(name):0) + (_t?sizeof(Transition):0) + (pixels?length()*sizeof(uint32_t):0); } #endif - inline bool getOption(uint8_t n) const { return ((options >> n) & 0x01); } - inline bool isSelected() const { return selected; } - inline bool isInTransition() const { return _t != nullptr; } - inline bool isActive() const { return stop > start; } - inline bool hasRGB() const { return _isRGB; } - inline bool hasWhite() const { return _hasW; } - inline bool isCCT() const { return _isCCT; } - inline uint16_t width() const { return isActive() ? (stop - start) : 0; } // segment width in physical pixels (length if 1D) - inline uint16_t height() const { return stopY - startY; } // segment height (if 2D) in physical pixels (it *is* always >=1) - inline uint16_t length() const { return width() * height(); } // segment length (count) in physical pixels - inline uint16_t groupLength() const { return grouping + spacing; } + inline bool getOption(uint8_t n) const { return ((options >> n) & 0x01); } + inline bool isSelected() const { return selected; } + inline bool isInTransition() const { return _t != nullptr; } + inline bool isActive() const { return stop > start && pixels; } + inline bool hasRGB() const { return _isRGB; } + inline bool hasWhite() const { return _hasW; } + inline bool isCCT() const { return _isCCT; } + inline uint16_t width() const { return stop > start ? (stop - start) : 0; }// segment width in physical pixels (length if 1D) + inline uint16_t height() const { return stopY - startY; } // segment height (if 2D) in physical pixels (it *is* always >=1) + 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 Segment &clearName() { if (name) free(name); name = nullptr; return *this; } - inline Segment &setName(const String &name) { return setName(name.c_str()); } + inline void deactivate() { setGeometry(0,0); } + inline Segment &clearName() { d_free(name); name = nullptr; return *this; } + inline Segment &setName(const String &name) { return setName(name.c_str()); } - 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; } - inline static bool getmodeBlend(void) { return _modeBlend; } - #endif 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 uint32_t getCurrentColor(unsigned i) { return Segment::_currentColors[i= 0 && i < length()) setPixelColorRaw(i,col); } #ifdef WLED_USE_AA_PIXELS void setPixelColor(float i, uint32_t c, bool aa = true) const; inline void setPixelColor(float i, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0, bool aa = true) const { setPixelColor(i, RGBW32(r,g,b,w), aa); } inline void setPixelColor(float i, CRGB c, bool aa = true) const { setPixelColor(i, RGBW32(c.r,c.g,c.b,0), aa); } #endif - #ifndef WLED_DISABLE_MODE_BLEND - 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) const; + [[gnu::hot]] bool isPixelClipped(int i) const; [[gnu::hot]] uint32_t getPixelColor(int i) const; // 1D support functions (some implement 2D as well) - void blur(uint8_t, bool smear = false); - void clear(); - void fill(uint32_t c); - void fade_out(uint8_t r); - void fadeToSecondaryBy(uint8_t fadeBy); - 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 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; + void blur(uint8_t, bool smear = false) const; + void clear() const { fill(BLACK); } // clear segment + void fill(uint32_t c) const; + void fade_out(uint8_t r) const; + void fadeToSecondaryBy(uint8_t fadeBy) const; + void fadeToBlackBy(uint8_t fadeBy) const; + inline void blendPixelColor(int n, uint32_t color, uint8_t blend) const { setPixelColor(n, color_blend(getPixelColor(n), color, blend)); } + inline void blendPixelColor(int n, CRGB c, uint8_t blend) const { blendPixelColor(n, RGBW32(c.r,c.g,c.b,0), blend); } + inline void addPixelColor(int n, uint32_t color, bool preserveCR = true) const { 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) const + { addPixelColor(n, RGBW32(r,g,b,w), preserveCR); } + inline void addPixelColor(int n, CRGB c, bool preserveCR = true) const { addPixelColor(n, RGBW32(c.r,c.g,c.b,0), preserveCR); } + inline void fadePixelColor(uint16_t n, uint8_t fade) const { setPixelColor(n, color_fade(getPixelColor(n), fade, true)); } + [[gnu::hot]] uint32_t color_from_palette(uint16_t, bool mapping, bool moving, uint8_t mcol, uint8_t pbri = 255) const; [[gnu::hot]] uint32_t color_wheel(uint8_t pos) const; - - // 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 - blur2D(0, blur_amount, smear); - } - inline void blurRows(fract8 blur_amount, bool smear = false) { // blur all rows - blur2D(blur_amount, 0, smear); - } - // 2D matrix - [[gnu::hot]] unsigned virtualWidth() const; // segment width in virtual pixels (accounts for groupping and spacing) - [[gnu::hot]] unsigned virtualHeight() const; // segment height in virtual pixels (accounts for groupping and spacing) - inline unsigned nrOfVStrips() const { // returns number of virtual vertical strips in 2D matrix (used to expand 1D effects into 2D) + unsigned virtualWidth() const; // segment width in virtual pixels (accounts for groupping and spacing) + unsigned virtualHeight() const; // segment height in virtual pixels (accounts for groupping and spacing) + inline unsigned nrOfVStrips() const { // returns number of virtual vertical strips in 2D matrix (used to expand 1D effects into 2D) #ifndef WLED_DISABLE_2D return (is2D() && map1D2D == M12_pBar) ? virtualWidth() : 1; #else @@ -725,52 +737,54 @@ typedef struct Segment { [[gnu::hot]] bool isPixelXYClipped(int x, int y) const; [[gnu::hot]] uint32_t getPixelColorXY(int x, int y) const; // 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 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)); } + inline void blendPixelColorXY(uint16_t x, uint16_t y, uint32_t color, uint8_t blend) const { setPixelColorXY(x, y, color_blend(getPixelColorXY(x,y), color, blend)); } + inline void blendPixelColorXY(uint16_t x, uint16_t y, CRGB c, uint8_t blend) const { blendPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), blend); } + inline void addPixelColorXY(int x, int y, uint32_t color, bool preserveCR = true) const { 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) const { addPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), preserveCR); } + inline void fadePixelColorXY(uint16_t x, uint16_t y, uint8_t fade) const { setPixelColorXY(x, y, color_fade(getPixelColorXY(x,y), fade, true)); } + inline void blurCols(fract8 blur_amount, bool smear = false) const { blur2D(0, blur_amount, smear); } // blur all columns (50% faster than full 2D blur) + inline void blurRows(fract8 blur_amount, bool smear = false) const { blur2D(blur_amount, 0, smear); } // blur all rows (50% faster than full 2D blur) //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); - void drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t c, bool soft = false); - inline void drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB c, bool soft = false) { drawCircle(cx, cy, radius, RGBW32(c.r,c.g,c.b,0), soft); } - void fillCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t c, bool soft = false); - inline void fillCircle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB c, bool soft = false) { fillCircle(cx, cy, radius, RGBW32(c.r,c.g,c.b,0), soft); } - void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint32_t c, bool soft = false); - inline void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, CRGB c, bool soft = false) { drawLine(x0, y0, x1, y1, RGBW32(c.r,c.g,c.b,0), soft); } // automatic inline - void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, uint32_t color, uint32_t col2 = 0, int8_t rotate = 0, bool usePalGrad = false); - 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, bool usePalGrad = false) { drawCharacter(chr, x, y, w, h, RGBW32(c.r,c.g,c.b,0), RGBW32(c2.r,c2.g,c2.b,0), rotate, usePalGrad); } // automatic inline - void wu_pixel(uint32_t x, uint32_t y, CRGB c); - inline void fill_solid(CRGB c) { fill(RGBW32(c.r,c.g,c.b,0)); } + void blur2D(uint8_t blur_x, uint8_t blur_y, bool smear = false) const; + void moveX(int delta, bool wrap = false) const; + void moveY(int delta, bool wrap = false) const; + void move(unsigned dir, unsigned delta, bool wrap = false) const; + void drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t c, bool soft = false) const; + void fillCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t c, bool soft = false) const; + void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint32_t c, bool soft = false) const; + void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, uint32_t color, uint32_t col2 = 0, int8_t rotate = 0) const; + void wu_pixel(uint32_t x, uint32_t y, CRGB c) const; + inline void drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB c, bool soft = false) const { drawCircle(cx, cy, radius, RGBW32(c.r,c.g,c.b,0), soft); } + inline void fillCircle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB c, bool soft = false) const { fillCircle(cx, cy, radius, RGBW32(c.r,c.g,c.b,0), soft); } + inline void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, CRGB c, bool soft = false) const { drawLine(x0, y0, x1, y1, RGBW32(c.r,c.g,c.b,0), soft); } // automatic inline + inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, CRGB c, CRGB c2 = CRGB::Black, int8_t rotate = 0) const { 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 + inline void fill_solid(CRGB c) const { fill(RGBW32(c.r,c.g,c.b,0)); } #else - inline bool is2D() const { return false; } - 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)); } + inline constexpr bool is2D() const { return false; } + inline void setPixelColorXY(int x, int y, uint32_t c) const { setPixelColor(x, c); } + inline void setPixelColorXY(unsigned x, unsigned y, uint32_t c) const { setPixelColor(int(x), c); } + inline void setPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) const { setPixelColor(x, RGBW32(r,g,b,w)); } + inline void setPixelColorXY(int x, int y, CRGB c) const { setPixelColor(x, RGBW32(c.r,c.g,c.b,0)); } + inline void setPixelColorXY(unsigned x, unsigned y, CRGB c) const { setPixelColor(int(x), RGBW32(c.r,c.g,c.b,0)); } #ifdef WLED_USE_AA_PIXELS - inline void setPixelColorXY(float x, float y, uint32_t c, bool aa = true) { setPixelColor(x, c, aa); } + inline void setPixelColorXY(float x, float y, uint32_t c, bool aa = true) const { 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 void setPixelColorXY(float x, float y, CRGB c, bool aa = true) const { setPixelColor(x, RGBW32(c.r,c.g,c.b,0), aa); } #endif - inline bool isPixelXYClipped(int x, int y) { return isPixelClipped(x); } - 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 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 bool isPixelXYClipped(int x, int y) const { return isPixelClipped(x); } + inline uint32_t getPixelColorXY(int x, int y) const { return getPixelColor(x); } + inline void blendPixelColorXY(uint16_t x, uint16_t y, uint32_t c, uint8_t blend) const { blendPixelColor(x, c, blend); } + inline void blendPixelColorXY(uint16_t x, uint16_t y, CRGB c, uint8_t blend) const { blendPixelColor(x, RGBW32(c.r,c.g,c.b,0), blend); } + inline void addPixelColorXY(int x, int y, uint32_t color, bool saturate = false) const { addPixelColor(x, color, saturate); } + inline void addPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0, bool saturate = false) const { addPixelColor(x, RGBW32(r,g,b,w), saturate); } + inline void addPixelColorXY(int x, int y, CRGB c, bool saturate = false) const { addPixelColor(x, RGBW32(c.r,c.g,c.b,0), saturate); } + inline void fadePixelColorXY(uint16_t x, uint16_t y, uint8_t fade) const { fadePixelColor(x, fade); } //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 blurCols(fract8 blur_amount, bool smear = false) { blur(blur_amount, smear); } // blur all columns (50% faster than full 2D blur) + inline void blurRows(fract8 blur_amount, bool smear = 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) {} @@ -780,16 +794,15 @@ typedef struct Segment { inline void fillCircle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB c, bool soft = false) {} inline void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint32_t c, bool soft = false) {} inline void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, CRGB c, bool soft = false) {} - inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, uint32_t color, uint32_t = 0, int8_t = 0, bool = false) {} - inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, CRGB color) {} - inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, CRGB c, CRGB c2, int8_t rotate = 0, bool usePalGrad = false) {} + inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, uint32_t color, uint32_t = 0, int8_t = 0) {} + inline void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, CRGB c, CRGB c2, int8_t rotate = 0) {} inline void wu_pixel(uint32_t x, uint32_t y, CRGB c) {} #endif -} segment; -//static int segSize = sizeof(Segment); + friend class WS2812FX; +}; -// main "strip" class -class WS2812FX { // 96 bytes +// main "strip" class (104 bytes) +class WS2812FX { typedef uint16_t (*mode_ptr)(); // pointer to mode function typedef void (*show_callback)(); // pre show callback typedef struct ModeData { @@ -799,8 +812,6 @@ class WS2812FX { // 96 bytes ModeData(uint8_t id, uint16_t (*fcn)(void), const char *data) : _id(id), _fcn(fcn), _data(data) {} } mode_data_t; - static WS2812FX* instance; - public: WS2812FX() : @@ -808,9 +819,6 @@ class WS2812FX { // 96 bytes now(millis()), timebase(0), isMatrix(false), -#ifndef WLED_DISABLE_2D - panels(1), -#endif #ifdef WLED_AUTOSEGMENTS autoSegments(true), #else @@ -819,27 +827,27 @@ class WS2812FX { // 96 bytes correctWB(false), cctFromRgb(false), // true private variables + _pixels(nullptr), _suspend(false), - _length(DEFAULT_LED_COUNT), _brightness(DEFAULT_BRIGHTNESS), + _length(DEFAULT_LED_COUNT), _transitionDur(750), - _targetFps(WLED_FPS), _frametime(FRAMETIME_FIXED), - _cumulativeFps(50 << FPS_CALC_SHIFT), + _cumulativeFps(WLED_FPS << FPS_CALC_SHIFT), + _targetFps(WLED_FPS), _isServicing(false), _isOffRefreshRequired(false), _hasWhiteChannel(false), _triggered(false), + _segment_index(0), + _mainSegment(0), _modeCount(MODE_COUNT), _callback(nullptr), customMappingTable(nullptr), customMappingSize(0), _lastShow(0), - _lastServiceShow(0), - _segment_index(0), - _mainSegment(0) + _lastServiceShow(0) { - WS2812FX::instance = this; _mode.reserve(_modeCount); // allocate memory to prevent initial fragmentation (does not increase size()) _modeData.reserve(_modeCount); // allocate memory to prevent initial fragmentation (does not increase size()) if (_mode.capacity() <= 1 || _modeData.capacity() <= 1) _modeCount = 1; // memory allocation failed only show Solid @@ -847,18 +855,16 @@ class WS2812FX { // 96 bytes } ~WS2812FX() { - if (customMappingTable) free(customMappingTable); + d_free(_pixels); + d_free(customMappingTable); _mode.clear(); _modeData.clear(); _segments.clear(); #ifndef WLED_DISABLE_2D panel.clear(); #endif - customPalettes.clear(); } - static WS2812FX* getInstance() { return instance; } - void #ifdef WLED_DEBUG printSize(), // prints memory usage for strip components @@ -873,29 +879,34 @@ class WS2812FX { // 96 bytes resetSegments(), // marks all segments for reset makeAutoSegments(bool forceReset = false), // will create segments based on configured outputs fixInvalidSegments(), // fixes incorrect segment configuration - setPixelColor(unsigned i, uint32_t c) const, // paints absolute strip pixel with index n and color c + blendSegment(const Segment &topSegment) const, // blends topSegment into pixels show(), // initiates LED output setTargetFps(unsigned fps), - setupEffectData(); // add default effects to the list; defined in FX.cpp - - inline void resetTimebase() { timebase = 0UL - millis(); } - inline void restartRuntime() { for (Segment &seg : _segments) { seg.markForReset().resetIfRequired(); } } - inline void setTransitionMode(bool t) { for (Segment &seg : _segments) seg.startTransition(t ? _transitionDur : 0); } - inline void setPixelColor(unsigned n, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0) const { setPixelColor(n, RGBW32(r,g,b,w)); } - inline void setPixelColor(unsigned n, CRGB c) const { setPixelColor(n, c.red, c.green, c.blue); } - inline void fill(uint32_t c) const { for (unsigned i = 0; i < getLengthTotal(); i++) setPixelColor(i, c); } // fill whole strip with color (inline) + setupEffectData(), // add default effects to the list; defined in FX.cpp + waitForIt(); // wait until frame is over (service() has finished or time for 1 frame has passed) + + void setRealtimePixelColor(unsigned i, uint32_t c); + inline void setPixelColor(unsigned n, uint32_t c) const { if (n < getLengthTotal()) _pixels[n] = c; } // paints absolute strip pixel with index n and color c + inline void resetTimebase() { timebase = 0UL - millis(); } + inline void setPixelColor(unsigned n, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0) const + { setPixelColor(n, RGBW32(r,g,b,w)); } + inline void setPixelColor(unsigned n, CRGB c) const { setPixelColor(n, c.red, c.green, c.blue); } + inline void fill(uint32_t c) const { for (size_t i = 0; i < getLengthTotal(); i++) setPixelColor(i, c); } // fill whole strip with color (inline) inline void trigger() { _triggered = true; } // Forces the next frame to be computed on all active segments. inline void setShowCallback(show_callback cb) { _callback = cb; } inline void setTransition(uint16_t t) { _transitionDur = t; } // sets transition time (in ms) - inline void appendSegment(const Segment &seg = Segment()) { if (_segments.size() < getMaxSegments()) _segments.push_back(seg); } + inline void appendSegment(uint16_t sStart=0, uint16_t sStop=30, uint16_t sStartY = 0, uint16_t sStopY = 1) + { if (_segments.size() < getMaxSegments()) _segments.emplace_back(sStart,sStop,sStartY,sStopY); } inline void suspend() { _suspend = true; } // will suspend (and canacel) strip.service() execution inline void resume() { _suspend = false; } // will resume strip.service() execution - bool - checkSegmentAlignment() const, - hasRGBWBus() const, - hasCCTBus() const, - deserializeMap(unsigned n = 0); + void restartRuntime(); + void setTransitionMode(bool t); + + bool checkSegmentAlignment() const; + bool hasRGBWBus() const; + bool hasCCTBus() const; + bool 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 @@ -904,26 +915,23 @@ class WS2812FX { // 96 bytes inline bool isSuspended() const { return _suspend; } // returns true if strip.service() execution is suspended inline bool needsUpdate() const { return _triggered; } // returns true if strip received a trigger() request - uint8_t - paletteBlend, - getActiveSegmentsNum() const, - getFirstSelectedSegId() const, - getLastActiveSegmentId() const, - getActiveSegsLightCapabilities(bool selectedOnly = false) const, - addEffect(uint8_t id, mode_ptr mode_fn, const char *mode_name); // add effect to the list; defined in FX.cpp; + uint8_t paletteBlend; + uint8_t getActiveSegmentsNum() const; + uint8_t getFirstSelectedSegId() const; + uint8_t getLastActiveSegmentId() const; + uint8_t getActiveSegsLightCapabilities(bool selectedOnly = false) const; + uint8_t 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 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 - inline uint8_t getPaletteCount() const { return 13 + GRADIENT_PALETTE_COUNT + customPalettes.size(); } inline uint8_t getTargetFps() const { return _targetFps; } // returns rough FPS value for las 2s interval inline uint8_t getModeCount() const { return _modeCount; } // returns number of registered modes/effects - uint16_t - getLengthPhysical() const, - getLengthTotal() const; // will include virtual/nonexistent pixels in matrix + uint16_t getLengthPhysical() const; + uint16_t getLengthTotal() const; // will include virtual/nonexistent pixels in matrix inline uint16_t getFps() const { return (millis() - _lastShow > 2000) ? 0 : (FPS_MULTIPLIER * _cumulativeFps) >> FPS_CALC_SHIFT; } // Returns the refresh rate of the LED strip (_cumulativeFps is stored in fixed point) inline uint16_t getFrameTime() const { return _frametime; } // returns amount of time a frame should take (in ms) @@ -936,12 +944,11 @@ class WS2812FX { // 96 bytes }; unsigned long now, timebase; - uint32_t getPixelColor(unsigned i) const; - - inline uint32_t getLastShow() const { return _lastShow; } // returns millis() timestamp of last strip.show() call + inline uint32_t getPixelColor(unsigned n) const { return (n < getLengthTotal()) ? _pixels[n] : 0; } // returns color of pixel n + inline uint32_t getLastShow() const { return _lastShow; } // returns millis() timestamp of last strip.show() call - 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 *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 Segment& getSegment(unsigned id); inline Segment& getFirstSelectedSeg() { return _segments[getFirstSelectedSegId()]; } // returns reference to first segment that is "selected" @@ -949,15 +956,9 @@ class WS2812FX { // 96 bytes inline Segment* getSegments() { return &(_segments[0]); } // returns pointer to segment vector structure (warning: use carefully) // 2D support (panels) - bool - isMatrix; #ifndef WLED_DISABLE_2D - #define WLED_MAX_PANELS 18 - uint8_t - panels; - - typedef struct panel_t { + struct Panel { uint16_t xOffset; // x offset relative to the top left of matrix in LEDs uint16_t yOffset; // y offset relative to the top left of matrix in LEDs uint8_t width; // width of the panel @@ -971,50 +972,48 @@ class WS2812FX { // 96 bytes bool serpentine : 1; // is serpentine? }; }; - panel_t() - : xOffset(0) - , yOffset(0) - , width(8) - , height(8) - , options(0) + Panel() + : xOffset(0) + , yOffset(0) + , width(8) + , height(8) + , options(0) {} - } Panel; + }; std::vector panel; #endif void setUpMatrix(); // sets up automatic matrix ledmap from panel configuration - // outsmart the compiler :) by correctly overloading - inline void setPixelColorXY(int x, int y, uint32_t c) const { setPixelColor((unsigned)(y * Segment::maxWidth + x), c); } - inline void setPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) const { setPixelColorXY(x, y, RGBW32(r,g,b,w)); } - inline void setPixelColorXY(int x, int y, CRGB c) const { setPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0)); } - - inline uint32_t getPixelColorXY(int x, int y) const { return getPixelColor(isMatrix ? y * Segment::maxWidth + x : x); } + inline void setPixelColorXY(unsigned x, unsigned y, uint32_t c) const { setPixelColor(y * Segment::maxWidth + x, c); } + inline void setPixelColorXY(unsigned x, unsigned y, byte r, byte g, byte b, byte w = 0) const { setPixelColorXY(x, y, RGBW32(r,g,b,w)); } + inline void setPixelColorXY(unsigned x, unsigned y, CRGB c) const { setPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0)); } + inline uint32_t getPixelColorXY(unsigned x, unsigned y) const { return getPixelColor(y * Segment::maxWidth + x); } // end 2D support - void loadCustomPalettes(); // loads custom palettes from JSON - std::vector customPalettes; // TODO: move custom palettes out of WS2812FX class - + bool isMatrix; struct { bool autoSegments : 1; bool correctWB : 1; bool cctFromRgb : 1; }; - std::vector _segments; - friend struct Segment; + Segment *_currentSegment; private: + uint32_t *_pixels; + std::vector _segments; + volatile bool _suspend; - uint16_t _length; uint8_t _brightness; + uint16_t _length; uint16_t _transitionDur; - uint8_t _targetFps; uint16_t _frametime; uint16_t _cumulativeFps; + uint8_t _targetFps; // will require only 1 byte struct { @@ -1024,6 +1023,9 @@ class WS2812FX { // 96 bytes bool _triggered : 1; }; + uint8_t _segment_index; + uint8_t _mainSegment; + uint8_t _modeCount; std::vector _mode; // SRAM footprint: 4 bytes per element std::vector _modeData; // mode (effect) name and its slider control data array @@ -1036,11 +1038,10 @@ class WS2812FX { // 96 bytes unsigned long _lastShow; unsigned long _lastServiceShow; - uint8_t _segment_index; - uint8_t _mainSegment; + friend class Segment; }; extern const char JSON_mode_names[]; extern const char JSON_palette_names[]; -#endif \ No newline at end of file +#endif diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index 61d14a12be..9a3c6fbe81 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -8,7 +8,6 @@ Parts of the code adapted from WLED Sound Reactive */ #include "wled.h" -#include "FX.h" #include "palettes.h" // setUpMatrix() - constructs ledmap array from matrix of panels with WxH pixels @@ -26,8 +25,7 @@ void WS2812FX::setUpMatrix() { // calculate width dynamically because it may have gaps Segment::maxWidth = 1; Segment::maxHeight = 1; - for (size_t i = 0; i < panel.size(); i++) { - Panel &p = panel[i]; + for (const Panel &p : panel) { if (p.xOffset + p.width > Segment::maxWidth) { Segment::maxWidth = p.xOffset + p.width; } @@ -37,21 +35,24 @@ void WS2812FX::setUpMatrix() { } // safety check - if (Segment::maxWidth * Segment::maxHeight > MAX_LEDS || Segment::maxWidth <= 1 || Segment::maxHeight <= 1) { + if (Segment::maxWidth * Segment::maxHeight > MAX_LEDS || Segment::maxWidth > 255 || Segment::maxHeight > 255 || Segment::maxWidth <= 1 || Segment::maxHeight <= 1) { DEBUG_PRINTLN(F("2D Bounds error.")); isMatrix = false; Segment::maxWidth = _length; Segment::maxHeight = 1; - panels = 0; panel.clear(); // release memory allocated by panels + panel.shrink_to_fit(); // release memory if allocated resetSegments(); return; } + suspend(); + waitForIt(); + customMappingSize = 0; // prevent use of mapping if anything goes wrong - if (customMappingTable) free(customMappingTable); - customMappingTable = static_cast(malloc(sizeof(uint16_t)*getLengthTotal())); + d_free(customMappingTable); + customMappingTable = static_cast(d_malloc(sizeof(uint16_t)*getLengthTotal())); // prefer to not use SPI RAM if (customMappingTable) { customMappingSize = getLengthTotal(); @@ -85,7 +86,7 @@ void WS2812FX::setUpMatrix() { JsonArray map = pDoc->as(); gapSize = map.size(); if (!map.isNull() && gapSize >= matrixSize) { // not an empty map - gapTable = static_cast(malloc(gapSize)); + gapTable = static_cast(p_malloc(gapSize)); if (gapTable) for (size_t i = 0; i < gapSize; i++) { gapTable[i] = constrain(map[i], -1, 1); } @@ -96,8 +97,7 @@ void WS2812FX::setUpMatrix() { } unsigned x, y, pix=0; //pixel - for (size_t pan = 0; pan < panel.size(); pan++) { - Panel &p = panel[pan]; + for (const Panel &p : panel) { unsigned h = p.vertical ? p.height : p.width; unsigned v = p.vertical ? p.width : p.height; for (size_t j = 0; j < v; j++){ @@ -113,7 +113,8 @@ void WS2812FX::setUpMatrix() { } // delete gap array as we no longer need it - if (gapTable) free(gapTable); + p_free(gapTable); + resume(); #ifdef WLED_DEBUG DEBUG_PRINT(F("Matrix ledmap:")); @@ -126,7 +127,6 @@ void WS2812FX::setUpMatrix() { } else { // memory allocation error DEBUG_PRINTLN(F("ERROR 2D LED map allocation error.")); isMatrix = false; - panels = 0; panel.clear(); Segment::maxWidth = _length; Segment::maxHeight = 1; @@ -144,103 +144,50 @@ void WS2812FX::setUpMatrix() { /////////////////////////////////////////////////////////// #ifndef WLED_DISABLE_2D - -// raw setColor function without checks (checks are done in setPixelColorXY()) -void IRAM_ATTR_YN Segment::_setPixelColorXY_raw(const int& x, const int& y, uint32_t& col) const -{ - const int baseX = start + x; - const int baseY = startY + y; -#ifndef WLED_DISABLE_MODE_BLEND - // if blending modes, blend with underlying pixel - if (_modeBlend && blendingStyle == BLEND_STYLE_FADE) col = color_blend16(strip.getPixelColorXY(baseX, baseY), col, 0xFFFFU - progress()); -#endif - strip.setPixelColorXY(baseX, baseY, col); - - // Apply mirroring - if (mirror || mirror_y) { - const int mirrorX = start + width() - x - 1; - const int mirrorY = startY + height() - y - 1; - if (mirror) strip.setPixelColorXY(transpose ? baseX : mirrorX, transpose ? mirrorY : baseY, col); - if (mirror_y) strip.setPixelColorXY(transpose ? mirrorX : baseX, transpose ? baseY : mirrorY, col); - if (mirror && mirror_y) strip.setPixelColorXY(mirrorX, mirrorY, col); - } -} - -// pixel is clipped if it falls outside clipping range (_modeBlend==true) or is inside clipping range (_modeBlend==false) +// pixel is clipped if it falls outside clipping range // if clipping start > stop the clipping range is inverted -// _modeBlend==true -> old effect during transition -// _modeBlend==false -> new effect during transition bool IRAM_ATTR_YN Segment::isPixelXYClipped(int x, int y) const { -#ifndef WLED_DISABLE_MODE_BLEND - if (_clipStart != _clipStop && blendingStyle != BLEND_STYLE_FADE) { - const bool invertX = _clipStart > _clipStop; + if (blendingStyle != BLEND_STYLE_FADE && isInTransition() && _clipStart != _clipStop) { + const bool invertX = _clipStart > _clipStop; const bool 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; + const int cStartX = invertX ? _clipStop : _clipStart; + const int cStopX = invertX ? _clipStart : _clipStop; + const int cStartY = invertY ? _clipStopY : _clipStartY; + const int cStopY = 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()) + const unsigned width = cStopX - cStartX; // assumes full segment width (faster than virtualWidth()) + const unsigned len = width * (cStopY - cStartY); // 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; + 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) + if (blendingStyle == BLEND_STYLE_CIRCULAR_IN || blendingStyle == BLEND_STYLE_CIRCULAR_OUT) { + const int cx = (cStopX-cStartX+1) / 2; + const int cy = (cStopY-cStartY+1) / 2; + const bool out = (blendingStyle == BLEND_STYLE_CIRCULAR_OUT); + const unsigned prog = out ? progress() : 0xFFFFU - progress(); + int radius2 = max(cx, cy) * prog / 0xFFFF; + radius2 = 2 * radius2 * radius2; + if (radius2 == 0) return out; + const int dx = x - cx; + const int dy = y - cy; + const bool outside = dx * dx + dy * dy > radius2; + return out ? outside : !outside; + } + bool xInside = (x >= cStartX && x < cStopX); if (invertX) xInside = !xInside; + bool yInside = (y >= cStartY && y < cStopY); if (invertY) yInside = !yInside; + const bool clip = blendingStyle == BLEND_STYLE_OUTSIDE_IN ? xInside || yInside : xInside && yInside; return !clip; } -#endif return false; } void IRAM_ATTR_YN Segment::setPixelColorXY(int x, int y, uint32_t col) const { 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) - -#ifndef WLED_DISABLE_MODE_BLEND - unsigned prog = 0xFFFF - progress(); - if (!prog && !_modeBlend && (blendingStyle & BLEND_STYLE_PUSH_MASK)) { - 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 - - // if color is unscaled - if (!_colorScaled) col = color_fade(col, _segBri); - - 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 - unsigned groupLen = groupLength(); - - if (groupLen > 1) { - int W = width(); - int H = height(); - x *= groupLen; // expand to physical pixels - y *= groupLen; // expand to physical pixels - 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); - } - } - } else { - _setPixelColorXY_raw(x, y, col); - } + if (x >= (int)vWidth() || y >= (int)vHeight() || x < 0 || y < 0) return; // if pixel would fall out of virtual segment just exit + setPixelColorXYRaw(x, y, col); } #ifdef WLED_USE_AA_PIXELS @@ -289,39 +236,17 @@ void Segment::setPixelColorXY(float x, float y, uint32_t col, bool aa) const // returns RGBW values of pixel uint32_t IRAM_ATTR_YN Segment::getPixelColorXY(int x, int y) const { if (!isActive()) return 0; // not active - - const int vW = vWidth(); - const int vH = vHeight(); - -#ifndef WLED_DISABLE_MODE_BLEND - unsigned prog = 0xFFFF - progress(); - if (!prog && !_modeBlend && (blendingStyle & BLEND_STYLE_PUSH_MASK)) { - 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) { std::swap(x,y); } // swap X & Y if segment transposed - x *= groupLength(); // expand to physical pixels - y *= groupLength(); // expand to physical pixels - if (x >= width() || y >= height()) return 0; - return strip.getPixelColorXY(start + x, startY + y); + if (x >= (int)vWidth() || y >= (int)vHeight() || x<0 || y<0) return 0; // if pixel would fall out of virtual segment just exit + return getPixelColorXYRaw(x,y); } // 2D blurring, can be asymmetrical -void Segment::blur2D(uint8_t blur_x, uint8_t blur_y, bool smear) { +void Segment::blur2D(uint8_t blur_x, uint8_t blur_y, bool smear) const { if (!isActive()) return; // not active const unsigned cols = vWidth(); const unsigned rows = vHeight(); - uint32_t lastnew; // not necessary to initialize lastnew and last, as both will be initialized by the first loop iteration + const auto XY = [&](unsigned x, unsigned y){ return x + y*cols; }; + uint32_t lastnew; // not necessary to initialize lastnew and last, as both will be initialized by the first loop iteration uint32_t last; if (blur_x) { const uint8_t keepx = smear ? 255 : 255 - blur_x; @@ -330,20 +255,20 @@ void Segment::blur2D(uint8_t blur_x, uint8_t blur_y, bool smear) { uint32_t carryover = BLACK; uint32_t curnew = BLACK; for (unsigned x = 0; x < cols; x++) { - uint32_t cur = getPixelColorXY(x, row); + uint32_t cur = getPixelColorRaw(XY(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 + if (last != prev) setPixelColorRaw(XY(x - 1, row), prev); + } else setPixelColorRaw(XY(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 + setPixelColorRaw(XY(cols-1, row), curnew); // set last pixel } } if (blur_y) { @@ -353,20 +278,20 @@ void Segment::blur2D(uint8_t blur_x, uint8_t blur_y, bool smear) { uint32_t carryover = BLACK; uint32_t curnew = BLACK; for (unsigned y = 0; y < rows; y++) { - uint32_t cur = getPixelColorXY(col, y); + uint32_t cur = getPixelColorRaw(XY(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 + if (last != prev) setPixelColorRaw(XY(col, y - 1), prev); + } else setPixelColorRaw(XY(col, y), curnew); // first pixel lastnew = curnew; last = cur; //save original value for comparison on next iteration carryover = part; } - setPixelColorXY(col, rows - 1, curnew); + setPixelColorRaw(XY(col, rows - 1), curnew); } } } @@ -445,10 +370,11 @@ void Segment::box_blur(unsigned radius, bool smear) { delete[] tmpWSum; } */ -void Segment::moveX(int delta, bool wrap) { +void Segment::moveX(int delta, bool wrap) const { 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) + const auto XY = [&](unsigned x, unsigned y){ return x + y*vW; }; int absDelta = abs(delta); if (absDelta >= vW) return; uint32_t newPxCol[vW]; @@ -465,16 +391,17 @@ void Segment::moveX(int delta, bool wrap) { for (int x = 0; x < stop; x++) { int srcX = x + newDelta; if (wrap) srcX %= vW; // Wrap using modulo when `wrap` is true - newPxCol[x] = getPixelColorXY(srcX, y); + newPxCol[x] = getPixelColorRaw(XY(srcX, y)); } - for (int x = 0; x < stop; x++) setPixelColorXY(x + start, y, newPxCol[x]); + for (int x = 0; x < stop; x++) setPixelColorRaw(XY(x + start, y), newPxCol[x]); } } -void Segment::moveY(int delta, bool wrap) { +void Segment::moveY(int delta, bool wrap) const { 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) + const auto XY = [&](unsigned x, unsigned y){ return x + y*vW; }; int absDelta = abs(delta); if (absDelta >= vH) return; uint32_t newPxCol[vH]; @@ -491,9 +418,9 @@ void Segment::moveY(int delta, bool wrap) { for (int y = 0; y < stop; y++) { int srcY = y + newDelta; if (wrap) srcY %= vH; // Wrap using modulo when `wrap` is true - newPxCol[y] = getPixelColorXY(x, srcY); + newPxCol[y] = getPixelColorRaw(XY(x, srcY)); } - for (int y = 0; y < stop; y++) setPixelColorXY(x, y + start, newPxCol[y]); + for (int y = 0; y < stop; y++) setPixelColorRaw(XY(x, y + start), newPxCol[y]); } } @@ -501,7 +428,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(unsigned dir, unsigned delta, bool wrap) { +void Segment::move(unsigned dir, unsigned delta, bool wrap) const { if (delta==0) return; switch (dir) { case 0: moveX( delta, wrap); break; @@ -515,7 +442,7 @@ void Segment::move(unsigned dir, unsigned delta, bool wrap) { } } -void Segment::drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t col, bool soft) { +void Segment::drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t col, bool soft) const { if (!isActive() || radius == 0) return; // not active if (soft) { // Xiaolin Wu’s algorithm @@ -549,9 +476,6 @@ 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; @@ -570,20 +494,16 @@ void Segment::drawCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t col, d += 4 * x + 6; } } - _colorScaled = false; } } // 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) { +void Segment::fillCircle(uint16_t cx, uint16_t cy, uint8_t radius, uint32_t col, bool soft) const { 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, _segBri); - _colorScaled = true; // fill it for (int y = -radius; y <= radius; y++) { for (int x = -radius; x <= radius; x++) { @@ -593,11 +513,10 @@ 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 -void Segment::drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint32_t c, bool soft) { +void Segment::drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint32_t c, bool soft) const { 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) @@ -633,15 +552,12 @@ void Segment::drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint3 int y = int(intersectY); if (steep) std::swap(x,y); // temporaryly swap if steep // pixel coverage is determined by fractional part of y co-ordinate - setPixelColorXY(x, y, color_blend(c, getPixelColorXY(x, y), keep)); - setPixelColorXY(x+int(steep), y+int(!steep), color_blend(c, getPixelColorXY(x+int(steep), y+int(!steep)), seep)); + blendPixelColorXY(x, y, c, seep); + blendPixelColorXY(x+int(steep), y+int(!steep), c, keep); intersectY += gradient; 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 (;;) { @@ -651,7 +567,6 @@ 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; } } @@ -663,29 +578,26 @@ void Segment::drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint3 // draws a raster font character on canvas // only supports: 4x6=24, 5x8=40, 5x12=60, 6x8=48 and 7x9=63 fonts ATM -void Segment::drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, uint32_t color, uint32_t col2, int8_t rotate, bool usePalGrad) { +void Segment::drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, uint32_t color, uint32_t col2, int8_t rotate) const { 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 font = w*h; - CRGB col = CRGB(color); - CRGBPalette16 grad = CRGBPalette16(col, col2 ? CRGB(col2) : col); - if(usePalGrad) grad = SEGPALETTE; // selected palette as gradient + // if col2 == BLACK then use currently selected palette for gradient otherwise create gradient from color and col2 + CRGBPalette16 grad = col2 ? CRGBPalette16(CRGB(color), CRGB(col2)) : SEGPALETTE; // selected palette as gradient - //if (w<5 || w>6 || h!=8) return; for (int i = 0; i= (int)vWidth() || y0 < 0 || y0 >= (int)vHeight()) continue; // drawing off-screen if (((bits>>(j+(8-w))) & 0x01)) { // bit set - setPixelColorXY(x0, y0, c.color32); + setPixelColorXYRaw(x0, y0, c.color32); } } - _colorScaled = false; } } #define WU_WEIGHT(a,b) ((uint8_t) (((a)*(b)+(a)+(b))>>8)) -void Segment::wu_pixel(uint32_t x, uint32_t y, CRGB c) { //awesome wu_pixel procedure by reddit u/sutaburosu +void Segment::wu_pixel(uint32_t x, uint32_t y, CRGB c) const { //awesome wu_pixel procedure by reddit u/sutaburosu if (!isActive()) return; // not active // extract the fractional parts and derive their inverses unsigned xx = x & 0xff, yy = y & 0xff, ix = 255 - xx, iy = 255 - yy; diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp old mode 100644 new mode 100755 index 42403fa852..438c36d5a5 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -10,7 +10,6 @@ Modified heavily for WLED */ #include "wled.h" -#include "FX.h" #include "FXparticleSystem.h" // TODO: better define the required function (mem service) in FX.h? #include "palettes.h" @@ -30,39 +29,10 @@ 19, 18, 17, 16, 15, 20, 21, 22, 23, 24, 29, 28, 27, 26, 25]} */ -#ifndef PIXEL_COUNTS - #define PIXEL_COUNTS DEFAULT_LED_COUNT -#endif - -#ifndef DATA_PINS - #define DATA_PINS DEFAULT_LED_PIN -#endif - -#ifndef LED_TYPES - #define LED_TYPES DEFAULT_LED_TYPE -#endif - -#ifndef DEFAULT_LED_COLOR_ORDER - #define DEFAULT_LED_COLOR_ORDER COL_ORDER_GRB //default to GRB -#endif - - #if MAX_NUM_SEGMENTS < WLED_MAX_BUSSES #error "Max segments must be at least max number of busses!" #endif -static constexpr unsigned sumPinsRequired(const unsigned* current, size_t count) { - return (count > 0) ? (Bus::getNumberOfPins(*current) + sumPinsRequired(current+1,count-1)) : 0; -} - -static constexpr bool validatePinsAndTypes(const unsigned* types, unsigned numTypes, unsigned numPins ) { - // Pins provided < pins required -> always invalid - // Pins provided = pins required -> always valid - // Pins provided > pins required -> valid if excess pins are a product of last type pins since it will be repeated - return (sumPinsRequired(types, numTypes) > numPins) ? false : - (numPins - sumPinsRequired(types, numTypes)) % Bus::getNumberOfPins(types[numTypes-1]) == 0; -} - /////////////////////////////////////////////////////////////////////////////// // Segment class implementation @@ -73,34 +43,40 @@ uint16_t Segment::maxHeight = 1; unsigned Segment::_vLength = 0; unsigned Segment::_vWidth = 0; unsigned Segment::_vHeight = 0; -uint8_t Segment::_segBri = 0; uint32_t Segment::_currentColors[NUM_COLORS] = {0,0,0}; -bool Segment::_colorScaled = false; 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::_transitionprogress = 0xFFFF; +uint16_t Segment::_lastPaletteChange = 0; // in seconds; perhaps it should be per segment +uint16_t Segment::_nextPaletteBlend = 0; // in millis -#ifndef WLED_DISABLE_MODE_BLEND -bool Segment::_modeBlend = false; +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 Segment::Segment(const Segment &orig) { //DEBUG_PRINTF_P(PSTR("-- Copy segment constructor: %p -> %p\n"), &orig, this); memcpy((void*)this, (void*)&orig, sizeof(Segment)); - _t = nullptr; // copied segment cannot be in transition + _t = nullptr; // copied segment cannot be in transition name = nullptr; data = nullptr; _dataLen = 0; - if (orig.name) { name = static_cast(malloc(strlen(orig.name)+1)); if (name) strcpy(name, orig.name); } + pixels = nullptr; + if (!stop) return; // nothing to do if segment is inactive/invalid + if (orig.name) { name = static_cast(d_malloc(strlen(orig.name)+1)); if (name) strcpy(name, orig.name); } if (orig.data) { if (allocateData(orig._dataLen)) memcpy(data, orig.data, orig._dataLen); } + if (orig.pixels) { + pixels = static_cast(d_malloc(sizeof(uint32_t) * orig.length())); + if (pixels) memcpy(pixels, orig.pixels, sizeof(uint32_t) * orig.length()); + else { + DEBUG_PRINTLN(F("!!! Not enough RAM for pixel buffer !!!")); + errorFlag = ERR_NORAM_PX; + stop = 0; // mark segment as inactive/invalid + } + } else stop = 0; // mark segment as inactive/invalid } // move constructor @@ -111,6 +87,7 @@ Segment::Segment(Segment &&orig) noexcept { orig.name = nullptr; orig.data = nullptr; orig._dataLen = 0; + orig.pixels = nullptr; } // copy assignment @@ -118,17 +95,29 @@ Segment& Segment::operator= (const Segment &orig) { //DEBUG_PRINTF_P(PSTR("-- Copying segment: %p -> %p\n"), &orig, this); if (this != &orig) { // clean destination - if (name) { free(name); name = nullptr; } - stopTransition(); + if (name) { d_free(name); name = nullptr; } + if (_t) stopTransition(); // also erases _t deallocateData(); + d_free(pixels); // copy source memcpy((void*)this, (void*)&orig, sizeof(Segment)); // erase pointers to allocated data data = nullptr; _dataLen = 0; + pixels = nullptr; + if (!stop) return *this; // nothing to do if segment is inactive/invalid // copy source data - if (orig.name) { name = static_cast(malloc(strlen(orig.name)+1)); if (name) strcpy(name, orig.name); } + if (orig.name) { name = static_cast(d_malloc(strlen(orig.name)+1)); if (name) strcpy(name, orig.name); } if (orig.data) { if (allocateData(orig._dataLen)) memcpy(data, orig.data, orig._dataLen); } + if (orig.pixels) { + pixels = static_cast(d_malloc(sizeof(uint32_t) * orig.length())); + if (pixels) memcpy(pixels, orig.pixels, sizeof(uint32_t) * orig.length()); + else { + DEBUG_PRINTLN(F("!!! Not enough RAM for pixel buffer !!!")); + errorFlag = ERR_NORAM_PX; + stop = 0; // mark segment as inactive/invalid + } + } else stop = 0; // mark segment as inactive/invalid } return *this; } @@ -137,48 +126,60 @@ Segment& Segment::operator= (const Segment &orig) { Segment& Segment::operator= (Segment &&orig) noexcept { //DEBUG_PRINTF_P(PSTR("-- Moving segment: %p -> %p\n"), &orig, this); if (this != &orig) { - if (name) { free(name); name = nullptr; } // free old name - stopTransition(); + if (name) { d_free(name); name = nullptr; } // free old name + if (_t) stopTransition(); // also erases _t deallocateData(); // free old runtime data + d_free(pixels); // free old pixel buffer + // move source data memcpy((void*)this, (void*)&orig, sizeof(Segment)); orig.name = nullptr; orig.data = nullptr; orig._dataLen = 0; - orig._t = nullptr; // old segment cannot be in transition + orig.pixels = nullptr; + orig._t = nullptr; // old segment cannot be in transition } return *this; } // allocates effect data buffer on heap and initialises (erases) it -bool IRAM_ATTR_YN Segment::allocateData(size_t len) { +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 + if (call == 0) { + //DEBUG_PRINTF_P(PSTR("-- Clearing data (%d): %p\n"), len, this); + memset(data, 0, len); // erase buffer if called during effect initialisation + } return true; } - //DEBUG_PRINTF_P(PSTR("-- Allocating data (%d): %p\n", len, this); - deallocateData(); // if the old buffer was smaller release it first - if (Segment::getUsedSegmentData() + len > MAX_SEGMENT_DATA) { + //DEBUG_PRINTF_P(PSTR("-- Allocating data (%d): %p\n"), len, this); + if (Segment::getUsedSegmentData() + len - _dataLen > MAX_SEGMENT_DATA) { // not enough memory - DEBUG_PRINT(F("!!! Effect RAM depleted: ")); - DEBUG_PRINTF_P(PSTR("%d/%d !!!\n"), len, Segment::getUsedSegmentData()); + DEBUG_PRINTF_P(PSTR("!!! Not enough RAM: %d/%d !!!\n"), len, Segment::getUsedSegmentData()); errorFlag = ERR_NORAM; return false; } - // do not use SPI RAM on ESP32 since it is slow - data = (byte*)calloc(len, sizeof(byte)); - if (!data) { DEBUG_PRINTLN(F("!!! Allocation failed. !!!")); return false; } // allocation failed - Segment::addUsedSegmentData(len); - //DEBUG_PRINTF_P(PSTR("--- Allocated data (%p): %d/%d -> %p\n"), this, len, Segment::getUsedSegmentData(), data); - _dataLen = len; - return true; + // prefer DRAM over SPI RAM on ESP32 since it is slow + if (data) data = (byte*)d_realloc(data, len); + else data = (byte*)d_malloc(len); + if (data) { + memset(data, 0, len); // erase buffer + Segment::addUsedSegmentData(len - _dataLen); + _dataLen = len; + //DEBUG_PRINTF_P(PSTR("--- Allocated data (%p): %d/%d -> %p\n"), this, len, Segment::getUsedSegmentData(), data); + return true; + } + // allocation failed + DEBUG_PRINTLN(F("!!! Allocation failed. !!!")); + Segment::addUsedSegmentData(-_dataLen); // subtract original buffer size + errorFlag = ERR_NORAM; + return false; } -void IRAM_ATTR_YN Segment::deallocateData() { +void Segment::deallocateData() { if (!data) { _dataLen = 0; return; } - //DEBUG_PRINTF_P(PSTR("--- Released data (%p): %d/%d -> %p\n"), this, _dataLen, Segment::getUsedSegmentData(), data); if ((Segment::getUsedSegmentData() > 0) && (_dataLen > 0)) { // check that we don't have a dangling / inconsistent data pointer - free(data); + //DEBUG_PRINTF_P(PSTR("--- Released data (%p): %d/%d -> %p\n"), this, _dataLen, Segment::getUsedSegmentData(), data); + d_free(data); } else { DEBUG_PRINTF_P(PSTR("---- Released data (%p): inconsistent UsedSegmentData (%d/%d), cowardly refusing to free nothing.\n"), this, _dataLen, Segment::getUsedSegmentData()); } @@ -195,9 +196,10 @@ void IRAM_ATTR_YN Segment::deallocateData() { * may free that data buffer. */ void Segment::resetIfRequired() { - if (!reset) return; + if (!reset || !isActive()) return; //DEBUG_PRINTF_P(PSTR("-- Segment reset: %p\n"), this); if (data && _dataLen > 0) memset(data, 0, _dataLen); // prevent heap fragmentation (just erase buffer instead of deallocateData()) + if (pixels) for (size_t i = 0; i < length(); i++) pixels[i] = BLACK; // clear pixel buffer next_time = 0; step = 0; call = 0; aux0 = 0; aux1 = 0; reset = false; #ifdef WLED_ENABLE_GIF @@ -207,32 +209,36 @@ void Segment::resetIfRequired() { CRGBPalette16 &Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) { if (pal < 245 && pal > GRADIENT_PALETTE_COUNT+13) pal = 0; - if (pal > 245 && (strip.customPalettes.size() == 0 || 255U-pal > strip.customPalettes.size()-1)) pal = 0; // TODO remove strip dependency by moving customPalettes out of strip + if (pal > 245 && (customPalettes.size() == 0 || 255U-pal > customPalettes.size()-1)) pal = 0; //default palette. Differs depending on effect - if (pal == 0) pal = _default_palette; //load default palette set in FX _data, party colors as default + if (pal == 0) pal = _default_palette; // _default_palette is set in setMode() switch (pal) { case 0: //default palette. Exceptions for specific effects above - targetPalette = PartyColors_p; break; + targetPalette = PartyColors_p; + break; case 1: //randomly generated palette targetPalette = _randomPalette; //random palette is generated at intervals in handleRandomPalette() break; case 2: {//primary color only - CRGB prim = gamma32(colors[0]); - targetPalette = CRGBPalette16(prim); break;} + CRGB prim = colors[0]; + targetPalette = CRGBPalette16(prim); + break;} case 3: {//primary + secondary - CRGB prim = gamma32(colors[0]); - CRGB sec = gamma32(colors[1]); - targetPalette = CRGBPalette16(prim,prim,sec,sec); break;} + CRGB prim = colors[0]; + CRGB sec = colors[1]; + targetPalette = CRGBPalette16(prim,prim,sec,sec); + break;} case 4: {//primary + secondary + tertiary - CRGB prim = gamma32(colors[0]); - CRGB sec = gamma32(colors[1]); - CRGB ter = gamma32(colors[2]); - targetPalette = CRGBPalette16(ter,sec,prim); break;} + CRGB prim = colors[0]; + CRGB sec = colors[1]; + CRGB ter = colors[2]; + targetPalette = CRGBPalette16(ter,sec,prim); + break;} case 5: {//primary + secondary (+tertiary if not off), more distinct - CRGB prim = gamma32(colors[0]); - CRGB sec = gamma32(colors[1]); + CRGB prim = colors[0]; + CRGB sec = colors[1]; if (colors[2]) { - CRGB ter = gamma32(colors[2]); + CRGB ter = colors[2]; targetPalette = CRGBPalette16(prim,prim,prim,prim,prim,sec,sec,sec,sec,sec,ter,ter,ter,ter,ter,prim); } else { targetPalette = CRGBPalette16(prim,prim,prim,prim,prim,prim,prim,prim,sec,sec,sec,sec,sec,sec,sec,sec); @@ -240,7 +246,7 @@ CRGBPalette16 &Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) { break;} default: //progmem palettes if (pal>245) { - targetPalette = strip.customPalettes[255-pal]; // we checked bounds above + targetPalette = customPalettes[255-pal]; // we checked bounds above } else if (pal < 13) { // palette 6 - 12, fastled palettes targetPalette = *fastledPalettes[pal-6]; } else { @@ -253,251 +259,137 @@ CRGBPalette16 &Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) { return targetPalette; } -void Segment::startTransition(uint16_t dur) { - if (dur == 0) { - if (isInTransition()) _t->_dur = dur; // this will stop transition in next handleTransition() +// starting a transition has to occur before change so we get current values 1st +void Segment::startTransition(uint16_t dur, bool segmentCopy) { + if (dur == 0 || !isActive()) { + if (isInTransition()) _t->_dur = 0; return; } - if (isInTransition()) return; // already in transition no need to store anything - - // starting a transition has to occur before change so we get current values 1st - _t = new(std::nothrow) Transition(dur); // no previous transition running - if (!_t) return; // failed to allocate data - - //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 - swapSegenv(_t->_segT); // copy runtime data to temporary - _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; + if (isInTransition()) { + if (segmentCopy && !_t->_oldSegment) { + // already in transition but segment copy requested and not yet created + _t->_oldSegment = new(std::nothrow) Segment(*this); // store/copy current segment settings + _t->_start = millis(); // restart countdown + _t->_dur = dur; + if (_t->_oldSegment) { + _t->_oldSegment->palette = _t->_palette; // restore original palette and colors (from start of transition) + for (unsigned i = 0; i < NUM_COLORS; i++) _t->_oldSegment->colors[i] = _t->_colors[i]; + } + DEBUG_PRINTF_P(PSTR("-- Updated transition with segment copy: S=%p T(%p) O[%p] OP[%p]\n"), this, _t, _t->_oldSegment, _t->_oldSegment->pixels); } + return; } - DEBUG_PRINTF_P(PSTR("-- pal: %d, bri: %d, C:[%08X,%08X,%08X], m: %d\n"), - (int)_t->_palTid, - (int)_t->_briT, - _t->_segT._colorT[0], - _t->_segT._colorT[1], - _t->_segT._colorT[2], - (int)_t->_modeT); -#else - for (size_t i=0; i_colorT[i] = colors[i]; -#endif -} -void Segment::stopTransition() { - if (isInTransition()) { - //DEBUG_PRINTF_P(PSTR("-- Stopping transition: %p\n"), this); - #ifndef WLED_DISABLE_MODE_BLEND - if (_t->_segT._dataT && _t->_segT._dataLenT > 0) { - //DEBUG_PRINTF_P(PSTR("-- Released duplicate data (%d) for %p: %p\n"), _t->_segT._dataLenT, this, _t->_segT._dataT); - free(_t->_segT._dataT); - _t->_segT._dataT = nullptr; - _t->_segT._dataLenT = 0; + // no previous transition running, start by allocating memory for segment copy + _t = new(std::nothrow) Transition(dur); + if (_t) { + _t->_bri = on ? opacity : 0; + _t->_cct = cct; + _t->_palette = palette; + #ifndef WLED_SAVE_RAM + loadPalette(_t->_palT, palette); + #endif + for (int i=0; i_colors[i] = colors[i]; + if (segmentCopy) _t->_oldSegment = new(std::nothrow) Segment(*this); // store/copy current segment settings + #ifdef WLED_DEBUG + if (_t->_oldSegment) { + DEBUG_PRINTF_P(PSTR("-- Started transition: S=%p T(%p) O[%p] OP[%p]\n"), this, _t, _t->_oldSegment, _t->_oldSegment->pixels); + } else { + DEBUG_PRINTF_P(PSTR("-- Started transition without old segment: S=%p T(%p)\n"), this, _t); } #endif - delete _t; - _t = nullptr; - } - _transitionprogress = 0xFFFFU; // stop means stop - transition has ended + }; } -// transition progression between 0-65535 -inline void Segment::updateTransitionProgress() { - _transitionprogress = 0xFFFFU; - if (isInTransition()) { - unsigned diff = millis() - _t->_start; - if (_t->_dur > 0 && diff < _t->_dur) _transitionprogress = diff * 0xFFFFU / _t->_dur; - } -} - -#ifndef WLED_DISABLE_MODE_BLEND -void Segment::swapSegenv(tmpsegd_t &tmpSeg) { - //DEBUG_PRINTF_P(PSTR("-- Saving temp seg: %p->(%p) [%d->%p]\n"), this, &tmpSeg, _dataLen, data); - tmpSeg._optionsT = options; - for (size_t i=0; i_segT)) { - // swap SEGENV with transitional data - options = _t->_segT._optionsT; - for (size_t i=0; i_segT._colorT[i]; - speed = _t->_segT._speedT; - intensity = _t->_segT._intensityT; - custom1 = _t->_segT._custom1T; - custom2 = _t->_segT._custom2T; - custom3 = _t->_segT._custom3T; - check1 = _t->_segT._check1T; - check2 = _t->_segT._check2T; - check3 = _t->_segT._check3T; - aux0 = _t->_segT._aux0T; - aux1 = _t->_segT._aux1T; - step = _t->_segT._stepT; - call = _t->_segT._callT; - data = _t->_segT._dataT; - _dataLen = _t->_segT._dataLenT; - } -} - -void Segment::restoreSegenv(const tmpsegd_t &tmpSeg) { - //DEBUG_PRINTF_P(PSTR("-- Restoring temp seg: %p->(%p) [%d->%p]\n"), &tmpSeg, this, _dataLen, data); - if (isInTransition() && &(_t->_segT) != &tmpSeg) { - // update possibly changed variables to keep old effect running correctly - _t->_segT._aux0T = aux0; - _t->_segT._aux1T = aux1; - _t->_segT._stepT = step; - _t->_segT._callT = call; - //if (_t->_segT._dataT != data) DEBUG_PRINTF_P(PSTR("--- data re-allocated: (%p) %p -> %p\n"), this, _t->_segT._dataT, data); - _t->_segT._dataT = data; - _t->_segT._dataLenT = _dataLen; - } - options = tmpSeg._optionsT; - for (size_t i=0; i_oldSegment); + delete _t; + _t = nullptr; } -#endif -uint8_t Segment::currentBri(bool useCct) const { - unsigned prog = isInTransition() ? progress() : 0xFFFFU; - uint32_t curBri = useCct ? cct : (on ? opacity : 0); - if (prog < 0xFFFFU) { -#ifndef WLED_DISABLE_MODE_BLEND - uint8_t tmpBri = useCct ? _t->_cctT : (_t->_segT._optionsT & 0x0004 ? _t->_briT : 0); - // _modeBlend==true -> old effect - 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); - return curBri / 0xFFFFU; +// sets transition progress variable (0-65535) based on time passed since transition start +void Segment::updateTransitionProgress() const { + if (isInTransition()) { + _t->_progress = 0xFFFF; + unsigned diff = millis() - _t->_start; + if (_t->_dur > 0 && diff < _t->_dur) _t->_progress = diff * 0xFFFFU / _t->_dur; } - return curBri; } -uint8_t Segment::currentMode() const { -#ifndef WLED_DISABLE_MODE_BLEND - unsigned prog = isInTransition() ? progress() : 0xFFFFU; - 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; // _modeBlend==true -> old effect +// will return segment's CCT during a transition +// isPreviousMode() is actually not implemented for CCT in strip.service() as WLED does not support per-pixel CCT +uint8_t Segment::currentCCT() const { + unsigned prog = progress(); + if (prog < 0xFFFFU) { + if (blendingStyle == BLEND_STYLE_FADE) return (cct * prog + (_t->_cct * (0xFFFFU - prog))) / 0xFFFFU; + //else return Segment::isPreviousMode() ? _t->_cct : cct; } - return _modeBlend ? _t->_modeT : mode; // _modeBlend==true -> old effect -#else - return mode; -#endif + return cct; } -uint32_t Segment::currentColor(uint8_t slot) const { - if (slot >= NUM_COLORS) slot = 0; +// will return segment's opacity during a transition (blending it with old in case of FADE transition) +uint8_t Segment::currentBri() const { unsigned prog = progress(); - if (prog == 0xFFFFU) return colors[slot]; -#ifndef WLED_DISABLE_MODE_BLEND - 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; // _modeBlend==true -> old effect - } - return color_blend16(_t->_segT._colorT[slot], colors[slot], prog); -#else - return color_blend16(_t->_colorT[slot], colors[slot], prog); -#endif + unsigned curBri = on ? opacity : 0; + if (prog < 0xFFFFU) { + // this will blend opacity in new mode if style is FADE (single effect call) + if (blendingStyle == BLEND_STYLE_FADE) curBri = (prog * curBri + _t->_bri * (0xFFFFU - prog)) / 0xFFFFU; + else curBri = Segment::isPreviousMode() ? _t->_bri : curBri; + } + return curBri; } // pre-calculate drawing parameters for faster access (based on the idea from @softhack007 from MM fork) -void Segment::beginDraw() { - _vWidth = virtualWidth(); - _vHeight = virtualHeight(); - _vLength = virtualLength(); - _segBri = currentBri(); - unsigned prog = isInTransition() ? progress() : 0xFFFFU; // transition progress; 0xFFFFU = no transition active - // adjust gamma for effects - for (unsigned i = 0; i < NUM_COLORS; i++) { - #ifndef WLED_DISABLE_MODE_BLEND - uint32_t col = isInTransition() ? color_blend16(_t->_segT._colorT[i], colors[i], prog) : colors[i]; +// and blends colors and palettes if necessary +// prog is the progress of the transition (0-65535) and is passed to the function as it may be called in the context of old segment +// which does not have transition structure +void Segment::beginDraw(uint16_t prog) { + setDrawDimensions(); + // load colors into _currentColors + for (unsigned i = 0; i < NUM_COLORS; i++) _currentColors[i] = colors[i]; + // load palette into _currentPalette + loadPalette(Segment::_currentPalette, palette); + if (isInTransition() && prog < 0xFFFFU && blendingStyle == BLEND_STYLE_FADE) { + // blend colors + for (unsigned i = 0; i < NUM_COLORS; i++) _currentColors[i] = color_blend16(_t->_colors[i], colors[i], prog); + // 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 + #ifndef WLED_SAVE_RAM + unsigned noOfBlends = ((255U * prog) / 0xFFFFU) - _t->_prevPaletteBlends; + for (unsigned i = 0; i < noOfBlends; i++, _t->_prevPaletteBlends++) nblendPaletteTowardPalette(_t->_palT, Segment::_currentPalette, 48); + Segment::_currentPalette = _t->_palT; // copy transitioning/temporary palette #else - uint32_t col = isInTransition() ? color_blend16(_t->_colorT[i], colors[i], prog) : colors[i]; + unsigned noOfBlends = ((255U * prog) / 0xFFFFU); + CRGBPalette16 tmpPalette; + loadPalette(tmpPalette, _t->_palette); + for (unsigned i = 0; i < noOfBlends; i++) nblendPaletteTowardPalette(tmpPalette, Segment::_currentPalette, 48); + Segment::_currentPalette = tmpPalette; // copy transitioning/temporary palette #endif - _currentColors[i] = gamma32(col); - } - // load palette into _currentPalette - loadPalette(_currentPalette, palette); - if (prog < 0xFFFFU) { -#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 - } } } -// loads palette of the old FX during transitions (used by particle system) -void Segment::loadOldPalette(void) { - if(isInTransition()) - loadPalette(_currentPalette, _t->_palTid); -} - // relies on WS2812FX::service() to call it for each frame void Segment::handleRandomPalette() { + unsigned long now = millis(); + uint16_t now_s = now / 1000; // we only need seconds (and @dedehai hated shift >> 10) + now = (now_s)*1000 + (now % 1000); // ignore days (now is limited to 18 hours as now_s can only store 65535s ~ 18h 12min) + if (now_s < Segment::_lastPaletteChange) Segment::_lastPaletteChange = 0; // handle overflow (will cause 2*randomPaletteChangeTime glitch at most) // 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 - } - - // 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); + if (now_s > Segment::_lastPaletteChange + randomPaletteChangeTime) { + Segment::_newRandomPalette = useHarmonicRandomPalette ? generateHarmonicRandomPalette(Segment::_randomPalette) : generateRandomPalette(); + Segment::_lastPaletteChange = now_s; + Segment::_nextPaletteBlend = now; // starts blending immediately + } + // there are about 255 blend passes of 48 "blends" to completely blend two palettes (in strip.getTransition() time) + // if randomPaletteChangeTime is shorter than strip.getTransition() palette will never fully blend + unsigned frameTime = strip.getFrameTime(); // in ms [8-1000] + unsigned transitionTime = strip.getTransition(); // in ms [100-65535] + if ((uint16_t)now < Segment::_nextPaletteBlend || now > ((Segment::_lastPaletteChange*1000) + transitionTime + 2*frameTime)) return; // not yet time or past transition time, no need to blend + unsigned transitionFrames = frameTime > transitionTime ? 1 : transitionTime / frameTime; // i.e. 700ms/23ms = 30 or 20000ms/8ms = 2500 or 100ms/1000ms = 0 -> 1 + unsigned noOfBlends = transitionFrames > 255 ? 1 : (255 + (transitionFrames>>1)) / transitionFrames; // we do some rounding here + for (unsigned i = 0; i < noOfBlends; i++) nblendPaletteTowardPalette(Segment::_randomPalette, Segment::_newRandomPalette, 48); + Segment::_nextPaletteBlend = now + ((transitionFrames >> 8) * frameTime); // postpone next blend if necessary } // sets Segment geometry (length or width/height and grouping, spacing and offset as well as 2D mapping) @@ -507,19 +399,11 @@ void Segment::setGeometry(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, ui // return if neither bounds nor grouping have changed bool boundsUnchanged = (start == i1 && stop == i2); #ifndef WLED_DISABLE_2D - if (Segment::maxHeight>1) boundsUnchanged &= (startY == i1Y && stopY == i2Y); // 2D + boundsUnchanged &= (startY == i1Y && stopY == i2Y); // 2D #endif + boundsUnchanged &= (grouping == grp && spacing == spc); // changing grouping and/or spacing changes virtual segment length (painting dimensions) if (stop && (spc > 0 || m12 != map1D2D)) clear(); -/* - if (boundsUnchanged - && (!grp || (grouping == grp && spacing == spc)) - && (ofs == UINT16_MAX || ofs == offset) - && (m12 == map1D2D) - ) return; -*/ - stateChanged = true; // send UDP/WS broadcast - if (grp) { // prevent assignment of 0 grouping = grp; spacing = spc; @@ -530,30 +414,50 @@ void Segment::setGeometry(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, ui if (ofs < UINT16_MAX) offset = ofs; map1D2D = constrain(m12, 0, 7); - DEBUG_PRINTF_P(PSTR("Segment geometry: %d,%d -> %d,%d\n"), (int)i1, (int)i2, (int)i1Y, (int)i2Y); - markForReset(); if (boundsUnchanged) return; + unsigned oldLength = length(); + + DEBUG_PRINTF_P(PSTR("Segment geometry: %d,%d -> %d,%d [%d,%d]\n"), (int)i1, (int)i2, (int)i1Y, (int)i2Y, (int)grp, (int)spc); + markForReset(); + startTransition(strip.getTransition()); // start transition prior to change (if segment is deactivated (start>stop) no transition will happen) + stateChanged = true; // send UDP/WS broadcast + // apply change immediately if (i2 <= i1) { //disable segment + d_free(pixels); + pixels = nullptr; stop = 0; return; } if (i1 < Segment::maxWidth || (i1 >= Segment::maxWidth*Segment::maxHeight && i1 < strip.getLengthTotal())) start = i1; // Segment::maxWidth equals strip.getLengthTotal() for 1D - stop = i2 > Segment::maxWidth*Segment::maxHeight ? MIN(i2,strip.getLengthTotal()) : (i2 > Segment::maxWidth ? Segment::maxWidth : MAX(1,i2)); + stop = i2 > Segment::maxWidth*Segment::maxHeight ? MIN(i2,strip.getLengthTotal()) : constrain(i2, 1, Segment::maxWidth); startY = 0; stopY = 1; #ifndef WLED_DISABLE_2D if (Segment::maxHeight>1) { // 2D if (i1Y < Segment::maxHeight) startY = i1Y; - stopY = i2Y > Segment::maxHeight ? Segment::maxHeight : MAX(1,i2Y); + stopY = constrain(i2Y, 1, Segment::maxHeight); } #endif // safety check if (start >= stop || startY >= stopY) { + d_free(pixels); + pixels = nullptr; stop = 0; return; } + // re-allocate FX render buffer + if (length() != oldLength) { + if (pixels) pixels = static_cast(d_realloc(pixels, sizeof(uint32_t) * length())); + else pixels = static_cast(d_malloc(sizeof(uint32_t) * length())); + if (!pixels) { + DEBUG_PRINTLN(F("!!! Not enough RAM for pixel buffer !!!")); + errorFlag = ERR_NORAM_PX; + stop = 0; + return; + } + } refreshLightCapabilities(); } @@ -565,7 +469,7 @@ Segment &Segment::setColor(uint8_t slot, uint32_t c) { if (slot == 1 && c != BLACK) return *this; // 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 + startTransition(strip.getTransition(), blendingStyle != BLEND_STYLE_FADE); // start transition prior to change colors[slot] = c; stateChanged = true; // send UDP/WS broadcast return *this; @@ -579,7 +483,7 @@ Segment &Segment::setCCT(uint16_t k) { } if (cct != k) { //DEBUG_PRINTF_P(PSTR("- Starting CCT transition: %d\n"), k); - startTransition(strip.getTransition()); // start transition prior to change + startTransition(strip.getTransition(), false); // start transition prior to change (no need to copy segment) cct = k; stateChanged = true; // send UDP/WS broadcast } @@ -589,7 +493,7 @@ Segment &Segment::setCCT(uint16_t k) { Segment &Segment::setOpacity(uint8_t o) { if (opacity != o) { //DEBUG_PRINTF_P(PSTR("- Starting opacity transition: %d\n"), o); - startTransition(strip.getTransition()); // start transition prior to change + startTransition(strip.getTransition(), blendingStyle != BLEND_STYLE_FADE); // start transition prior to change opacity = o; stateChanged = true; // send UDP/WS broadcast } @@ -597,11 +501,13 @@ Segment &Segment::setOpacity(uint8_t o) { } Segment &Segment::setOption(uint8_t n, bool val) { - bool prevOn = on; - if (n == SEG_OPTION_ON && val != prevOn) startTransition(strip.getTransition()); // start transition prior to change + bool prev = (options >> n) & 0x01; + if (val == prev) return *this; + //DEBUG_PRINTF_P(PSTR("- Starting option transition: %d\n"), n); + if (n == SEG_OPTION_ON) startTransition(strip.getTransition(), blendingStyle != BLEND_STYLE_FADE); // 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 + stateChanged = true; // send UDP/WS broadcast return *this; } @@ -611,10 +517,7 @@ Segment &Segment::setMode(uint8_t fx, bool loadDefaults) { if (fx >= strip.getModeCount()) fx = 0; // set solid mode // 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 + startTransition(strip.getTransition(), true); // set effect transitions (must create segment copy) mode = fx; int sOpt; // load default values from effect string @@ -633,10 +536,10 @@ Segment &Segment::setMode(uint8_t fx, bool loadDefaults) { sOpt = extractModeDefaults(fx, "mi"); if (sOpt >= 0) mirror = (bool)sOpt; // NOTE: setting this option is a risky business sOpt = extractModeDefaults(fx, "rY"); if (sOpt >= 0) reverse_y = (bool)sOpt; sOpt = extractModeDefaults(fx, "mY"); if (sOpt >= 0) mirror_y = (bool)sOpt; // NOTE: setting this option is a risky business - sOpt = extractModeDefaults(fx, "pal"); if (sOpt >= 0) setPalette(sOpt); //else setPalette(0); } sOpt = extractModeDefaults(fx, "pal"); // always extract 'pal' to set _default_palette - if(sOpt <= 0) sOpt = 6; // partycolors if zero or not set + if (sOpt >= 0 && loadDefaults) setPalette(sOpt); + if (sOpt <= 0) sOpt = 6; // partycolors if zero or not set _default_palette = sOpt; // _deault_palette is loaded into pal0 in loadPalette() (if selected) markForReset(); stateChanged = true; // send UDP/WS broadcast @@ -646,10 +549,10 @@ Segment &Segment::setMode(uint8_t fx, bool loadDefaults) { Segment &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 > 245 && (customPalettes.size() == 0 || 255U-pal > customPalettes.size()-1)) pal = 0; // custom palettes if (pal != palette) { //DEBUG_PRINTF_P(PSTR("- Starting palette transition: %d\n"), pal); - startTransition(strip.getTransition()); + startTransition(strip.getTransition(), blendingStyle != BLEND_STYLE_FADE); // start transition prior to change (no need to copy segment) palette = pal; stateChanged = true; // send UDP/WS broadcast } @@ -660,8 +563,8 @@ Segment &Segment::setName(const char *newName) { if (newName) { const int newLen = min(strlen(newName), (size_t)WLED_MAX_SEGNAME_LEN); if (newLen) { - if (name) name = static_cast(realloc(name, newLen+1)); - else name = static_cast(malloc(newLen+1)); + if (name) name = static_cast(d_realloc(name, newLen+1)); + else name = static_cast(d_malloc(newLen+1)); if (name) strlcpy(name, newName, newLen+1); name[newLen] = 0; return *this; @@ -690,7 +593,7 @@ unsigned Segment::virtualHeight() const { constexpr int Fixed_Scale = 16384; // fixpoint scaling factor (14bit for fraction) // Pinwheel helper function: matrix dimensions to number of rays static int getPinwheelLength(int vW, int vH) { - // Returns multiple of 8, prevents over drawing + // Returns multiple of 8, prevents over drawing return (max(vW, vH) + 15) & ~7; } static void setPinwheelParameters(int i, int vW, int vH, int& startx, int& starty, int* cosVal, int* sinVal, bool getPixel = false) { @@ -705,7 +608,7 @@ static void setPinwheelParameters(int i, int vW, int vH, int& startx, int& start sinVal[k] = (sin16_t(angle) * Fixed_Scale) >> 15; // using explicit bit shifts as dividing negative numbers is not equivalent (rounding error is acceptable) } startx = (vW * Fixed_Scale) / 2; // + cosVal[0] / 4; // starting position = center + 1/4 pixel (in fixed point) - starty = (vH * Fixed_Scale) / 2; // + sinVal[0] / 4; + starty = (vH * Fixed_Scale) / 2; // + sinVal[0] / 4; } #endif @@ -742,13 +645,10 @@ uint16_t Segment::virtualLength() const { return vLength; } -// pixel is clipped if it falls outside clipping range (_modeBlend==true) or is inside clipping range (_modeBlend==false) +// pixel is clipped if it falls outside clipping range // if clipping start > stop the clipping range is inverted -// _modeBlend==true -> old effect during transition -// _modeBlend==false -> new effect during transition bool IRAM_ATTR_YN Segment::isPixelClipped(int i) const { -#ifndef WLED_DISABLE_MODE_BLEND - if (_clipStart != _clipStop && blendingStyle > BLEND_STYLE_FADE) { + if (blendingStyle != BLEND_STYLE_FADE && isInTransition() && _clipStart != _clipStop) { bool invert = _clipStart > _clipStop; // ineverted start & stop int start = invert ? _clipStop : _clipStart; int stop = invert ? _clipStart : _clipStop; @@ -757,15 +657,11 @@ bool IRAM_ATTR_YN Segment::isPixelClipped(int i) const { if (len < 2) return false; unsigned shuffled = hashInt(i) % len; unsigned pos = (shuffled * 0xFFFFU) / len; - return (progress() <= pos) ^ _modeBlend; + return progress() <= pos; } const bool iInside = (i >= start && i < stop); - //if (!invert && iInside) return _modeBlend; - //if ( invert && !iInside) return _modeBlend; - //return !_modeBlend; - return !iInside ^ invert ^ _modeBlend; // thanks @willmmiles (https://github.com/wled-dev/WLED/pull/3877#discussion_r1554633876) + return !iInside ^ invert; // thanks @willmmiles (https://github.com/wled/WLED/pull/3877#discussion_r1554633876) } -#endif return false; } @@ -775,7 +671,7 @@ void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col) const #ifndef WLED_DISABLE_2D int vStrip = 0; #endif - int vL = vLength(); + const 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 >= vL) { @@ -793,23 +689,21 @@ void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col) const 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, _segBri); - _colorScaled = true; + const auto XY = [&](unsigned x, unsigned y){ return x + y*vW;}; switch (map1D2D) { case M12_Pixels: // use all available pixels as a long strip - setPixelColorXY(i % vW, i / vW, col); + setPixelColorRaw(XY(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); - else for (int x = 0; x < vW; x++) setPixelColorXY(x, vH - i - 1, col); + if (vStrip > 0) setPixelColorRaw(XY(vStrip - 1, vH - i - 1), col); + else for (int x = 0; x < vW; x++) setPixelColorRaw(XY(x, vH - i - 1), col); break; case M12_pArc: // expand in circular fashion from center if (i == 0) - setPixelColorXY(0, 0, col); + setPixelColorRaw(XY(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 @@ -837,106 +731,106 @@ void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col) const } 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++) setPixelColorRaw(XY(x, i), col); + for (int y = 0; y < i; y++) setPixelColorRaw(XY(i, y), col); break; - case M12_sPinwheel: { - // Uses Bresenham's algorithm to place coordinates of two lines in arrays then draws between them - int startX, startY, cosVal[2], sinVal[2]; // in fixed point scale - setPinwheelParameters(i, vW, vH, startX, startY, cosVal, sinVal); - - unsigned maxLineLength = max(vW, vH) + 2; // pixels drawn is always smaller than dx or dy, +1 pair for rounding errors - uint16_t lineCoords[2][maxLineLength]; // uint16_t to save ram - int lineLength[2] = {0}; - - static int prevRays[2] = {INT_MAX, INT_MAX}; // previous two ray numbers - int closestEdgeIdx = INT_MAX; // index of the closest edge pixel - - for (int lineNr = 0; lineNr < 2; lineNr++) { - int x0 = startX; // x, y coordinates in fixed scale - int y0 = startY; - int x1 = (startX + (cosVal[lineNr] << 9)); // outside of grid - int y1 = (startY + (sinVal[lineNr] << 9)); // outside of grid - const int dx = abs(x1-x0), sx = x0= vW || unsigned(y0) >= vH) { - closestEdgeIdx = min(closestEdgeIdx, idx-2); - break; // stop if outside of grid (exploit unsigned int overflow) - } - coordinates[idx++] = x0; - coordinates[idx++] = y0; - (*length)++; - // note: since endpoint is out of grid, no need to check if endpoint is reached - int e2 = 2 * err; - if (e2 >= dy) { err += dy; x0 += sx; } - if (e2 <= dx) { err += dx; y0 += sy; } + case M12_sPinwheel: { + // Uses Bresenham's algorithm to place coordinates of two lines in arrays then draws between them + int startX, startY, cosVal[2], sinVal[2]; // in fixed point scale + setPinwheelParameters(i, vW, vH, startX, startY, cosVal, sinVal); + + unsigned maxLineLength = max(vW, vH) + 2; // pixels drawn is always smaller than dx or dy, +1 pair for rounding errors + uint16_t lineCoords[2][maxLineLength]; // uint16_t to save ram + int lineLength[2] = {0}; + + static int prevRays[2] = {INT_MAX, INT_MAX}; // previous two ray numbers + int closestEdgeIdx = INT_MAX; // index of the closest edge pixel + + for (int lineNr = 0; lineNr < 2; lineNr++) { + int x0 = startX; // x, y coordinates in fixed scale + int y0 = startY; + int x1 = (startX + (cosVal[lineNr] << 9)); // outside of grid + int y1 = (startY + (sinVal[lineNr] << 9)); // outside of grid + const int dx = abs(x1-x0), sx = x0= (unsigned)vW || (unsigned)y0 >= (unsigned)vH) { + closestEdgeIdx = min(closestEdgeIdx, idx-2); + break; // stop if outside of grid (exploit unsigned int overflow) } + coordinates[idx++] = x0; + coordinates[idx++] = y0; + (*length)++; + // note: since endpoint is out of grid, no need to check if endpoint is reached + int e2 = 2 * err; + if (e2 >= dy) { err += dy; x0 += sx; } + if (e2 <= dx) { err += dx; y0 += sy; } } - - // fill up the shorter line with missing coordinates, so block filling works correctly and efficiently - int diff = lineLength[0] - lineLength[1]; - int longLineIdx = (diff > 0) ? 0 : 1; - int shortLineIdx = longLineIdx ? 0 : 1; - if (diff != 0) { - int idx = (lineLength[shortLineIdx] - 1) * 2; // last valid coordinate index - int lastX = lineCoords[shortLineIdx][idx++]; - int lastY = lineCoords[shortLineIdx][idx++]; - bool keepX = lastX == 0 || lastX == vW - 1; - for (int d = 0; d < abs(diff); d++) { - lineCoords[shortLineIdx][idx] = keepX ? lastX :lineCoords[longLineIdx][idx]; - idx++; - lineCoords[shortLineIdx][idx] = keepX ? lineCoords[longLineIdx][idx] : lastY; - idx++; - } + } + + // fill up the shorter line with missing coordinates, so block filling works correctly and efficiently + int diff = lineLength[0] - lineLength[1]; + int longLineIdx = (diff > 0) ? 0 : 1; + int shortLineIdx = longLineIdx ? 0 : 1; + if (diff != 0) { + int idx = (lineLength[shortLineIdx] - 1) * 2; // last valid coordinate index + int lastX = lineCoords[shortLineIdx][idx++]; + int lastY = lineCoords[shortLineIdx][idx++]; + bool keepX = lastX == 0 || lastX == vW - 1; + for (int d = 0; d < abs(diff); d++) { + lineCoords[shortLineIdx][idx] = keepX ? lastX :lineCoords[longLineIdx][idx]; + idx++; + lineCoords[shortLineIdx][idx] = keepX ? lineCoords[longLineIdx][idx] : lastY; + idx++; } - - // draw and block-fill the line coordinates. Note: block filling only efficient if angle between lines is small - closestEdgeIdx += 2; - int max_i = getPinwheelLength(vW, vH) - 1; - bool drawFirst = !(prevRays[0] == i - 1 || (i == 0 && prevRays[0] == max_i)); // draw first line if previous ray was not adjacent including wrap - bool drawLast = !(prevRays[0] == i + 1 || (i == max_i && prevRays[0] == 0)); // same as above for last line - for (int idx = 0; idx < lineLength[longLineIdx] * 2;) { //!! should be long line idx! - int x1 = lineCoords[0][idx]; - int x2 = lineCoords[1][idx++]; - int y1 = lineCoords[0][idx]; - int y2 = lineCoords[1][idx++]; - int minX, maxX, minY, maxY; - (x1 < x2) ? (minX = x1, maxX = x2) : (minX = x2, maxX = x1); - (y1 < y2) ? (minY = y1, maxY = y2) : (minY = y2, maxY = y1); - - // fill the block between the two x,y points - bool alwaysDraw = (drawFirst && drawLast) || // No adjacent rays, draw all pixels - (idx > closestEdgeIdx) || // Edge pixels on uneven lines are always drawn - (i == 0 && idx == 2) || // Center pixel special case - (i == prevRays[1]); // Effect drawing twice in 1 frame - for (int x = minX; x <= maxX; x++) { - for (int y = minY; y <= maxY; y++) { - bool onLine1 = x == x1 && y == y1; - bool onLine2 = x == x2 && y == y2; - if ((alwaysDraw) || - (!onLine1 && (!onLine2 || drawLast)) || // Middle pixels and line2 if drawLast - (!onLine2 && (!onLine1 || drawFirst)) // Middle pixels and line1 if drawFirst - ) { - setPixelColorXY(x, y, col); - } + } + + // draw and block-fill the line coordinates. Note: block filling only efficient if angle between lines is small + closestEdgeIdx += 2; + int max_i = getPinwheelLength(vW, vH) - 1; + bool drawFirst = !(prevRays[0] == i - 1 || (i == 0 && prevRays[0] == max_i)); // draw first line if previous ray was not adjacent including wrap + bool drawLast = !(prevRays[0] == i + 1 || (i == max_i && prevRays[0] == 0)); // same as above for last line + for (int idx = 0; idx < lineLength[longLineIdx] * 2;) { //!! should be long line idx! + int x1 = lineCoords[0][idx]; + int x2 = lineCoords[1][idx++]; + int y1 = lineCoords[0][idx]; + int y2 = lineCoords[1][idx++]; + int minX, maxX, minY, maxY; + (x1 < x2) ? (minX = x1, maxX = x2) : (minX = x2, maxX = x1); + (y1 < y2) ? (minY = y1, maxY = y2) : (minY = y2, maxY = y1); + + // fill the block between the two x,y points + bool alwaysDraw = (drawFirst && drawLast) || // No adjacent rays, draw all pixels + (idx > closestEdgeIdx) || // Edge pixels on uneven lines are always drawn + (i == 0 && idx == 2) || // Center pixel special case + (i == prevRays[1]); // Effect drawing twice in 1 frame + for (int x = minX; x <= maxX; x++) { + for (int y = minY; y <= maxY; y++) { + bool onLine1 = x == x1 && y == y1; + bool onLine2 = x == x2 && y == y2; + if ((alwaysDraw) || + (!onLine1 && (!onLine2 || drawLast)) || // Middle pixels and line2 if drawLast + (!onLine2 && (!onLine1 || drawFirst)) // Middle pixels and line1 if drawFirst + ) { + setPixelColorXY(x, y, col); } } } - prevRays[1] = prevRays[0]; - prevRays[0] = i; - break; } + prevRays[1] = prevRays[0]; + prevRays[0] = i; + break; } - return; + } + return; } 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) @@ -948,58 +842,7 @@ void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col) const } } #endif - -#ifndef WLED_DISABLE_MODE_BLEND - // 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; - else i += dI; - } -#endif - - if (i >= vL || i < 0 || isPixelClipped(i)) return; // handle clipping on 1D - - unsigned len = length(); - // 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? - if (mirror) { // is segment mirrored? - i = (len - 1) / 2 - i; //only need to index half the pixels - } else { - i = (len - 1) - i; - } - } - i += start; // starting pixel in a group - - uint32_t tmpCol = col; - // set all the pixels in the group - for (int j = 0; j < grouping; j++) { - unsigned indexSet = i + ((reverse) ? -j : j); - if (indexSet >= start && indexSet < stop) { - if (mirror) { //set the corresponding mirrored pixel - unsigned indexMir = stop - indexSet + start - 1; - indexMir += offset; // offset/phase - if (indexMir >= stop) indexMir -= len; // wrap -#ifndef WLED_DISABLE_MODE_BLEND - // _modeBlend==true -> old effect - if (_modeBlend && blendingStyle == BLEND_STYLE_FADE) tmpCol = color_blend16(strip.getPixelColor(indexMir), col, 0xFFFFU - progress()); -#endif - strip.setPixelColor(indexMir, tmpCol); - } - indexSet += offset; // offset/phase - if (indexSet >= stop) indexSet -= len; // wrap -#ifndef WLED_DISABLE_MODE_BLEND - // _modeBlend==true -> old effect - if (_modeBlend && blendingStyle == BLEND_STYLE_FADE) tmpCol = color_blend16(strip.getPixelColor(indexSet), col, 0xFFFFU - progress()); -#endif - strip.setPixelColor(indexSet, tmpCol); - } - } + setPixelColorRaw(i, col); } #ifdef WLED_USE_AA_PIXELS @@ -1039,36 +882,42 @@ void Segment::setPixelColor(float i, uint32_t col, bool aa) const uint32_t IRAM_ATTR_YN Segment::getPixelColor(int i) const { - if (!isActive()) return 0; // not active + if (!isActive() || i < 0) return 0; // not active or invalid index - int vL = vLength(); - if (i >= vL || i < 0) return 0; +#ifndef WLED_DISABLE_2D + int vStrip = i>>16; // virtual strips are only relevant in Bar expansion mode + i &= 0xFFFF; +#endif + if (i >= (int)vLength()) return 0; #ifndef WLED_DISABLE_2D 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) + int x = 0, y = 0; switch (map1D2D) { case M12_Pixels: - return getPixelColorXY(i % vW, i / vW); + x = i % vW; + y = i / vW; + break; + case M12_pBar: + if (vStrip > 0) { x = vStrip - 1; y = vH - i - 1; } + else { y = 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 = sqrt32_bw(i*i/2); - return getPixelColorXY(vI,vI); // use diagonal + if (i > vW && i > vH) { + x = y = sqrt32_bw(i*i/2); + break; // use diagonal } + // otherwise fallthrough case M12_pCorner: // use longest dimension - return vW>vH ? getPixelColorXY(i, 0) : getPixelColorXY(0, i); + if (vW > vH) x = i; + else y = i; break; - case M12_sPinwheel: + case M12_sPinwheel: { // not 100% accurate, returns pixel at outer edge - int x, y, cosVal[2], sinVal[2]; + int cosVal[2], sinVal[2]; setPinwheelParameters(i, vW, vH, x, y, cosVal, sinVal, true); int maxX = (vW-1) * Fixed_Scale; int maxY = (vH-1) * Fixed_Scale; @@ -1079,140 +928,56 @@ uint32_t IRAM_ATTR_YN Segment::getPixelColor(int i) const } x /= Fixed_Scale; y /= Fixed_Scale; - return getPixelColorXY(x, y); break; } - return 0; - } -#endif - -#ifndef WLED_DISABLE_MODE_BLEND - 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; - else i += dI; + } + return getPixelColorXY(x, y); } #endif + return getPixelColorRaw(i); +} - 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 - i += offset; - if (i >= stop) i -= length(); - return strip.getPixelColor(i); -} - -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; - if (offset != b.offset) d |= SEG_DIFFERS_GSO; - if (grouping != b.grouping) d |= SEG_DIFFERS_GSO; - if (spacing != b.spacing) d |= SEG_DIFFERS_GSO; - if (opacity != b.opacity) d |= SEG_DIFFERS_BRI; - if (mode != b.mode) d |= SEG_DIFFERS_FX; - if (speed != b.speed) d |= SEG_DIFFERS_FX; - if (intensity != b.intensity) d |= SEG_DIFFERS_FX; - if (palette != b.palette) d |= SEG_DIFFERS_FX; - if (custom1 != b.custom1) d |= SEG_DIFFERS_FX; - if (custom2 != b.custom2) d |= SEG_DIFFERS_FX; - if (custom3 != b.custom3) d |= SEG_DIFFERS_FX; - if (startY != b.startY) d |= SEG_DIFFERS_BOUNDS; - if (stopY != b.stopY) d |= SEG_DIFFERS_BOUNDS; - - //bit pattern: (msb first) - // set:2, sound:2, mapping:3, transposed, mirrorY, reverseY, [reset,] paused, mirrored, on, reverse, [selected] - if ((options & 0b1111111111011110U) != (b.options & 0b1111111111011110U)) d |= SEG_DIFFERS_OPT; - if ((options & 0x0001U) != (b.options & 0x0001U)) d |= SEG_DIFFERS_SEL; - for (unsigned i = 0; i < NUM_COLORS; i++) if (colors[i] != b.colors[i]) d |= SEG_DIFFERS_COL; - - return d; -} - -void Segment::refreshLightCapabilities() { +void Segment::refreshLightCapabilities() const { unsigned capabilities = 0; - unsigned segStartIdx = 0xFFFFU; - unsigned segStopIdx = 0; if (!isActive()) { _capabilities = 0; return; } - if (start < Segment::maxWidth * Segment::maxHeight) { - // we are withing 2D matrix (includes 1D segments) - for (int y = startY; y < stopY; y++) for (int x = start; x < stop; x++) { - unsigned index = strip.getMappedPixelIndex(x + Segment::maxWidth * y); // convert logical address to physical - if (index < 0xFFFFU) { - if (segStartIdx > index) segStartIdx = index; - if (segStopIdx < index) segStopIdx = index; + // we must traverse each pixel in segment to determine its capabilities (as pixel may be mapped) + for (unsigned y = startY; y < stopY; y++) for (unsigned x = start; x < stop; x++) { + unsigned index = x + Segment::maxWidth * y; + index = strip.getMappedPixelIndex(index); // convert logical address to physical + if (index == 0xFFFF) continue; // invalid/missing pixel + for (unsigned b = 0; b < BusManager::getNumBusses(); b++) { + const Bus *bus = BusManager::getBus(b); + if (!bus || !bus->isOk()) break; + if (bus->containsPixel(index)) { + if (bus->hasRGB() || (strip.cctFromRgb && bus->hasCCT())) capabilities |= SEG_CAPABILITY_RGB; + if (!strip.cctFromRgb && bus->hasCCT()) capabilities |= SEG_CAPABILITY_CCT; + if (strip.correctWB && (bus->hasRGB() || bus->hasCCT())) capabilities |= SEG_CAPABILITY_CCT; //white balance correction (CCT slider) + if (bus->hasWhite()) { + unsigned aWM = Bus::getGlobalAWMode() == AW_GLOBAL_DISABLED ? bus->getAutoWhiteMode() : Bus::getGlobalAWMode(); + bool whiteSlider = (aWM == RGBW_MODE_DUAL || aWM == RGBW_MODE_MANUAL_ONLY); // white slider allowed + // if auto white calculation from RGB is active (Accurate/Brighter), force RGB controls even if there are no RGB busses + if (!whiteSlider) capabilities |= SEG_CAPABILITY_RGB; + // if auto white calculation from RGB is disabled/optional (None/Dual), allow white channel adjustments + if ( whiteSlider) capabilities |= SEG_CAPABILITY_W; + } + break; } - if (segStartIdx == segStopIdx) segStopIdx++; // we only have 1 pixel segment - } - } else { - // we are on the strip located after the matrix - segStartIdx = start; - segStopIdx = stop; - } - - for (unsigned b = 0; b < BusManager::getNumBusses(); b++) { - const Bus *bus = BusManager::getBus(b); - if (!bus || !bus->isOk()) break; - if (bus->getStart() >= segStopIdx || bus->getStart() + bus->getLength() <= segStartIdx) continue; - if (bus->hasRGB() || (strip.cctFromRgb && bus->hasCCT())) capabilities |= SEG_CAPABILITY_RGB; - if (!strip.cctFromRgb && bus->hasCCT()) capabilities |= SEG_CAPABILITY_CCT; - if (strip.correctWB && (bus->hasRGB() || bus->hasCCT())) capabilities |= SEG_CAPABILITY_CCT; //white balance correction (CCT slider) - if (bus->hasWhite()) { - unsigned aWM = Bus::getGlobalAWMode() == AW_GLOBAL_DISABLED ? bus->getAutoWhiteMode() : Bus::getGlobalAWMode(); - bool whiteSlider = (aWM == RGBW_MODE_DUAL || aWM == RGBW_MODE_MANUAL_ONLY); // white slider allowed - // if auto white calculation from RGB is active (Accurate/Brighter), force RGB controls even if there are no RGB busses - if (!whiteSlider) capabilities |= SEG_CAPABILITY_RGB; - // if auto white calculation from RGB is disabled/optional (None/Dual), allow white channel adjustments - if ( whiteSlider) capabilities |= SEG_CAPABILITY_W; } } _capabilities = capabilities; } -/* - * Fills segment with black - */ -void Segment::clear() { - if (!isActive()) return; // not active - unsigned oldVW = _vWidth; - unsigned oldVH = _vHeight; - unsigned oldVL = _vLength; - unsigned oldSB = _segBri; - _vWidth = virtualWidth(); - _vHeight = virtualHeight(); - _vLength = virtualLength(); - _segBri = currentBri(); - fill(BLACK); - _vWidth = oldVW; - _vHeight = oldVH; - _vLength = oldVL; - _segBri = oldSB; -} - /* * Fills segment with color */ -void Segment::fill(uint32_t c) { +void Segment::fill(uint32_t c) const { 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; + for (unsigned i = 0; i < length(); i++) setPixelColorRaw(i,c); // always fill all pixels (blending will take care of grouping, spacing and clipping) } /* @@ -1220,16 +985,12 @@ void Segment::fill(uint32_t c) { * fading is highly dependant on frame rate (higher frame rates, faster fading) * each frame will fade at max 9% or as little as 0.8% */ -void Segment::fade_out(uint8_t rate) { +void Segment::fade_out(uint8_t rate) const { if (!isActive()) return; // not active - const int cols = is2D() ? vWidth() : vLength(); - const int rows = vHeight(); // will be 1 for 1D - rate = (256-rate) >> 1; const int mappedRate = 256 / (rate + 1); - - for (int y = 0; y < rows; y++) for (int x = 0; x < cols; x++) { - uint32_t color = is2D() ? getPixelColorXY(x, y) : getPixelColor(x); + for (unsigned j = 0; j < vLength(); j++) { + uint32_t color = getPixelColorRaw(j); if (color == colors[1]) continue; // already at target color for (int i = 0; i < 32; i += 8) { uint8_t c2 = (colors[1]>>i); // get background channel @@ -1242,40 +1003,27 @@ void Segment::fade_out(uint8_t rate) { color &= ~(0xFF< 215 this function does not work properly (creates alternating pattern) */ -void Segment::blur(uint8_t blur_amount, bool smear) { +void Segment::blur(uint8_t blur_amount, bool smear) const { if (!isActive() || blur_amount == 0) return; // optimization: 0 means "don't blur" #ifndef WLED_DISABLE_2D if (is2D()) { @@ -1289,24 +1037,24 @@ void Segment::blur(uint8_t blur_amount, bool smear) { uint8_t seep = blur_amount >> 1; unsigned vlength = vLength(); uint32_t carryover = BLACK; - uint32_t lastnew; // not necessary to initialize lastnew and last, as both will be initialized by the first loop iteration + uint32_t lastnew; // not necessary to initialize lastnew and last, as both will be initialized by the first loop iteration uint32_t last; uint32_t curnew = BLACK; for (unsigned i = 0; i < vlength; i++) { - uint32_t cur = getPixelColor(i); + uint32_t cur = getPixelColorRaw(i); uint32_t part = color_fade(cur, seep); curnew = color_fade(cur, keep); if (i > 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) setPixelColor(i - 1, prev); - } else setPixelColor(i, curnew); // first pixel + if (last != prev) setPixelColorRaw(i - 1, prev); + } else setPixelColorRaw(i, curnew); // first pixel lastnew = curnew; last = cur; // save original value for comparison on next iteration carryover = part; } - setPixelColor(vlength - 1, curnew); + setPixelColorRaw(vlength - 1, curnew); } /* @@ -1315,17 +1063,23 @@ void Segment::blur(uint8_t blur_amount, bool smear) { * Inspired by the Adafruit examples. */ 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" + if (palette) return color_from_palette(pos, false, false, 0); // never wrap palette uint8_t w = W(getCurrentColor(0)); pos = 255 - pos; - if (pos < 85) { - return RGBW32((255 - pos * 3), 0, (pos * 3), w); - } else if (pos < 170) { - pos -= 85; - return RGBW32(0, (pos * 3), (255 - pos * 3), w); + if (useRainbowWheel) { + CRGB rgb; + hsv2rgb_rainbow(CHSV(pos, 255, 255), rgb); + return RGBW32(rgb.r, rgb.g, rgb.b, w); } else { - pos -= 170; - return RGBW32((pos * 3), (255 - pos * 3), 0, w); + if (pos < 85) { + return RGBW32((255 - pos * 3), 0, (pos * 3), w); + } else if (pos < 170) { + pos -= 85; + return RGBW32(0, (pos * 3), (255 - pos * 3), w); + } else { + pos -= 170; + return RGBW32((pos * 3), (255 - pos * 3), 0, w); + } } } @@ -1339,19 +1093,18 @@ 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 moving, uint8_t mcol, uint8_t pbri) const { - uint32_t color = getCurrentColor(mcol < NUM_COLORS ? mcol : 0); + uint32_t color = getCurrentColor(mcol); // default palette or no RGB support on segment if ((palette == 0 && mcol < NUM_COLORS) || !_isRGB) { return color_fade(color, pbri, true); } - const int vL = vLength(); unsigned paletteIndex = i; - if (mapping && vL > 1) paletteIndex = (i*255)/(vL -1); + if (mapping) paletteIndex = min((i*255)/vLength(), 255U); // paletteBlend: 0 - wrap when moving, 1 - always wrap, 2 - never wrap, 3 - none (undefined/no interpolation of palette entries) // ColorFromPalette interpolations are: NOBLEND, LINEARBLEND, LINEARBLEND_NOWRAP TBlendType blend = NOBLEND; - switch (strip.paletteBlend) { // NOTE: paletteBlend should be global + switch (paletteBlend) { case 0: blend = moving ? LINEARBLEND : LINEARBLEND_NOWRAP; break; case 1: blend = LINEARBLEND; break; case 2: blend = LINEARBLEND_NOWRAP; break; @@ -1379,6 +1132,7 @@ void WS2812FX::finalizeInit() { enumerateLedmaps(); _hasWhiteChannel = _isOffRefreshRequired = false; + BusManager::removeAll(); unsigned digitalCount = 0; #if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3) @@ -1408,94 +1162,8 @@ void WS2812FX::finalizeInit() { busConfigs.clear(); busConfigs.shrink_to_fit(); - //if busses failed to load, add default (fresh install, FS issue, ...) - if (BusManager::getNumBusses() == 0) { - DEBUG_PRINTLN(F("No busses, init default")); - constexpr unsigned defDataTypes[] = {LED_TYPES}; - constexpr unsigned defDataPins[] = {DATA_PINS}; - constexpr unsigned defCounts[] = {PIXEL_COUNTS}; - constexpr unsigned defNumTypes = ((sizeof defDataTypes) / (sizeof defDataTypes[0])); - constexpr unsigned defNumPins = ((sizeof defDataPins) / (sizeof defDataPins[0])); - constexpr unsigned defNumCounts = ((sizeof defCounts) / (sizeof defCounts[0])); - - static_assert(validatePinsAndTypes(defDataTypes, defNumTypes, defNumPins), - "The default pin list defined in DATA_PINS does not match the pin requirements for the default buses defined in LED_TYPES"); - - unsigned prevLen = 0; - unsigned pinsIndex = 0; - digitalCount = 0; - for (unsigned i = 0; i < WLED_MAX_BUSSES+WLED_MIN_VIRTUAL_BUSSES; i++) { - uint8_t defPin[OUTPUT_MAX_PINS]; - // if we have less types than requested outputs and they do not align, use last known type to set current type - unsigned dataType = defDataTypes[(i < defNumTypes) ? i : defNumTypes -1]; - unsigned busPins = Bus::getNumberOfPins(dataType); - - // if we need more pins than available all outputs have been configured - if (pinsIndex + busPins > defNumPins) break; - - // Assign all pins first so we can check for conflicts on this bus - for (unsigned j = 0; j < busPins && j < OUTPUT_MAX_PINS; j++) defPin[j] = defDataPins[pinsIndex + j]; - - for (unsigned j = 0; j < busPins && j < OUTPUT_MAX_PINS; j++) { - bool validPin = true; - // When booting without config (1st boot) we need to make sure GPIOs defined for LED output don't clash with hardware - // i.e. DEBUG (GPIO1), DMX (2), SPI RAM/FLASH (16&17 on ESP32-WROVER/PICO), read/only pins, etc. - // Pin should not be already allocated, read/only or defined for current bus - while (PinManager::isPinAllocated(defPin[j]) || !PinManager::isPinOk(defPin[j],true)) { - if (validPin) { - DEBUG_PRINTLN(F("Some of the provided pins cannot be used to configure this LED output.")); - defPin[j] = 1; // start with GPIO1 and work upwards - validPin = false; - } else if (defPin[j] < WLED_NUM_PINS) { - defPin[j]++; - } else { - DEBUG_PRINTLN(F("No available pins left! Can't configure output.")); - return; - } - // is the newly assigned pin already defined or used previously? - // try next in line until there are no clashes or we run out of pins - bool clash; - do { - clash = false; - // check for conflicts on current bus - for (const auto &pin : defPin) { - if (&pin != &defPin[j] && pin == defPin[j]) { - clash = true; - break; - } - } - // We already have a clash on current bus, no point checking next buses - if (!clash) { - // check for conflicts in defined pins - for (const auto &pin : defDataPins) { - if (pin == defPin[j]) { - clash = true; - break; - } - } - } - if (clash) defPin[j]++; - if (defPin[j] >= WLED_NUM_PINS) break; - } while (clash); - } - } - pinsIndex += busPins; - - unsigned start = prevLen; - // if we have less counts than pins and they do not align, use last known count to set current count - unsigned count = defCounts[(i < defNumCounts) ? i : defNumCounts -1]; - // analog always has length 1 - if (Bus::isPWM(dataType) || Bus::isOnOff(dataType)) count = 1; - prevLen += count; - BusConfig defCfg = BusConfig(dataType, defPin, start, count, DEFAULT_LED_COLOR_ORDER, false, 0, RGBW_MODE_MANUAL_ONLY, 0, useGlobalLedBuffer); - mem += defCfg.memUsage(Bus::isDigital(dataType) && !Bus::is2Pin(dataType) ? digitalCount++ : 0); - if (BusManager::add(defCfg) == -1) break; - } - } - DEBUG_PRINTF_P(PSTR("LED buffer size: %uB/%uB\n"), mem, BusManager::memUsage()); - _length = 0; - for (int i=0; iisOk() || bus->getStart() + bus->getLength() > MAX_LEDS) break; //RGBW mode is enabled if at least one of the strips is RGBW @@ -1504,7 +1172,6 @@ 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; - // This must be done after all buses have been created, as some kinds (parallel I2S) interact bus->begin(); bus->setBrightness(bri); @@ -1519,17 +1186,22 @@ void WS2812FX::finalizeInit() { loadCustomPalettes(); // (re)load all custom palettes DEBUG_PRINTLN(F("Loading custom ledmaps")); deserializeMap(); // (re)load default ledmap (will also setUpMatrix() if ledmap does not exist) + + // allocate frame buffer after matrix has been set up (gaps!) + if (_pixels) _pixels = static_cast(d_realloc(_pixels, getLengthTotal() * sizeof(uint32_t))); + else _pixels = static_cast(d_malloc(getLengthTotal() * sizeof(uint32_t))); + DEBUG_PRINTF_P(PSTR("strip buffer size: %uB\n"), getLengthTotal() * sizeof(uint32_t)); + + DEBUG_PRINTF_P(PSTR("Heap after strip init: %uB\n"), ESP.getFreeHeap()); } void WS2812FX::service() { unsigned long nowUp = millis(); // Be aware, millis() rolls over every 49 days now = nowUp + timebase; - if (_suspend) return; unsigned long elapsed = nowUp - _lastServiceShow; - - 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 + if (_suspend || 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 } bool doShow = false; @@ -1537,10 +1209,10 @@ void WS2812FX::service() { _isServicing = true; _segment_index = 0; - for (segment &seg : _segments) { + for (Segment &seg : _segments) { if (_suspend) break; // immediately stop processing segments if suspend requested during service() - // process transition (mode changes in the middle of transition) + // process transition (also pre-calculates progress value) seg.handleTransition(); // reset the segment runtime data if needed seg.resetIfRequired(); @@ -1548,7 +1220,7 @@ 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 frameDelay = FRAMETIME; @@ -1558,154 +1230,392 @@ void WS2812FX::service() { // 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); - else BusManager::setSegmentCCT(seg.currentBri(true), correctWB); + else BusManager::setSegmentCCT(seg.currentCCT(), correctWB); // Effect blending - // When two effects are being blended, each may have different segment data, this - // data needs to be saved first and then restored before running previous mode. - // 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. - seg.beginDraw(); // set up parameters for get/setPixelColor() -#ifndef WLED_DISABLE_MODE_BLEND - Segment::setClippingRect(0, 0); // disable clipping (just in case) - if (seg.isInTransition()) { - // a hack to determine if effect has changed - uint8_t m = seg.currentMode(); - Segment::modeBlend(true); // set semaphore - bool sameEffect = (m == seg.currentMode()); - Segment::modeBlend(false); // clear semaphore - // set clipping rectangle - // new mode is run inside clipping area and old mode outside clipping area - unsigned p = seg.progress(); - unsigned w = seg.is2D() ? Segment::vWidth() : Segment::vLength(); - unsigned h = Segment::vHeight(); - unsigned dw = p * w / 0xFFFFU + 1; - unsigned dh = p * h / 0xFFFFU + 1; - unsigned orgBS = blendingStyle; - if (w*h == 1) blendingStyle = BLEND_STYLE_FADE; // disable style for single pixel segments (use fade instead) - else if (sameEffect && (blendingStyle & BLEND_STYLE_PUSH_MASK)) { - // when effect stays the same push will look awful, change it to swipe - switch (blendingStyle) { - case BLEND_STYLE_PUSH_BR: - case BLEND_STYLE_PUSH_TR: - case BLEND_STYLE_PUSH_RIGHT: blendingStyle = BLEND_STYLE_SWIPE_RIGHT; break; - case BLEND_STYLE_PUSH_BL: - case BLEND_STYLE_PUSH_TL: - case BLEND_STYLE_PUSH_LEFT: blendingStyle = BLEND_STYLE_SWIPE_LEFT; break; - case BLEND_STYLE_PUSH_DOWN: blendingStyle = BLEND_STYLE_SWIPE_DOWN; break; - case BLEND_STYLE_PUSH_UP: blendingStyle = BLEND_STYLE_SWIPE_UP; break; - } - } - switch (blendingStyle) { - 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_PUSH_LEFT: // right-to-left - Segment::setClippingRect(w - dw, w, 0, h); - break; - 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 - 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_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_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 - Segment::setClippingRect((w - dw)/2, (w + dw)/2, 0, h); - break; - 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) - Segment::setClippingRect(0, dw, 0, dh); - break; - 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) - Segment::setClippingRect(w - dw, w, h - dh, h); - break; - case BLEND_STYLE_PUSH_BL: // BL-to-TR (2D) - Segment::setClippingRect(0, dw, h - dh, h); - break; - } - frameDelay = (*_mode[m])(); // 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) - seg.beginDraw(); // set up parameters for get/setPixelColor() - frameDelay = min(frameDelay, (unsigned)(*_mode[seg.currentMode()])()); // run old mode - seg.call++; // increment old mode run counter - seg.restoreSegenv(_tmpSegData); // restore mode state (will also update transitional state) - Segment::modeBlend(false); // unset semaphore - blendingStyle = orgBS; // restore blending style if it was modified for single pixel segment - } else -#endif - frameDelay = (*_mode[seg.mode])(); // run effect mode (not in transition) + uint16_t prog = seg.progress(); + seg.beginDraw(prog); // set up parameters for get/setPixelColor() (will also blend colors and palette if blend style is FADE) + _currentSegment = &seg; // set current segment for effect functions (SEGMENT & SEGENV) + // workaround for on/off transition to respect blending style + frameDelay = (*_mode[seg.mode])(); // run new/current mode (needed for bri workaround) seg.call++; + // if segment is in transition and no old segment exists we don't need to run the old mode + // (blendSegments() takes care of On/Off transitions and clipping) + Segment *segO = seg.getOldSegment(); + if (segO && (seg.mode != segO->mode || blendingStyle != BLEND_STYLE_FADE)) { + Segment::modeBlend(true); // set semaphore for beginDraw() to blend colors and palette + segO->beginDraw(prog); // set up palette & colors (also sets draw dimensions), parent segment has transition progress + _currentSegment = segO; // set current segment + // workaround for on/off transition to respect blending style + frameDelay = min(frameDelay, (unsigned)(*_mode[segO->mode])()); // run old mode (needed for bri workaround; semaphore!!) + segO->call++; // increment old mode run counter + Segment::modeBlend(false); // unset semaphore + } if (seg.isInTransition() && frameDelay > FRAMETIME) frameDelay = 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 + frameDelay; } _segment_index++; } - Segment::setClippingRect(0, 0); // disable clipping for overlays - _isServicing = false; - _triggered = false; #ifdef WLED_DEBUG if ((_targetFps != FPS_UNLIMITED) && (millis() - nowUp > _frametime)) DEBUG_PRINTF_P(PSTR("Slow effects %u/%d.\n"), (unsigned)(millis()-nowUp), (int)_frametime); #endif - if (doShow) { + if (doShow && !_suspend) { yield(); Segment::handleRandomPalette(); // slowly transition random palette; move it into for loop when each segment has individual random palette _lastServiceShow = nowUp; // update timestamp, for precise FPS control - if (!_suspend) show(); + show(); } #ifdef WLED_DEBUG if ((_targetFps != FPS_UNLIMITED) && (millis() - nowUp > _frametime)) DEBUG_PRINTF_P(PSTR("Slow strip %u/%d.\n"), (unsigned)(millis()-nowUp), (int)_frametime); #endif + + _triggered = false; + _isServicing = false; } -void IRAM_ATTR WS2812FX::setPixelColor(unsigned i, uint32_t col) const { - i = getMappedPixelIndex(i); - if (i >= _length) return; - BusManager::setPixelColor(i, col); +// https://en.wikipedia.org/wiki/Blend_modes but using a for top layer & b for bottom layer +static uint8_t _top (uint8_t a, uint8_t b) { return a; } +static uint8_t _bottom (uint8_t a, uint8_t b) { return b; } +static uint8_t _add (uint8_t a, uint8_t b) { unsigned t = a + b; return t > 255 ? 255 : t; } +static uint8_t _subtract (uint8_t a, uint8_t b) { return b > a ? (b - a) : 0; } +static uint8_t _difference(uint8_t a, uint8_t b) { return b > a ? (b - a) : (a - b); } +static uint8_t _average (uint8_t a, uint8_t b) { return (a + b) >> 1; } +#ifdef CONFIG_IDF_TARGET_ESP32C3 +static uint8_t _multiply (uint8_t a, uint8_t b) { return ((a * b) + 255) >> 8; } // faster than division on C3 but slightly less accurate +#else +static uint8_t _multiply (uint8_t a, uint8_t b) { return (a * b) / 255; } // origianl uses a & b in range [0,1] +#endif +static uint8_t _divide (uint8_t a, uint8_t b) { return a > b ? (b * 255) / a : 255; } +static uint8_t _lighten (uint8_t a, uint8_t b) { return a > b ? a : b; } +static uint8_t _darken (uint8_t a, uint8_t b) { return a < b ? a : b; } +static uint8_t _screen (uint8_t a, uint8_t b) { return 255 - _multiply(~a,~b); } // 255 - (255-a)*(255-b)/255 +static uint8_t _overlay (uint8_t a, uint8_t b) { return b < 128 ? 2 * _multiply(a,b) : (255 - 2 * _multiply(~a,~b)); } +static uint8_t _hardlight (uint8_t a, uint8_t b) { return a < 128 ? 2 * _multiply(a,b) : (255 - 2 * _multiply(~a,~b)); } +#ifdef CONFIG_IDF_TARGET_ESP32C3 +static uint8_t _softlight (uint8_t a, uint8_t b) { return (((b * b * (255 - 2 * a) + 255) >> 8) + 2 * a * b + 255) >> 8; } // Pegtop's formula (1 - 2a)b^2 + 2ab +#else +static uint8_t _softlight (uint8_t a, uint8_t b) { return (b * b * (255 - 2 * a) / 255 + 2 * a * b) / 255; } // Pegtop's formula (1 - 2a)b^2 + 2ab +#endif +static uint8_t _dodge (uint8_t a, uint8_t b) { return _divide(~a,b); } +static uint8_t _burn (uint8_t a, uint8_t b) { return ~_divide(a,~b); } + +void WS2812FX::blendSegment(const Segment &topSegment) const { + + typedef uint8_t(*FuncType)(uint8_t, uint8_t); + FuncType funcs[] = { + _top, _bottom, + _add, _subtract, _difference, _average, + _multiply, _divide, _lighten, _darken, _screen, _overlay, + _hardlight, _softlight, _dodge, _burn + }; + + const size_t blendMode = topSegment.blendMode < (sizeof(funcs) / sizeof(FuncType)) ? topSegment.blendMode : 0; + const auto func = funcs[blendMode]; // blendMode % (sizeof(funcs) / sizeof(FuncType)) + const auto blend = [&](uint32_t top, uint32_t bottom){ return RGBW32(func(R(top),R(bottom)), func(G(top),G(bottom)), func(B(top),B(bottom)), func(W(top),W(bottom))); }; + + const int length = topSegment.length(); // physical segment length (counts all pixels in 2D segment) + const int width = topSegment.width(); + const int height = topSegment.height(); + const auto XY = [](int x, int y){ return x + y*Segment::maxWidth; }; + const size_t matrixSize = Segment::maxWidth * Segment::maxHeight; + const size_t startIndx = XY(topSegment.start, topSegment.startY); + const size_t stopIndx = startIndx + length; + const unsigned progress = topSegment.progress(); + const unsigned progInv = 0xFFFFU - progress; + uint8_t opacity = topSegment.currentBri(); // returns transitioned opacity for style FADE + + Segment::setClippingRect(0, 0); // disable clipping by default + + const unsigned dw = (blendingStyle==BLEND_STYLE_OUTSIDE_IN ? progInv : progress) * width / 0xFFFFU + 1; + const unsigned dh = (blendingStyle==BLEND_STYLE_OUTSIDE_IN ? progInv : progress) * height / 0xFFFFU + 1; + const unsigned orgBS = blendingStyle; + if (width*height == 1) blendingStyle = BLEND_STYLE_FADE; // disable style for single pixel segments (use fade instead) + switch (blendingStyle) { + case BLEND_STYLE_CIRCULAR_IN: // (must set entire segment, see isPixelXYClipped()) + case BLEND_STYLE_CIRCULAR_OUT:// (must set entire segment, see isPixelXYClipped()) + case BLEND_STYLE_FAIRY_DUST: // fairy dust (must set entire segment, see isPixelXYClipped()) + Segment::setClippingRect(0, width, 0, height); + break; + case BLEND_STYLE_SWIPE_RIGHT: // left-to-right + case BLEND_STYLE_PUSH_RIGHT: // left-to-right + Segment::setClippingRect(0, dw, 0, height); + break; + case BLEND_STYLE_SWIPE_LEFT: // right-to-left + case BLEND_STYLE_PUSH_LEFT: // right-to-left + Segment::setClippingRect(width - dw, width, 0, height); + break; + case BLEND_STYLE_OUTSIDE_IN: // corners + Segment::setClippingRect((width + dw)/2, (width - dw)/2, (height + dh)/2, (height - dh)/2); // inverted!! + break; + case BLEND_STYLE_INSIDE_OUT: // outward + Segment::setClippingRect((width - dw)/2, (width + dw)/2, (height - dh)/2, (height + dh)/2); + break; + case BLEND_STYLE_SWIPE_DOWN: // top-to-bottom (2D) + case BLEND_STYLE_PUSH_DOWN: // top-to-bottom (2D) + Segment::setClippingRect(0, width, 0, dh); + break; + case BLEND_STYLE_SWIPE_UP: // bottom-to-top (2D) + case BLEND_STYLE_PUSH_UP: // bottom-to-top (2D) + Segment::setClippingRect(0, width, height - dh, height); + break; + case BLEND_STYLE_OPEN_H: // horizontal-outward (2D) same look as INSIDE_OUT on 1D + Segment::setClippingRect((width - dw)/2, (width + dw)/2, 0, height); + break; + case BLEND_STYLE_OPEN_V: // vertical-outward (2D) + Segment::setClippingRect(0, width, (height - dh)/2, (height + dh)/2); + break; + case BLEND_STYLE_SWIPE_TL: // TL-to-BR (2D) + case BLEND_STYLE_PUSH_TL: // TL-to-BR (2D) + Segment::setClippingRect(0, dw, 0, dh); + break; + case BLEND_STYLE_SWIPE_TR: // TR-to-BL (2D) + case BLEND_STYLE_PUSH_TR: // TR-to-BL (2D) + Segment::setClippingRect(width - dw, width, 0, dh); + break; + case BLEND_STYLE_SWIPE_BR: // BR-to-TL (2D) + case BLEND_STYLE_PUSH_BR: // BR-to-TL (2D) + Segment::setClippingRect(width - dw, width, height - dh, height); + break; + case BLEND_STYLE_SWIPE_BL: // BL-to-TR (2D) + case BLEND_STYLE_PUSH_BL: // BL-to-TR (2D) + Segment::setClippingRect(0, dw, height - dh, height); + break; + } + + if (isMatrix && stopIndx <= matrixSize) { +#ifndef WLED_DISABLE_2D + const int nCols = topSegment.virtualWidth(); + const int nRows = topSegment.virtualHeight(); + const Segment *segO = topSegment.getOldSegment(); + const int oCols = segO ? segO->virtualWidth() : nCols; + const int oRows = segO ? segO->virtualHeight() : nRows; + + const auto setMirroredPixel = [&](int x, int y, uint32_t c, uint8_t o) { + const int baseX = topSegment.start + x; + const int baseY = topSegment.startY + y; + size_t indx = XY(baseX, baseY); // absolute address on strip + _pixels[indx] = color_blend(_pixels[indx], blend(c, _pixels[indx]), o); + // Apply mirroring + if (topSegment.mirror || topSegment.mirror_y) { + const int mirrorX = topSegment.start + width - x - 1; + const int mirrorY = topSegment.startY + height - y - 1; + const size_t idxMX = XY(topSegment.transpose ? baseX : mirrorX, topSegment.transpose ? mirrorY : baseY); + const size_t idxMY = XY(topSegment.transpose ? mirrorX : baseX, topSegment.transpose ? baseY : mirrorY); + const size_t idxMM = XY(mirrorX, mirrorY); + if (topSegment.mirror) _pixels[idxMX] = color_blend(_pixels[idxMX], blend(c, _pixels[idxMX]), o); + if (topSegment.mirror_y) _pixels[idxMY] = color_blend(_pixels[idxMY], blend(c, _pixels[idxMY]), o); + if (topSegment.mirror && topSegment.mirror_y) _pixels[idxMM] = color_blend(_pixels[idxMM], blend(c, _pixels[idxMM]), o); + } + }; + + // if we blend using "push" style we need to "shift" canvas to left/right/up/down + unsigned offsetX = (blendingStyle == BLEND_STYLE_PUSH_UP || blendingStyle == BLEND_STYLE_PUSH_DOWN) ? 0 : progInv * nCols / 0xFFFFU; + unsigned offsetY = (blendingStyle == BLEND_STYLE_PUSH_LEFT || blendingStyle == BLEND_STYLE_PUSH_RIGHT) ? 0 : progInv * nRows / 0xFFFFU; + + // we only traverse new segment, not old one + for (int r = 0; r < nRows; r++) for (int c = 0; c < nCols; c++) { + const bool clipped = topSegment.isPixelXYClipped(c, r); + // if segment is in transition and pixel is clipped take old segment's pixel and opacity + const Segment *seg = clipped && segO ? segO : &topSegment; // pixel is never clipped for FADE + int vCols = seg == segO ? oCols : nCols; // old segment may have different dimensions + int vRows = seg == segO ? oRows : nRows; // old segment may have different dimensions + int x = c; + int y = r; + // if we blend using "push" style we need to "shift" canvas to left/right/up/down + switch (blendingStyle) { + case BLEND_STYLE_PUSH_RIGHT: x = (x + offsetX) % nCols; break; + case BLEND_STYLE_PUSH_LEFT: x = (x - offsetX + nCols) % nCols; break; + case BLEND_STYLE_PUSH_DOWN: y = (y + offsetY) % nRows; break; + case BLEND_STYLE_PUSH_UP: y = (y - offsetY + nRows) % nRows; break; + } + uint32_t c_a = BLACK; + if (x < vCols && y < vRows) c_a = seg->getPixelColorRaw(x + y*vCols); // will get clipped pixel from old segment or unclipped pixel from new segment + if (segO && blendingStyle == BLEND_STYLE_FADE && topSegment.mode != segO->mode && x < oCols && y < oRows) { + // we need to blend old segment using fade as pixels ae not clipped + c_a = color_blend16(c_a, segO->getPixelColorRaw(x + y*oCols), progInv); + } else if (blendingStyle != BLEND_STYLE_FADE) { + // workaround for On/Off transition + // (bri != briT) && !bri => from On to Off + // (bri != briT) && bri => from Off to On + if ((!clipped && (bri != briT) && !bri) || (clipped && (bri != briT) && bri)) c_a = BLACK; + } + // map it into frame buffer + x = c; // restore coordiates if we were PUSHing + y = r; + if (topSegment.reverse ) x = nCols - x - 1; + if (topSegment.reverse_y) y = nRows - y - 1; + if (topSegment.transpose) std::swap(x,y); // swap X & Y if segment transposed + // expand pixel + const unsigned groupLen = topSegment.groupLength(); + if (groupLen == 1) { + setMirroredPixel(x, y, c_a, opacity); + } else { + // handle grouping and spacing + x *= groupLen; // expand to physical pixels + y *= groupLen; // expand to physical pixels + const int maxX = std::min(x + topSegment.grouping, width); + const int maxY = std::min(y + topSegment.grouping, height); + while (y < maxY) { + while (x < maxX) setMirroredPixel(x++, y, c_a, opacity); + y++; + } + } + } +#endif + } else { + const int nLen = topSegment.virtualLength(); + const Segment *segO = topSegment.getOldSegment(); + const int oLen = segO ? segO->virtualLength() : nLen; + + const auto setMirroredPixel = [&](int i, uint32_t c, uint8_t o) { + int indx = topSegment.start + i; + // Apply mirroring + if (topSegment.mirror) { + unsigned indxM = topSegment.stop - i - 1; + indxM += topSegment.offset; // offset/phase + if (indxM >= topSegment.stop) indxM -= length; // wrap + _pixels[indxM] = color_blend(_pixels[indxM], blend(c, _pixels[indxM]), o); + } + indx += topSegment.offset; // offset/phase + if (indx >= topSegment.stop) indx -= length; // wrap + _pixels[indx] = color_blend(_pixels[indx], blend(c, _pixels[indx]), o); + }; + + // if we blend using "push" style we need to "shift" canvas to left/right/ + unsigned offsetI = progInv * nLen / 0xFFFFU; + + for (int k = 0; k < nLen; k++) { + const bool clipped = topSegment.isPixelClipped(k); + // if segment is in transition and pixel is clipped take old segment's pixel and opacity + const Segment *seg = clipped && segO ? segO : &topSegment; // pixel is never clipped for FADE + const int vLen = seg == segO ? oLen : nLen; + int i = k; + // if we blend using "push" style we need to "shift" canvas to left or right + switch (blendingStyle) { + case BLEND_STYLE_PUSH_RIGHT: i = (i + offsetI) % nLen; break; + case BLEND_STYLE_PUSH_LEFT: i = (i - offsetI + nLen) % nLen; break; + } + uint32_t c_a = BLACK; + if (i < vLen) c_a = seg->getPixelColorRaw(i); // will get clipped pixel from old segment or unclipped pixel from new segment + if (segO && blendingStyle == BLEND_STYLE_FADE && topSegment.mode != segO->mode && i < oLen) { + // we need to blend old segment using fade as pixels are not clipped + c_a = color_blend16(c_a, segO->getPixelColorRaw(i), progInv); + } else if (blendingStyle != BLEND_STYLE_FADE) { + // workaround for On/Off transition + // (bri != briT) && !bri => from On to Off + // (bri != briT) && bri => from Off to On + if ((!clipped && (bri != briT) && !bri) || (clipped && (bri != briT) && bri)) c_a = BLACK; + } + // map into frame buffer + i = k; // restore index if we were PUSHing + if (topSegment.reverse) i = nLen - i - 1; // is segment reversed? + // expand pixel + i *= topSegment.groupLength(); + // set all the pixels in the group + const int maxI = std::min(i + topSegment.grouping, length); // make sure to not go beyond physical length + while (i < maxI) setMirroredPixel(i++, c_a, opacity); + } + } + + blendingStyle = orgBS; + Segment::setClippingRect(0, 0); // disable clipping for overlays } -uint32_t IRAM_ATTR WS2812FX::getPixelColor(unsigned i) const { - i = getMappedPixelIndex(i); - if (i >= _length) return 0; - return BusManager::getPixelColor(i); +// To disable brightness limiter we either set output max current to 0 or single LED current to 0 +static uint8_t estimateCurrentAndLimitBri(uint8_t brightness, uint32_t *pixels) { + unsigned milliAmpsMax = BusManager::ablMilliampsMax(); + if (milliAmpsMax > 0) { + unsigned milliAmpsTotal = 0; + unsigned avgMilliAmpsPerLED = 0; + unsigned lengthDigital = 0; + bool useWackyWS2815PowerModel = false; + + for (size_t i = 0; i < BusManager::getNumBusses(); i++) { + const Bus *bus = BusManager::getBus(i); + if (!(bus && bus->isDigital() && bus->isOk())) continue; + unsigned maPL = bus->getLEDCurrent(); + if (maPL == 0 || bus->getMaxCurrent() > 0) continue; // skip buses with 0 mA per LED or max current per bus defined (PP-ABL) + if (maPL == 255) { + useWackyWS2815PowerModel = true; + maPL = 12; // WS2815 uses 12mA per channel + } + avgMilliAmpsPerLED += maPL * bus->getLength(); + lengthDigital += bus->getLength(); + // sum up the usage of each LED on digital bus + uint32_t busPowerSum = 0; + for (unsigned j = 0; j < bus->getLength(); j++) { + uint32_t c = pixels[j + bus->getStart()]; + byte r = R(c), g = G(c), b = B(c), w = W(c); + if (useWackyWS2815PowerModel) { //ignore white component on WS2815 power calculation + busPowerSum += (max(max(r,g),b)) * 3; + } else { + busPowerSum += (r + g + b + w); + } + } + // RGBW led total output with white LEDs enabled is still 50mA, so each channel uses less + if (bus->hasWhite()) { + busPowerSum *= 3; + busPowerSum >>= 2; //same as /= 4 + } + // powerSum has all the values of channels summed (max would be getLength()*765 as white is excluded) so convert to milliAmps + milliAmpsTotal += (busPowerSum * maPL * brightness) / (765*255); + } + if (lengthDigital > 0) { + avgMilliAmpsPerLED /= lengthDigital; + + if (milliAmpsMax > MA_FOR_ESP && avgMilliAmpsPerLED > 0) { //0 mA per LED and too low numbers turn off calculation + unsigned powerBudget = (milliAmpsMax - MA_FOR_ESP); //80/120mA for ESP power + if (powerBudget > lengthDigital) { //each LED uses about 1mA in standby, exclude that from power budget + powerBudget -= lengthDigital; + } else { + powerBudget = 0; + } + if (milliAmpsTotal > powerBudget) { + //scale brightness down to stay in current limit + unsigned scaleB = powerBudget * 255 / milliAmpsTotal; + brightness = ((brightness * scaleB) >> 8) + 1; + } + } + } + } + return brightness; } void WS2812FX::show() { + unsigned long showNow = millis(); + size_t diff = showNow - _lastShow; + + size_t totalLen = getLengthTotal(); + if (realtimeMode == REALTIME_MODE_INACTIVE || useMainSegmentOnly || realtimeOverride > REALTIME_OVERRIDE_NONE) { + // clear frame buffer + for (size_t i = 0; i < totalLen; i++) _pixels[i] = BLACK; // memset(_pixels, 0, sizeof(uint32_t) * getLengthTotal()); + // blend all segments into (cleared) buffer + for (Segment &seg : _segments) if (seg.isActive() && (seg.on || seg.isInTransition())) { + blendSegment(seg); // blend segment's buffer into frame buffer + } + } + // avoid race condition, capture _callback value show_callback callback = _callback; - if (callback) callback(); - unsigned long showNow = millis(); + if (callback) callback(); // will call setPixelColor or setRealtimePixelColor + + // determine ABL brightness + uint8_t newBri = estimateCurrentAndLimitBri(_brightness, _pixels); + if (newBri != _brightness) BusManager::setBrightness(newBri); + + // paint actuall pixels + for (size_t i = 0; i < totalLen; i++) BusManager::setPixelColor(getMappedPixelIndex(i), realtimeMode && arlsDisableGammaCorrection ? _pixels[i] : gamma32(_pixels[i])); // 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(); - size_t diff = showNow - _lastShow; + // restore brightness for next frame + if (newBri != _brightness) BusManager::setBrightness(_brightness); if (diff > 0) { // skip calculation if no time has passed size_t fpsCurr = (1000 << FPS_CALC_SHIFT) / diff; // fixed point math @@ -1714,6 +1624,40 @@ void WS2812FX::show() { } } +void WS2812FX::setRealtimePixelColor(unsigned i, uint32_t c) { + if (useMainSegmentOnly) { + const Segment &seg = getMainSegment(); + if (seg.isActive() && i < seg.length()) seg.setPixelColorRaw(i, c); + } else { + setPixelColor(i, c); + } +} + +// reset all segments +void WS2812FX::restartRuntime() { + suspend(); + waitForIt(); + for (Segment &seg : _segments) seg.markForReset().resetIfRequired(); + resume(); +} + +// start or stop transition for all segments +void WS2812FX::setTransitionMode(bool t) { + suspend(); + waitForIt(); + for (Segment &seg : _segments) seg.startTransition(t ? _transitionDur : 0); + resume(); +} + +// wait until frame is over (service() has finished or time for 1 frame has passed; yield() crashes on 8266) +void WS2812FX::waitForIt() { + unsigned long maxWait = millis() + getFrameTime(); + while (isServicing() && maxWait > millis()) delay(1); + #ifdef WLED_DEBUG + if (millis() >= maxWait) DEBUG_PRINTLN(F("Waited for strip to finish servicing.")); + #endif +}; + void WS2812FX::setTargetFps(unsigned fps) { if (fps <= 250) _targetFps = fps; if (_targetFps > 0) _frametime = 1000 / _targetFps; @@ -1721,7 +1665,7 @@ void WS2812FX::setTargetFps(unsigned fps) { } void WS2812FX::setCCT(uint16_t k) { - for (segment &seg : _segments) { + for (Segment &seg : _segments) { if (seg.isActive() && seg.isSelected()) { seg.setCCT(k); } @@ -1735,22 +1679,18 @@ void WS2812FX::setBrightness(uint8_t b, bool direct) { if (_brightness == b) return; _brightness = b; if (_brightness == 0) { //unfreeze all segments on power off - for (segment &seg : _segments) { - seg.freeze = false; - } + for (const Segment &seg : _segments) seg.freeze = false; // freeze is mutable } - // setting brightness with NeoPixelBusLg has no effect on already painted pixels, - // so we need to force an update to existing buffer BusManager::setBrightness(b); if (!direct) { unsigned long t = millis(); - if (_segments[0].next_time > t + 22 && t - _lastShow > MIN_FRAME_DELAY) trigger(); //apply brightness change immediately if no refresh soon + if (_segments[0].next_time > t + 22 && t - _lastShow > MIN_SHOW_DELAY) trigger(); //apply brightness change immediately if no refresh soon } } uint8_t WS2812FX::getActiveSegsLightCapabilities(bool selectedOnly) const { uint8_t totalLC = 0; - for (const segment &seg : _segments) { + for (const Segment &seg : _segments) { if (seg.isActive() && (!selectedOnly || seg.isSelected())) totalLC |= seg.getLightCapabilities(); } return totalLC; @@ -1758,7 +1698,7 @@ uint8_t WS2812FX::getActiveSegsLightCapabilities(bool selectedOnly) const { uint8_t WS2812FX::getFirstSelectedSegId() const { size_t i = 0; - for (const segment &seg : _segments) { + for (const Segment &seg : _segments) { if (seg.isActive() && seg.isSelected()) return i; i++; } @@ -1767,8 +1707,8 @@ uint8_t WS2812FX::getFirstSelectedSegId() const { } void WS2812FX::setMainSegmentId(unsigned n) { - _mainSegment = 0; - if (n < _segments.size()) { + _mainSegment = getLastActiveSegmentId(); + if (n < _segments.size() && _segments[n].isActive()) { // only set if segment is active _mainSegment = n; } return; @@ -1782,10 +1722,8 @@ uint8_t WS2812FX::getLastActiveSegmentId() const { } uint8_t WS2812FX::getActiveSegmentsNum() const { - uint8_t c = 0; - for (size_t i = 0; i < _segments.size(); i++) { - if (_segments[i].isActive()) c++; - } + unsigned c = 0; + for (const Segment &seg : _segments) if (seg.isActive()) c++; return c; } @@ -1796,13 +1734,7 @@ uint16_t WS2812FX::getLengthTotal() const { } uint16_t WS2812FX::getLengthPhysical() const { - unsigned len = 0; - for (size_t b = 0; b < BusManager::getNumBusses(); b++) { - Bus *bus = BusManager::getBus(b); - if (bus->isVirtual()) continue; //exclude non-physical network busses - len += bus->getLength(); - } - return len; + return BusManager::getTotalLength(true); } //used for JSON API info.leds.rgbw. Little practical use, deprecate with info.leds.rgbw. @@ -1847,14 +1779,9 @@ Segment& WS2812FX::getSegment(unsigned id) { } void WS2812FX::resetSegments() { - _segments.clear(); // destructs all Segment as part of clearing - #ifndef WLED_DISABLE_2D - segment seg = isMatrix ? Segment(0, Segment::maxWidth, 0, Segment::maxHeight) : Segment(0, _length); - #else - segment seg = Segment(0, _length); - #endif - _segments.push_back(seg); - _segments.shrink_to_fit(); // just in case ... + _segments.clear(); // destructs all Segment as part of clearing + _segments.emplace_back(0, isMatrix ? Segment::maxWidth : _length, 0, isMatrix ? Segment::maxHeight : 1); + _segments.shrink_to_fit(); // just in case ... _mainSegment = 0; } @@ -1902,12 +1829,12 @@ void WS2812FX::makeAutoSegments(bool forceReset) { // there is always at least one segment (but we need to differentiate between 1D and 2D) #ifndef WLED_DISABLE_2D if (isMatrix) - _segments.push_back(Segment(0, Segment::maxWidth, 0, Segment::maxHeight)); + _segments.emplace_back(0, Segment::maxWidth, 0, Segment::maxHeight); else #endif - _segments.push_back(Segment(segStarts[0], segStops[0])); + _segments.emplace_back(segStarts[0], segStops[0]); for (size_t i = 1; i < s; i++) { - _segments.push_back(Segment(segStarts[i], segStops[i])); + _segments.emplace_back(segStarts[i], segStops[i]); } DEBUG_PRINTF_P(PSTR("%d auto segments created.\n"), _segments.size()); @@ -1918,15 +1845,9 @@ void WS2812FX::makeAutoSegments(bool forceReset) { else if (getActiveSegmentsNum() == 1) { size_t i = getLastActiveSegmentId(); #ifndef WLED_DISABLE_2D - _segments[i].start = 0; - _segments[i].stop = Segment::maxWidth; - _segments[i].startY = 0; - _segments[i].stopY = Segment::maxHeight; - _segments[i].grouping = 1; - _segments[i].spacing = 0; + _segments[i].setGeometry(0, Segment::maxWidth, 1, 0, 0xFFFF, 0, Segment::maxHeight); #else - _segments[i].start = 0; - _segments[i].stop = _length; + _segments[i].setGeometry(0, _length); #endif } } @@ -1958,7 +1879,7 @@ void WS2812FX::fixInvalidSegments() { // if any segments were deleted free memory purgeSegments(); // this is always called as the last step after finalizeInit(), update covered bus types - for (segment &seg : _segments) + for (const Segment &seg : _segments) seg.refreshLightCapabilities(); } @@ -1966,7 +1887,7 @@ void WS2812FX::fixInvalidSegments() { //irrelevant in 2D set-up bool WS2812FX::checkSegmentAlignment() const { bool aligned = false; - for (const segment &seg : _segments) { + for (const Segment &seg : _segments) { for (unsigned b = 0; bisOk()) break; @@ -1996,59 +1917,9 @@ void WS2812FX::printSize() { } #endif -void WS2812FX::loadCustomPalettes() { - byte tcp[72]; //support gradient palettes with up to 18 entries - CRGBPalette16 targetPalette; - customPalettes.clear(); // start fresh - for (int index = 0; index<10; index++) { - char fileName[32]; - sprintf_P(fileName, PSTR("/palette%d.json"), index); - - StaticJsonDocument<1536> pDoc; // barely enough to fit 72 numbers - if (WLED_FS.exists(fileName)) { - DEBUG_PRINT(F("Reading palette from ")); - DEBUG_PRINTLN(fileName); - - if (readObjectFromFile(fileName, nullptr, &pDoc)) { - JsonArray pal = pDoc[F("palette")]; - if (!pal.isNull() && pal.size()>3) { // not an empty palette (at least 2 entries) - if (pal[0].is() && pal[1].is()) { - // we have an array of index & hex strings - size_t palSize = MIN(pal.size(), 36); - palSize -= palSize % 2; // make sure size is multiple of 2 - for (size_t i=0, j=0; i()<256; i+=2, j+=4) { - uint8_t rgbw[] = {0,0,0,0}; - tcp[ j ] = (uint8_t) pal[ i ].as(); // index - colorFromHexString(rgbw, pal[i+1].as()); // will catch non-string entires - for (size_t c=0; c<3; c++) tcp[j+1+c] = gamma8(rgbw[c]); // only use RGB component - DEBUG_PRINTF_P(PSTR("%d(%d) : %d %d %d\n"), i, int(tcp[j]), int(tcp[j+1]), int(tcp[j+2]), int(tcp[j+3])); - } - } else { - size_t palSize = MIN(pal.size(), 72); - palSize -= palSize % 4; // make sure size is multiple of 4 - for (size_t i=0; i()<256; i+=4) { - tcp[ i ] = (uint8_t) pal[ i ].as(); // index - tcp[i+1] = gamma8((uint8_t) pal[i+1].as()); // R - tcp[i+2] = gamma8((uint8_t) pal[i+2].as()); // G - tcp[i+3] = gamma8((uint8_t) pal[i+3].as()); // B - DEBUG_PRINTF_P(PSTR("%d(%d) : %d %d %d\n"), i, int(tcp[i]), int(tcp[i+1]), int(tcp[i+2]), int(tcp[i+3])); - } - } - customPalettes.push_back(targetPalette.loadDynamicGradientPalette(tcp)); - } else { - DEBUG_PRINTLN(F("Wrong palette format.")); - } - } - } else { - break; - } - } -} - -//load custom mapping table from JSON file (called from finalizeInit() or deserializeState()) +// load custom mapping table from JSON file (called from finalizeInit() or deserializeState()) +// if this is a matrix set-up and default ledmap.json file does not exist, create mapping table using setUpMatrix() from panel information 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]; strcpy_P(fileName, PSTR("/ledmap")); if (n) sprintf(fileName +7, "%d", n); @@ -2060,6 +1931,7 @@ bool WS2812FX::deserializeMap(unsigned n) { if (n == 0 || isFile) interfaceUpdateCallMode = CALL_MODE_WS_SEND; // schedule WS update (to inform UI) if (!isFile && n==0 && isMatrix) { + // 2D panel support creates its own ledmap (on the fly) if a ledmap.json does not exist setUpMatrix(); return false; } @@ -2070,25 +1942,28 @@ bool WS2812FX::deserializeMap(unsigned n) { filter[F("width")] = true; filter[F("height")] = true; if (!readObjectFromFile(fileName, nullptr, pDoc, &filter)) { - DEBUG_PRINT(F("ERROR Invalid ledmap in ")); DEBUG_PRINTLN(fileName); + DEBUG_PRINTF_P(PSTR("ERROR Invalid ledmap in %s\n"), fileName); releaseJSONBufferLock(); return false; // if file does not load properly then exit - } + } else + DEBUG_PRINTF_P(PSTR("Reading LED map from %s\n"), fileName); suspend(); + waitForIt(); JsonObject root = pDoc->as(); // if we are loading default ledmap (at boot) set matrix width and height from the ledmap (compatible with WLED MM ledmaps) - if (isMatrix && n == 0 && (!root[F("width")].isNull() || !root[F("height")].isNull())) { - Segment::maxWidth = min(max(root[F("width")].as(), 1), 128); - Segment::maxHeight = min(max(root[F("height")].as(), 1), 128); + if (n == 0 && (!root[F("width")].isNull() || !root[F("height")].isNull())) { + Segment::maxWidth = min(max(root[F("width")].as(), 1), 255); + Segment::maxHeight = min(max(root[F("height")].as(), 1), 255); + isMatrix = true; } - if (customMappingTable) free(customMappingTable); - customMappingTable = static_cast(malloc(sizeof(uint16_t)*getLengthTotal())); + d_free(customMappingTable); + customMappingTable = static_cast(d_malloc(sizeof(uint16_t)*getLengthTotal())); // do not use SPI RAM if (customMappingTable) { - DEBUG_PRINT(F("Reading LED map from ")); DEBUG_PRINTLN(fileName); + DEBUG_PRINTF_P(PSTR("ledmap allocated: %uB\n"), sizeof(uint16_t)*getLengthTotal()); File f = WLED_FS.open(fileName, "r"); f.find("\"map\":["); while (f.available()) { // f.position() < f.size() - 1 @@ -2140,8 +2015,6 @@ bool WS2812FX::deserializeMap(unsigned n) { } -WS2812FX* WS2812FX::instance = nullptr; - const char JSON_mode_names[] PROGMEM = R"=====(["FX names moved"])====="; const char JSON_palette_names[] PROGMEM = R"=====([ "Default","* Random Cycle","* Color 1","* Colors 1&2","* Color Gradient","* Colors Only","Party","Cloud","Lava","Ocean", @@ -2152,4 +2025,4 @@ const char JSON_palette_names[] PROGMEM = R"=====([ "Aurora","Atlantica","C9 2","C9 New","Temperature","Aurora 2","Retro Clown","Candy","Toxy Reaf","Fairy Reaf", "Semi Blue","Pink Candy","Red Reaf","Aqua Flash","Yelblu Hot","Lite Light","Red Flash","Blink Red","Red Shift","Red Tide", "Candy2","Traffic Light" -])====="; \ No newline at end of file +])====="; diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index cee34c2ea2..56e5947959 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -32,8 +32,31 @@ extern bool useParallelI2S; uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb); //udp.cpp -uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, const uint8_t* buffer, uint8_t bri=255, bool isRGBW=false); - +uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, const byte *buffer, uint8_t bri=255, bool isRGBW=false); + +//util.cpp +// PSRAM allocation wrappers +#ifndef ESP8266 +extern "C" { + void *p_malloc(size_t); // prefer PSRAM over DRAM + void *p_calloc(size_t, size_t); // prefer PSRAM over DRAM + void *p_realloc(void *, size_t); // prefer PSRAM over DRAM + inline void p_free(void *ptr) { heap_caps_free(ptr); } + void *d_malloc(size_t); // prefer DRAM over PSRAM + void *d_calloc(size_t, size_t); // prefer DRAM over PSRAM + void *d_realloc(void *, size_t); // prefer DRAM over PSRAM + inline void d_free(void *ptr) { heap_caps_free(ptr); } +} +#else +#define p_malloc malloc +#define p_calloc calloc +#define p_realloc realloc +#define p_free free +#define d_malloc malloc +#define d_calloc calloc +#define d_realloc realloc +#define d_free free +#endif //color mangling macros #define RGBW32(r,g,b,w) (uint32_t((byte(w) << 24) | (byte(r) << 16) | (byte(g) << 8) | (byte(b)))) @@ -72,7 +95,7 @@ void Bus::calculateCCT(uint32_t c, uint8_t &ww, uint8_t &cw) { } else { cct = (approximateKelvinFromRGB(c) - 1900) >> 5; // convert K (from RGB value) to relative format } - + //0 - linear (CCT 127 = 50% warm, 50% cold), 127 - additive CCT blending (CCT 127 = 100% warm, 100% cold) if (cct < _cctBlend) ww = 255; else ww = ((255-cct) * 255) / (255 - _cctBlend); @@ -106,7 +129,6 @@ BusDigital::BusDigital(const BusConfig &bc, uint8_t nr) , _colorOrder(bc.colorOrder) , _milliAmpsPerLed(bc.milliAmpsPerLed) , _milliAmpsMax(bc.milliAmpsMax) -, _data(nullptr) { DEBUGBUS_PRINTLN(F("Bus: Creating digital bus.")); if (!isDigital(bc.type) || !bc.count) { DEBUGBUS_PRINTLN(F("Not digial or empty bus!")); return; } @@ -127,10 +149,6 @@ BusDigital::BusDigital(const BusConfig &bc, uint8_t nr) _hasRgb = hasRGB(bc.type); _hasWhite = hasWhite(bc.type); _hasCCT = hasCCT(bc.type); - if (bc.doubleBuffer) { - _data = (uint8_t*)calloc(_len, Bus::getNumberOfChannels(_type)); - if (!_data) DEBUGBUS_PRINTLN(F("Bus: Buffer allocation failed!")); - } 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); @@ -213,43 +231,6 @@ void BusDigital::show() { uint8_t cctWW = 0, cctCW = 0; unsigned newBri = estimateCurrentAndLimitBri(); // will fill _milliAmpsTotal (TODO: could use PolyBus::CalcTotalMilliAmpere()) if (newBri < _bri) PolyBus::setBrightness(_busPtr, _iType, newBri); // limit brightness to stay within current limits - - if (_data) { - size_t channels = getNumberOfChannels(); - int16_t oldCCT = Bus::_cct; // temporarily save bus CCT - for (size_t i=0; i<_len; i++) { - size_t offset = i * channels; - unsigned co = _colorOrderMap.getPixelColorOrder(i+_start, _colorOrder); - uint32_t c; - if (_type == TYPE_WS2812_1CH_X3) { // map to correct IC, each controls 3 LEDs (_len is always a multiple of 3) - switch (i%3) { - case 0: c = RGBW32(_data[offset] , _data[offset+1], _data[offset+2], 0); break; - case 1: c = RGBW32(_data[offset-1], _data[offset] , _data[offset+1], 0); break; - case 2: c = RGBW32(_data[offset-2], _data[offset-1], _data[offset] , 0); break; - } - } else { - if (hasRGB()) c = RGBW32(_data[offset], _data[offset+1], _data[offset+2], hasWhite() ? _data[offset+3] : 0); - else c = RGBW32(0, 0, 0, _data[offset]); - } - if (hasCCT()) { - // 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 extract and appy CCT value for each pixel individually even though all buses share the same _cct variable - // TODO: there is an issue if CCT is calculated from RGB value (_cct==-1), we cannot do that with double buffer - Bus::_cct = _data[offset+channels-1]; - Bus::calculateCCT(c, cctWW, cctCW); - if (_type == TYPE_WS2812_WWA) c = RGBW32(cctWW, cctCW, 0, W(c)); // may need swapping - } - unsigned pix = i; - if (_reversed) pix = _len - pix -1; - pix += _skip; - PolyBus::setPixelColor(_busPtr, _iType, pix, c, co, (cctCW<<8) | cctWW); - } - #if !defined(STATUSLED) || STATUSLED>=0 - if (_skip) PolyBus::setPixelColor(_busPtr, _iType, 0, 0, _colorOrderMap.getPixelColorOrder(_start, _colorOrder)); // paint skipped pixels black - #endif - for (int i=1; i<_skip; i++) PolyBus::setPixelColor(_busPtr, _iType, i, 0, _colorOrderMap.getPixelColorOrder(_start, _colorOrder)); // paint skipped pixels black - Bus::_cct = oldCCT; - } else { if (newBri < _bri) { unsigned hwLen = _len; if (_type == TYPE_WS2812_1CH_X3) hwLen = NUM_ICS_WS2812_1CH_3X(_len); // only needs a third of "RGB" LEDs for NeoPixelBus @@ -260,8 +241,7 @@ void BusDigital::show() { PolyBus::setPixelColor(_busPtr, _iType, i, c, 0, (cctCW<<8) | cctWW); // repaint all pixels with new brightness } } - } - PolyBus::show(_busPtr, _iType, !_data); // faster if buffer consistency is not important + PolyBus::show(_busPtr, _iType, false); // faster if buffer consistency is not important // restore bus brightness to its original value // this is done right after show, so this is only OK if LED updates are completed before show() returns // or async show has a separate buffer (ESP32 RMT and I2S are ok) @@ -292,86 +272,61 @@ void IRAM_ATTR BusDigital::setPixelColor(unsigned pix, uint32_t c) { if (!_valid) return; 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()) { - *dataptr++ = R(c); - *dataptr++ = G(c); - *dataptr++ = B(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()) *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; - unsigned co = _colorOrderMap.getPixelColorOrder(pix+_start, _colorOrder); - if (_type == TYPE_WS2812_1CH_X3) { // map to correct IC, each controls 3 LEDs - unsigned pOld = pix; - pix = IC_INDEX_WS2812_1CH_3X(pix); - uint32_t cOld = restoreColorLossy(PolyBus::getPixelColor(_busPtr, _iType, pix, co),_bri); - switch (pOld % 3) { // change only the single channel (TODO: this can cause loss because of get/set) - case 0: c = RGBW32(R(cOld), W(c) , B(cOld), 0); break; - case 1: c = RGBW32(W(c) , G(cOld), B(cOld), 0); break; - case 2: c = RGBW32(R(cOld), G(cOld), W(c) , 0); break; - } + if (_reversed) pix = _len - pix -1; + pix += _skip; + unsigned co = _colorOrderMap.getPixelColorOrder(pix+_start, _colorOrder); + if (_type == TYPE_WS2812_1CH_X3) { // map to correct IC, each controls 3 LEDs + unsigned pOld = pix; + pix = IC_INDEX_WS2812_1CH_3X(pix); + uint32_t cOld = restoreColorLossy(PolyBus::getPixelColor(_busPtr, _iType, pix, co),_bri); + switch (pOld % 3) { // change only the single channel (TODO: this can cause loss because of get/set) + case 0: c = RGBW32(R(cOld), W(c) , B(cOld), 0); break; + case 1: c = RGBW32(W(c) , G(cOld), B(cOld), 0); break; + case 2: c = RGBW32(R(cOld), G(cOld), W(c) , 0); break; } - uint16_t wwcw = 0; - if (hasCCT()) { - uint8_t cctWW = 0, cctCW = 0; - Bus::calculateCCT(c, cctWW, cctCW); - wwcw = (cctCW<<8) | cctWW; - if (_type == TYPE_WS2812_WWA) c = RGBW32(cctWW, cctCW, 0, W(c)); // may need swapping - } - PolyBus::setPixelColor(_busPtr, _iType, pix, c, co, wwcw); } + uint16_t wwcw = 0; + if (hasCCT()) { + uint8_t cctWW = 0, cctCW = 0; + Bus::calculateCCT(c, cctWW, cctCW); + wwcw = (cctCW<<8) | cctWW; + if (_type == TYPE_WS2812_WWA) c = RGBW32(cctWW, cctCW, 0, W(c)); + } + PolyBus::setPixelColor(_busPtr, _iType, pix, c, co, wwcw); } // returns original color if global buffering is enabled, else returns lossly restored color from bus uint32_t IRAM_ATTR BusDigital::getPixelColor(unsigned pix) const { if (!_valid) return 0; - if (_data) { - const size_t offset = pix * getNumberOfChannels(); - uint32_t c; - if (!hasRGB()) { - c = RGBW32(_data[offset], _data[offset], _data[offset], _data[offset]); - } else { - c = RGBW32(_data[offset], _data[offset+1], _data[offset+2], hasWhite() ? _data[offset+3] : 0); - } - return c; - } else { - if (_reversed) pix = _len - pix -1; - pix += _skip; - 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); - unsigned g = _reversed ? B(c) : G(c); // should G and B be switched if _reversed? - unsigned b = _reversed ? G(c) : B(c); - switch (pix % 3) { // get only the single channel - case 0: c = RGBW32(g, g, g, g); break; - case 1: c = RGBW32(r, r, r, r); break; - case 2: c = RGBW32(b, b, b, b); break; - } + if (_reversed) pix = _len - pix -1; + pix += _skip; + 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); + unsigned g = _reversed ? B(c) : G(c); // should G and B be switched if _reversed? + unsigned b = _reversed ? G(c) : B(c); + switch (pix % 3) { // get only the single channel + case 0: c = RGBW32(g, g, g, g); break; + case 1: c = RGBW32(r, r, r, r); break; + case 2: c = RGBW32(b, b, b, b); break; } - if (_type == TYPE_WS2812_WWA) { - uint8_t w = R(c) | G(c); - c = RGBW32(w, w, 0, w); - } - return c; } + if (_type == TYPE_WS2812_WWA) { + uint8_t w = R(c) | G(c); + c = RGBW32(w, w, 0, w); + } + return c; } -unsigned BusDigital::getPins(uint8_t* pinArray) const { +size_t BusDigital::getPins(uint8_t* pinArray) const { unsigned numPins = is2Pin(_type) + 1; if (pinArray) for (unsigned i = 0; i < numPins; i++) pinArray[i] = _pins[i]; return numPins; } -unsigned BusDigital::getBusSize() const { - return sizeof(BusDigital) + (isOk() ? PolyBus::getDataSize(_busPtr, _iType) + (_data ? _len * getNumberOfChannels() : 0) : 0); +size_t BusDigital::getBusSize() const { + return sizeof(BusDigital) + (isOk() ? PolyBus::getDataSize(_busPtr, _iType) /*+ (_data ? _len * getNumberOfChannels() : 0)*/ : 0); } void BusDigital::setColorOrder(uint8_t colorOrder) { @@ -380,7 +335,7 @@ void BusDigital::setColorOrder(uint8_t colorOrder) { _colorOrder = colorOrder; } -// credit @willmmiles & @netmindz https://github.com/wled-dev/WLED/pull/4056 +// credit @willmmiles & @netmindz https://github.com/wled/WLED/pull/4056 std::vector BusDigital::getLEDTypes() { return { {TYPE_WS2812_RGB, "D", PSTR("WS281x")}, @@ -414,8 +369,6 @@ void BusDigital::begin() { void BusDigital::cleanup() { DEBUGBUS_PRINTLN(F("Digital Cleanup.")); PolyBus::cleanup(_busPtr, _iType); - free(_data); - _data = nullptr; _iType = I_NONE; _valid = false; _busPtr = nullptr; @@ -453,7 +406,7 @@ BusPwm::BusPwm(const BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWhite, 1, bc.reversed, bc.refreshReq) // hijack Off refresh flag to indicate usage of dithering { if (!isPWM(bc.type)) return; - unsigned numPins = numPWMPins(bc.type); + const unsigned numPins = numPWMPins(bc.type); [[maybe_unused]] const bool dithering = _needsRefresh; _frequency = bc.frequency ? bc.frequency : WLED_PWM_FREQ; // duty cycle resolution (_depth) can be extracted from this formula: CLOCK_FREQUENCY > _frequency * 2^_depth @@ -461,36 +414,40 @@ BusPwm::BusPwm(const BusConfig &bc) managed_pin_type pins[numPins]; for (unsigned i = 0; i < numPins; i++) pins[i] = {(int8_t)bc.pins[i], true}; - if (!PinManager::allocateMultiplePins(pins, numPins, PinOwner::BusPwm)) return; - -#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 - PinManager::deallocateMultiplePins(pins, numPins, PinOwner::BusPwm); - return; - } - // if _needsRefresh is true (UI hack) we are using dithering (credit @dedehai & @zalatnaicsongor) - if (dithering) _depth = 12; // fixed 8 bit depth PWM with 4 bit dithering (ESP8266 has no hardware to support dithering) -#endif - - for (unsigned i = 0; i < numPins; i++) { - _pins[i] = bc.pins[i]; // store only after allocateMultiplePins() succeeded + if (PinManager::allocateMultiplePins(pins, numPins, PinOwner::BusPwm)) { #ifdef ESP8266 - pinMode(_pins[i], OUTPUT); + analogWriteRange((1<<_depth)-1); + analogWriteFreq(_frequency); #else - unsigned channel = _ledcStart + i; - ledcSetup(channel, _frequency, _depth - (dithering*4)); // with dithering _frequency doesn't really matter as resolution is 8 bit - ledcAttachPin(_pins[i], channel); - // LEDC timer reset credit @dedehai - uint8_t group = (channel / 8), timer = ((channel / 2) % 4); // same fromula as in ledcSetup() - ledc_timer_rst((ledc_mode_t)group, (ledc_timer_t)timer); // reset timer so all timers are almost in sync (for phase shift) + // 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 + PinManager::deallocateMultiplePins(pins, numPins, PinOwner::BusPwm); + DEBUGBUS_PRINTLN(F("No more free LEDC channels!")); + return; + } + // if _needsRefresh is true (UI hack) we are using dithering (credit @dedehai & @zalatnaicsongor) + if (dithering) _depth = 12; // fixed 8 bit depth PWM with 4 bit dithering (ESP8266 has no hardware to support dithering) #endif + + for (unsigned i = 0; i < numPins; i++) { + _pins[i] = bc.pins[i]; // store only after allocateMultiplePins() succeeded + #ifdef ESP8266 + pinMode(_pins[i], OUTPUT); + #else + unsigned channel = _ledcStart + i; + ledcSetup(channel, _frequency, _depth - (dithering*4)); // with dithering _frequency doesn't really matter as resolution is 8 bit + ledcAttachPin(_pins[i], channel); + // LEDC timer reset credit @dedehai + uint8_t group = (channel / 8), timer = ((channel / 2) % 4); // same fromula as in ledcSetup() + ledc_timer_rst((ledc_mode_t)group, (ledc_timer_t)timer); // reset timer so all timers are almost in sync (for phase shift) + #endif + } + _hasRgb = hasRGB(bc.type); + _hasWhite = hasWhite(bc.type); + _hasCCT = hasCCT(bc.type); + _valid = true; } - _hasRgb = hasRGB(bc.type); - _hasWhite = hasWhite(bc.type); - _hasCCT = hasCCT(bc.type); - _valid = true; DEBUGBUS_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]); } @@ -561,7 +518,7 @@ void BusPwm::show() { 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/wled-dev/WLED/pull/4115 and https://github.com/zalatnaicsongor/WLED/pull/1) + // https://github.com/wled/WLED/pull/4115 and https://github.com/zalatnaicsongor/WLED/pull/1) const bool dithering = _needsRefresh; // avoid working with bitfield 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) @@ -620,14 +577,14 @@ void BusPwm::show() { } } -unsigned BusPwm::getPins(uint8_t* pinArray) const { +size_t BusPwm::getPins(uint8_t* pinArray) const { if (!_valid) return 0; unsigned numPins = numPWMPins(_type); if (pinArray) for (unsigned i = 0; i < numPins; i++) pinArray[i] = _pins[i]; return numPins; } -// credit @willmmiles & @netmindz https://github.com/wled-dev/WLED/pull/4056 +// credit @willmmiles & @netmindz https://github.com/wled/WLED/pull/4056 std::vector BusPwm::getLEDTypes() { return { {TYPE_ANALOG_1CH, "A", PSTR("PWM White")}, @@ -695,13 +652,13 @@ void BusOnOff::show() { digitalWrite(_pin, _reversed ? !(bool)_data : (bool)_data); } -unsigned BusOnOff::getPins(uint8_t* pinArray) const { +size_t BusOnOff::getPins(uint8_t* pinArray) const { if (!_valid) return 0; if (pinArray) pinArray[0] = _pin; return 1; } -// credit @willmmiles & @netmindz https://github.com/wled-dev/WLED/pull/4056 +// credit @willmmiles & @netmindz https://github.com/wled/WLED/pull/4056 std::vector BusOnOff::getLEDTypes() { return { {TYPE_ONOFF, "", PSTR("On/Off")}, @@ -731,7 +688,7 @@ BusNetwork::BusNetwork(const BusConfig &bc) _hasCCT = false; _UDPchannels = _hasWhite + 3; _client = IPAddress(bc.pins[0],bc.pins[1],bc.pins[2],bc.pins[3]); - _data = (uint8_t*)calloc(_len, _UDPchannels); + _data = (uint8_t*)d_calloc(_len, _UDPchannels); _valid = (_data != nullptr); DEBUGBUS_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]); } @@ -760,12 +717,12 @@ void BusNetwork::show() { _broadcastLock = false; } -unsigned BusNetwork::getPins(uint8_t* pinArray) const { +size_t BusNetwork::getPins(uint8_t* pinArray) const { if (pinArray) for (unsigned i = 0; i < 4; i++) pinArray[i] = _client[i]; return 4; } -// credit @willmmiles & @netmindz https://github.com/wled-dev/WLED/pull/4056 +// credit @willmmiles & @netmindz https://github.com/wled/WLED/pull/4056 std::vector BusNetwork::getLEDTypes() { return { {TYPE_NET_DDP_RGB, "N", PSTR("DDP RGB (network)")}, // should be "NNNN" to determine 4 "pin" fields @@ -776,13 +733,13 @@ std::vector BusNetwork::getLEDTypes() { //{TYPE_VIRTUAL_I2C_W, "V", PSTR("I2C White (virtual)")}, // allows setting I2C address in _pin[0] //{TYPE_VIRTUAL_I2C_CCT, "V", PSTR("I2C CCT (virtual)")}, // allows setting I2C address in _pin[0] //{TYPE_VIRTUAL_I2C_RGB, "VVV", PSTR("I2C RGB (virtual)")}, // allows setting I2C address in _pin[0] and 2 additional values in _pin[1] & _pin[2] - //{TYPE_USERMOD, "VVVVV", PSTR("Usermod (virtual)")}, // 5 data fields (see https://github.com/wled-dev/WLED/pull/4123) + //{TYPE_USERMOD, "VVVVV", PSTR("Usermod (virtual)")}, // 5 data fields (see https://github.com/wled/WLED/pull/4123) }; } void BusNetwork::cleanup() { DEBUGBUS_PRINTLN(F("Virtual Cleanup.")); - free(_data); + d_free(_data); _data = nullptr; _type = I_NONE; _valid = false; @@ -790,11 +747,11 @@ void BusNetwork::cleanup() { //utility to get the approx. memory usage of a given BusConfig -unsigned BusConfig::memUsage(unsigned nr) const { +size_t BusConfig::memUsage(unsigned nr) const { if (Bus::isVirtual(type)) { return sizeof(BusNetwork) + (count * Bus::getNumberOfChannels(type)); } else if (Bus::isDigital(type)) { - return sizeof(BusDigital) + PolyBus::memUsage(count + skipAmount, PolyBus::getI(type, pins, nr)) + doubleBuffer * (count + skipAmount) * Bus::getNumberOfChannels(type); + return sizeof(BusDigital) + PolyBus::memUsage(count + skipAmount, PolyBus::getI(type, pins, nr)) /*+ doubleBuffer * (count + skipAmount) * Bus::getNumberOfChannels(type)*/; } else if (Bus::isOnOff(type)) { return sizeof(BusOnOff); } else { @@ -803,7 +760,7 @@ unsigned BusConfig::memUsage(unsigned nr) const { } -unsigned BusManager::memUsage() { +size_t BusManager::memUsage() { // when ESP32, S2 & S3 use parallel I2S only the largest bus determines the total memory requirements for back buffers // front buffers are always allocated per bus unsigned size = 0; @@ -832,22 +789,24 @@ unsigned BusManager::memUsage() { } int BusManager::add(const BusConfig &bc) { - DEBUGBUS_PRINTF_P(PSTR("Bus: Adding bus (%d - %d >= %d)\n"), getNumBusses(), getNumVirtualBusses(), WLED_MAX_BUSSES); - if (getNumBusses() - getNumVirtualBusses() >= WLED_MAX_BUSSES) return -1; - unsigned numDigital = 0; - for (const auto &bus : busses) if (bus->isDigital() && !bus->is2Pin()) numDigital++; + DEBUGBUS_PRINTF_P(PSTR("Bus: Adding bus (p:%d v:%d)\n"), getNumBusses(), getNumVirtualBusses()); + unsigned digital = 0; + unsigned analog = 0; + unsigned twoPin = 0; + for (const auto &bus : busses) { + if (bus->isPWM()) analog += bus->getPins(); // number of analog channels used + if (bus->isDigital() && !bus->is2Pin()) digital++; + if (bus->is2Pin()) twoPin++; + } + if (digital > WLED_MAX_DIGITAL_CHANNELS || analog > WLED_MAX_ANALOG_CHANNELS) return -1; if (Bus::isVirtual(bc.type)) { busses.push_back(make_unique(bc)); - //busses.push_back(new BusNetwork(bc)); } else if (Bus::isDigital(bc.type)) { - busses.push_back(make_unique(bc, numDigital)); - //busses.push_back(new BusDigital(bc, numDigital)); + busses.push_back(make_unique(bc, Bus::is2Pin(bc.type) ? twoPin : digital)); } else if (Bus::isOnOff(bc.type)) { busses.push_back(make_unique(bc)); - //busses.push_back(new BusOnOff(bc)); } else { busses.push_back(make_unique(bc)); - //busses.push_back(new BusPwm(bc)); } return busses.size(); } @@ -865,7 +824,7 @@ static String LEDTypesToJson(const std::vector& types) { return json; } -// credit @willmmiles & @netmindz https://github.com/wled-dev/WLED/pull/4056 +// credit @willmmiles & @netmindz https://github.com/wled/WLED/pull/4056 String BusManager::getLEDTypesJSONString() { String json = "["; json += LEDTypesToJson(BusDigital::getLEDTypes()); @@ -891,7 +850,6 @@ void BusManager::removeAll() { DEBUGBUS_PRINTLN(F("Removing all.")); //prevents crashes due to deleting busses while in use. while (!canAllShow()) yield(); - //for (auto &bus : busses) delete bus; // needed when not using std::unique_ptr C++ >11 busses.clear(); PolyBus::setParallelI2S1Output(false); } @@ -980,9 +938,8 @@ void BusManager::show() { void IRAM_ATTR BusManager::setPixelColor(unsigned pix, uint32_t c) { for (auto &bus : busses) { - unsigned bstart = bus->getStart(); - if (pix < bstart || pix >= bstart + bus->getLength()) continue; - bus->setPixelColor(pix - bstart, c); + if (!bus->containsPixel(pix)) continue; + bus->setPixelColor(pix - bus->getStart(), c); } } @@ -997,9 +954,8 @@ void BusManager::setSegmentCCT(int16_t cct, bool allowWBCorrection) { uint32_t BusManager::getPixelColor(unsigned pix) { for (auto &bus : busses) { - unsigned bstart = bus->getStart(); if (!bus->containsPixel(pix)) continue; - return bus->getPixelColor(pix - bstart); + return bus->getPixelColor(pix - bus->getStart()); } return 0; } @@ -1022,6 +978,5 @@ uint8_t Bus::_gAWM = 255; uint16_t BusDigital::_milliAmpsTotal = 0; std::vector> BusManager::busses; -//std::vector BusManager::busses; uint16_t BusManager::_gMilliAmpsUsed = 0; uint16_t BusManager::_gMilliAmpsMax = ABL_MILLIAMPS_DEFAULT; diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h index 0570cc2d6c..064b600a66 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -114,17 +114,17 @@ class Bus { _autoWhiteMode = Bus::hasWhite(type) ? aw : RGBW_MODE_MANUAL_ONLY; }; - virtual ~Bus() {} //throw the bus under the bus (derived class needs to freeData()) + virtual ~Bus() {} //throw the bus under the bus virtual void begin() {}; - virtual void show() = 0; + virtual void show() = 0; virtual bool canShow() const { return true; } virtual void setStatusPixel(uint32_t c) {} - virtual void setPixelColor(unsigned 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(unsigned pix) const { return 0; } - virtual unsigned getPins(uint8_t* pinArray = nullptr) const { return 0; } + virtual size_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; } virtual unsigned skippedLeds() const { return 0; } @@ -132,7 +132,7 @@ class Bus { virtual uint16_t getLEDCurrent() const { return 0; } virtual uint16_t getUsedCurrent() const { return 0; } virtual uint16_t getMaxCurrent() const { return 0; } - virtual unsigned getBusSize() const { return sizeof(Bus); } + virtual size_t getBusSize() const { return sizeof(Bus); } inline bool hasRGB() const { return _hasRgb; } inline bool hasWhite() const { return _hasWhite; } @@ -148,7 +148,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 unsigned getNumberOfChannels() const { return hasWhite() + 3*hasRGB() + hasCCT(); } + inline size_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; } @@ -157,8 +157,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 unsigned getNumberOfPins(uint8_t type) { return isVirtual(type) ? 4 : isPWM(type) ? numPWMPins(type) : is2Pin(type) + 1; } // credit @PaoloTK - static constexpr unsigned getNumberOfChannels(uint8_t type) { return hasWhite(type) + 3*hasRGB(type) + hasCCT(type); } + static constexpr size_t getNumberOfPins(uint8_t type) { return isVirtual(type) ? 4 : isPWM(type) ? numPWMPins(type) : is2Pin(type) + 1; } // credit @PaoloTK + static constexpr size_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); } @@ -243,13 +243,13 @@ class BusDigital : public Bus { void setColorOrder(uint8_t colorOrder) override; [[gnu::hot]] uint32_t getPixelColor(unsigned pix) const override; uint8_t getColorOrder() const override { return _colorOrder; } - unsigned getPins(uint8_t* pinArray = nullptr) const override; + size_t getPins(uint8_t* pinArray = nullptr) const override; unsigned skippedLeds() const override { return _skip; } uint16_t getFrequency() const override { return _frequencykHz; } uint16_t getLEDCurrent() const override { return _milliAmpsPerLed; } uint16_t getUsedCurrent() const override { return _milliAmpsTotal; } uint16_t getMaxCurrent() const override { return _milliAmpsMax; } - unsigned getBusSize() const override; + size_t getBusSize() const override; void begin() override; void cleanup(); @@ -263,7 +263,6 @@ class BusDigital : public Bus { uint16_t _frequencykHz; uint8_t _milliAmpsPerLed; uint16_t _milliAmpsMax; - uint8_t *_data; void *_busPtr; static uint16_t _milliAmpsTotal; // is overwitten/recalculated on each show() @@ -290,9 +289,9 @@ class BusPwm : public Bus { void setPixelColor(unsigned pix, uint32_t c) override; uint32_t getPixelColor(unsigned pix) const override; //does no index check - unsigned getPins(uint8_t* pinArray = nullptr) const override; + size_t getPins(uint8_t* pinArray = nullptr) const override; uint16_t getFrequency() const override { return _frequency; } - unsigned getBusSize() const override { return sizeof(BusPwm); } + size_t getBusSize() const override { return sizeof(BusPwm); } void show() override; inline void cleanup() { deallocatePins(); } @@ -318,8 +317,8 @@ class BusOnOff : public Bus { void setPixelColor(unsigned pix, uint32_t c) override; uint32_t getPixelColor(unsigned pix) const override; - unsigned getPins(uint8_t* pinArray) const override; - unsigned getBusSize() const override { return sizeof(BusOnOff); } + size_t getPins(uint8_t* pinArray) const override; + size_t getBusSize() const override { return sizeof(BusOnOff); } void show() override; inline void cleanup() { PinManager::deallocatePin(_pin, PinOwner::BusOnOff); } @@ -339,10 +338,10 @@ class BusNetwork : public Bus { bool canShow() const override { return !_broadcastLock; } // this should be a return value from UDP routine if it is still sending data out [[gnu::hot]] void setPixelColor(unsigned pix, uint32_t c) override; [[gnu::hot]] uint32_t getPixelColor(unsigned pix) const override; - unsigned getPins(uint8_t* pinArray = nullptr) const override; - unsigned getBusSize() const override { return sizeof(BusNetwork) + (isOk() ? _len * _UDPchannels : 0); } - void show() override; - void cleanup(); + size_t getPins(uint8_t* pinArray = nullptr) const override; + size_t getBusSize() const override { return sizeof(BusNetwork) + (isOk() ? _len * _UDPchannels : 0); } + void show() override; + void cleanup(); static std::vector getLEDTypes(); @@ -367,11 +366,10 @@ struct BusConfig { uint8_t autoWhite; uint8_t pins[5] = {255, 255, 255, 255, 255}; uint16_t frequency; - bool doubleBuffer; uint8_t milliAmpsPerLed; uint16_t milliAmpsMax; - BusConfig(uint8_t busType, uint8_t* ppins, uint16_t pstart, uint16_t len = 1, uint8_t pcolorOrder = COL_ORDER_GRB, bool rev = false, uint8_t skip = 0, byte aw=RGBW_MODE_MANUAL_ONLY, uint16_t clock_kHz=0U, bool dblBfr=false, uint8_t maPerLed=LED_MILLIAMPS_DEFAULT, uint16_t maMax=ABL_MILLIAMPS_DEFAULT) + BusConfig(uint8_t busType, uint8_t* ppins, uint16_t pstart, uint16_t len = 1, uint8_t pcolorOrder = COL_ORDER_GRB, bool rev = false, uint8_t skip = 0, byte aw=RGBW_MODE_MANUAL_ONLY, uint16_t clock_kHz=0U, uint8_t maPerLed=LED_MILLIAMPS_DEFAULT, uint16_t maMax=ABL_MILLIAMPS_DEFAULT) : count(std::max(len,(uint16_t)1)) , start(pstart) , colorOrder(pcolorOrder) @@ -379,7 +377,6 @@ struct BusConfig { , skipAmount(skip) , autoWhite(aw) , frequency(clock_kHz) - , doubleBuffer(dblBfr) , milliAmpsPerLed(maPerLed) , milliAmpsMax(maMax) { @@ -411,7 +408,7 @@ struct BusConfig { return true; } - unsigned memUsage(unsigned nr = 0) const; + size_t memUsage(unsigned nr = 0) const; }; diff --git a/wled00/button.cpp b/wled00/button.cpp index cf8fabe42e..1c50200a2a 100644 --- a/wled00/button.cpp +++ b/wled00/button.cpp @@ -74,7 +74,7 @@ void doublePressAction(uint8_t b) if (!macroDoublePress[b]) { switch (b) { //case 0: toggleOnOff(); colorUpdated(CALL_MODE_BUTTON); break; //instant short press on button 0 if no macro set - case 1: ++effectPalette %= strip.getPaletteCount(); colorUpdated(CALL_MODE_BUTTON); break; + case 1: ++effectPalette %= getPaletteCount(); colorUpdated(CALL_MODE_BUTTON); break; } } else { applyPreset(macroDoublePress[b], CALL_MODE_BUTTON_PRESET); @@ -226,8 +226,8 @@ void handleAnalog(uint8_t b) effectIntensity = aRead; } else if (macroDoublePress[b] == 247) { // selected palette - effectPalette = map(aRead, 0, 252, 0, strip.getPaletteCount()-1); - effectPalette = constrain(effectPalette, 0, strip.getPaletteCount()-1); // map is allowed to "overshoot", so we need to contrain the result + effectPalette = map(aRead, 0, 252, 0, getPaletteCount()-1); + effectPalette = constrain(effectPalette, 0, getPaletteCount()-1); // map is allowed to "overshoot", so we need to contrain the result } else if (macroDoublePress[b] == 200) { // primary color, hue, full saturation colorHStoRGB(aRead*256,255,colPri); diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index fa0397fc65..c9c110067c 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -6,6 +6,35 @@ * The structure of the JSON is not to be considered an official API and may change without notice. */ +#ifndef PIXEL_COUNTS + #define PIXEL_COUNTS DEFAULT_LED_COUNT +#endif + +#ifndef DATA_PINS + #define DATA_PINS DEFAULT_LED_PIN +#endif + +#ifndef LED_TYPES + #define LED_TYPES DEFAULT_LED_TYPE +#endif + +#ifndef DEFAULT_LED_COLOR_ORDER + #define DEFAULT_LED_COLOR_ORDER COL_ORDER_GRB //default to GRB +#endif + +static constexpr unsigned sumPinsRequired(const unsigned* current, size_t count) { + return (count > 0) ? (Bus::getNumberOfPins(*current) + sumPinsRequired(current+1,count-1)) : 0; +} + +static constexpr bool validatePinsAndTypes(const unsigned* types, unsigned numTypes, unsigned numPins ) { + // Pins provided < pins required -> always invalid + // Pins provided = pins required -> always valid + // Pins provided > pins required -> valid if excess pins are a product of last type pins since it will be repeated + return (sumPinsRequired(types, numTypes) > numPins) ? false : + (numPins - sumPinsRequired(types, numTypes)) % Bus::getNumberOfPins(types[numTypes-1]) == 0; +} + + //simple macro for ArduinoJSON's or syntax #define CJSON(a,b) a = b | a @@ -20,7 +49,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { //long vid = doc[F("vid")]; // 2010020 -#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_ETHERNET) +#ifdef WLED_USE_ETHERNET JsonObject ethernet = doc[F("eth")]; CJSON(ethernetType, ethernet["type"]); // NOTE: Ethernet configuration takes priority over other use of pins @@ -120,7 +149,6 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { 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")]); #if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3) CJSON(useParallelI2S, hw_led[F("prl")]); #endif @@ -130,12 +158,13 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { JsonObject matrix = hw_led[F("matrix")]; if (!matrix.isNull()) { strip.isMatrix = true; - CJSON(strip.panels, matrix[F("mpc")]); + unsigned numPanels = matrix[F("mpc")] | 1; + numPanels = constrain(numPanels, 1, WLED_MAX_PANELS); strip.panel.clear(); JsonArray panels = matrix[F("panels")]; - int s = 0; + unsigned s = 0; if (!panels.isNull()) { - strip.panel.reserve(max(1U,min((size_t)strip.panels,(size_t)WLED_MAX_PANELS))); // pre-allocate memory for panels + strip.panel.reserve(numPanels); // pre-allocate default 8x8 panels for (JsonObject pnl : panels) { WS2812FX::Panel p; CJSON(p.bottomStart, pnl["b"]); @@ -147,30 +176,21 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { CJSON(p.height, pnl["h"]); CJSON(p.width, pnl["w"]); strip.panel.push_back(p); - if (++s >= WLED_MAX_PANELS || s >= strip.panels) break; // max panels reached + if (++s >= numPanels) break; // max panels reached } - } else { - // fallback - WS2812FX::Panel p; - strip.panels = 1; - p.height = p.width = 8; - p.xOffset = p.yOffset = 0; - p.options = 0; - strip.panel.push_back(p); } - // cannot call strip.setUpMatrix() here due to already locked JSON buffer + strip.panel.shrink_to_fit(); // release unused memory (just in case) + // cannot call strip.deserializeLedmap()/strip.setUpMatrix() here due to already locked JSON buffer + //if (!fromFS) doInit2D = true; // if called at boot (fromFS==true), WLED::beginStrip() will take care of setting up matrix } #endif + DEBUG_PRINTF_P(PSTR("Heap before buses: %d\n"), ESP.getFreeHeap()); JsonArray ins = hw_led["ins"]; - - if (fromFS || !ins.isNull()) { - DEBUG_PRINTF_P(PSTR("Heap before buses: %d\n"), ESP.getFreeHeap()); + if (!ins.isNull()) { int s = 0; // bus iterator - if (fromFS) BusManager::removeAll(); // can't safely manipulate busses directly in network callback - for (JsonObject elm : ins) { - if (s >= WLED_MAX_BUSSES) break; + if (s >= WLED_MAX_BUSSES) break; // only counts physical buses uint8_t pins[5] = {255, 255, 255, 255, 255}; JsonArray pinArr = elm["pin"]; if (pinArr.size() == 0) continue; @@ -199,11 +219,101 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { } ledType |= refresh << 7; // hack bit 7 to indicate strip requires off refresh - //busConfigs.push_back(std::move(BusConfig(ledType, pins, start, length, colorOrder, reversed, skipFirst, AWmode, freqkHz, useGlobalLedBuffer, maPerLed, maMax))); - busConfigs.emplace_back(ledType, pins, start, length, colorOrder, reversed, skipFirst, AWmode, freqkHz, useGlobalLedBuffer, maPerLed, maMax); + busConfigs.emplace_back(ledType, pins, start, length, colorOrder, reversed, skipFirst, AWmode, freqkHz, maPerLed, maMax); doInitBusses = true; // finalization done in beginStrip() if (!Bus::isVirtual(ledType)) s++; // have as many virtual buses as you want } + } else if (fromFS) { + //if busses failed to load, add default (fresh install, FS issue, ...) + BusManager::removeAll(); + busConfigs.clear(); + + DEBUG_PRINTLN(F("No busses, init default")); + constexpr unsigned defDataTypes[] = {LED_TYPES}; + constexpr unsigned defDataPins[] = {DATA_PINS}; + constexpr unsigned defCounts[] = {PIXEL_COUNTS}; + constexpr unsigned defNumTypes = (sizeof(defDataTypes) / sizeof(defDataTypes[0])); + constexpr unsigned defNumPins = (sizeof(defDataPins) / sizeof(defDataPins[0])); + constexpr unsigned defNumCounts = (sizeof(defCounts) / sizeof(defCounts[0])); + + static_assert(validatePinsAndTypes(defDataTypes, defNumTypes, defNumPins), + "The default pin list defined in DATA_PINS does not match the pin requirements for the default buses defined in LED_TYPES"); + + unsigned mem = 0; + unsigned pinsIndex = 0; + unsigned digitalCount = 0; + for (unsigned i = 0; i < WLED_MAX_BUSSES; i++) { + uint8_t defPin[OUTPUT_MAX_PINS]; + // if we have less types than requested outputs and they do not align, use last known type to set current type + unsigned dataType = defDataTypes[(i < defNumTypes) ? i : defNumTypes -1]; + unsigned busPins = Bus::getNumberOfPins(dataType); + + // if we need more pins than available all outputs have been configured + if (pinsIndex + busPins > defNumPins) break; + + // Assign all pins first so we can check for conflicts on this bus + for (unsigned j = 0; j < busPins && j < OUTPUT_MAX_PINS; j++) defPin[j] = defDataPins[pinsIndex + j]; + + for (unsigned j = 0; j < busPins && j < OUTPUT_MAX_PINS; j++) { + bool validPin = true; + // When booting without config (1st boot) we need to make sure GPIOs defined for LED output don't clash with hardware + // i.e. DEBUG (GPIO1), DMX (2), SPI RAM/FLASH (16&17 on ESP32-WROVER/PICO), read/only pins, etc. + // Pin should not be already allocated, read/only or defined for current bus + while (PinManager::isPinAllocated(defPin[j]) || !PinManager::isPinOk(defPin[j],true)) { + if (validPin) { + DEBUG_PRINTLN(F("Some of the provided pins cannot be used to configure this LED output.")); + defPin[j] = 1; // start with GPIO1 and work upwards + validPin = false; + } else if (defPin[j] < WLED_NUM_PINS) { + defPin[j]++; + } else { + DEBUG_PRINTLN(F("No available pins left! Can't configure output.")); + break; + } + // is the newly assigned pin already defined or used previously? + // try next in line until there are no clashes or we run out of pins + bool clash; + do { + clash = false; + // check for conflicts on current bus + for (const auto &pin : defPin) { + if (&pin != &defPin[j] && pin == defPin[j]) { + clash = true; + break; + } + } + // We already have a clash on current bus, no point checking next buses + if (!clash) { + // check for conflicts in defined pins + for (const auto &pin : defDataPins) { + if (pin == defPin[j]) { + clash = true; + break; + } + } + } + if (clash) defPin[j]++; + if (defPin[j] >= WLED_NUM_PINS) break; + } while (clash); + } + } + pinsIndex += busPins; + + // if we have less counts than pins and they do not align, use last known count to set current count + unsigned count = defCounts[(i < defNumCounts) ? i : defNumCounts -1]; + unsigned start = 0; + // analog always has length 1 + if (Bus::isPWM(dataType) || Bus::isOnOff(dataType)) count = 1; + BusConfig defCfg = BusConfig(dataType, defPin, start, count, DEFAULT_LED_COLOR_ORDER, false, 0, RGBW_MODE_MANUAL_ONLY, 0); + mem += defCfg.memUsage(Bus::isDigital(dataType) && !Bus::is2Pin(dataType) ? digitalCount++ : 0); + if (mem > MAX_LED_MEMORY) { + DEBUG_PRINTF_P(PSTR("Out of LED memory! Bus %d (%d) #%u not created."), (int)dataType, (int)count, digitalCount); + break; + } + busConfigs.push_back(defCfg); // use push_back for simplification as we needed defCfg to calculate memory usage + doInitBusses = true; // finalization done in beginStrip() + } + DEBUG_PRINTF_P(PSTR("LED buffer size: %uB/%uB\n"), mem, BusManager::memUsage()); } if (hw_led["rev"] && BusManager::getNumBusses()) BusManager::getBus(0)->setReversed(true); //set 0.11 global reversed setting for first bus @@ -292,30 +402,28 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { macroLongPress[s] = 0; macroDoublePress[s] = 0; } - } else { + } else if (fromFS) { // new install/missing configuration (button 0 has defaults) - if (fromFS) { - // relies upon only being called once with fromFS == true, which is currently true. - for (size_t s = 0; s < WLED_MAX_BUTTONS; s++) { - if (buttonType[s] == BTN_TYPE_NONE || btnPin[s] < 0 || !PinManager::allocatePin(btnPin[s], false, PinOwner::Button)) { - btnPin[s] = -1; - buttonType[s] = BTN_TYPE_NONE; - } - if (btnPin[s] >= 0) { - if (disablePullUp) { - pinMode(btnPin[s], INPUT); - } else { - #ifdef ESP32 - pinMode(btnPin[s], buttonType[s]==BTN_TYPE_PUSH_ACT_HIGH ? INPUT_PULLDOWN : INPUT_PULLUP); - #else - pinMode(btnPin[s], INPUT_PULLUP); - #endif - } + // relies upon only being called once with fromFS == true, which is currently true. + for (size_t s = 0; s < WLED_MAX_BUTTONS; s++) { + if (buttonType[s] == BTN_TYPE_NONE || btnPin[s] < 0 || !PinManager::allocatePin(btnPin[s], false, PinOwner::Button)) { + btnPin[s] = -1; + buttonType[s] = BTN_TYPE_NONE; + } + if (btnPin[s] >= 0) { + if (disablePullUp) { + pinMode(btnPin[s], INPUT); + } else { + #ifdef ESP32 + pinMode(btnPin[s], buttonType[s]==BTN_TYPE_PUSH_ACT_HIGH ? INPUT_PULLDOWN : INPUT_PULLUP); + #else + pinMode(btnPin[s], INPUT_PULLUP); + #endif } - macroButton[s] = 0; - macroLongPress[s] = 0; - macroDoublePress[s] = 0; } + macroButton[s] = 0; + macroLongPress[s] = 0; + macroDoublePress[s] = 0; } } @@ -392,8 +500,9 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { JsonObject light = doc[F("light")]; CJSON(briMultiplier, light[F("scale-bri")]); - CJSON(strip.paletteBlend, light[F("pal-mode")]); + CJSON(paletteBlend, light[F("pal-mode")]); CJSON(strip.autoSegments, light[F("aseg")]); + CJSON(useRainbowWheel, light[F("rw")]); CJSON(gammaCorrectVal, light["gc"]["val"]); // default 2.8 float light_gc_bri = light["gc"]["bri"]; @@ -648,11 +757,10 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { static const char s_cfg_json[] PROGMEM = "/cfg.json"; void deserializeConfigFromFS() { - bool success = deserializeConfigSec(); + [[maybe_unused]] bool success = deserializeConfigSec(); #ifdef WLED_ADD_EEPROM_SUPPORT if (!success) { //if file does not exist, try reading from EEPROM deEEPSettings(); - return; } #endif @@ -661,23 +769,6 @@ void deserializeConfigFromFS() { DEBUG_PRINTLN(F("Reading settings from /cfg.json...")); success = readObjectFromFile(s_cfg_json, nullptr, pDoc); - if (!success) { // if file does not exist, optionally try reading from EEPROM and then save defaults to FS - releaseJSONBufferLock(); - #ifdef WLED_ADD_EEPROM_SUPPORT - deEEPSettings(); - #endif - - // save default values to /cfg.json - // call readFromConfig() with an empty object so that usermods can initialize to defaults prior to saving - JsonObject empty = JsonObject(); - UsermodManager::readFromConfig(empty); - serializeConfigToFS(); - // init Ethernet (in case default type is set at compile time) - #if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_ETHERNET) - initEthernet(); - #endif - return; - } // NOTE: This routine deserializes *and* applies the configuration // Therefore, must also initialize ethernet from this function @@ -800,14 +891,13 @@ void serializeConfig(JsonObject root) { JsonObject hw_led = hw.createNestedObject("led"); hw_led[F("total")] = strip.getLengthTotal(); //provided for compatibility on downgrade and per-output ABL hw_led[F("maxpwr")] = BusManager::ablMilliampsMax(); - hw_led[F("ledma")] = 0; // no longer used +// hw_led[F("ledma")] = 0; // no longer used hw_led["cct"] = strip.correctWB; hw_led[F("cr")] = strip.cctFromRgb; hw_led[F("ic")] = cctICused; 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; #if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3) hw_led[F("prl")] = BusManager::hasParallelOutput(); #endif @@ -816,7 +906,7 @@ void serializeConfig(JsonObject root) { // 2D Matrix Settings if (strip.isMatrix) { JsonObject matrix = hw_led.createNestedObject(F("matrix")); - matrix[F("mpc")] = strip.panels; + matrix[F("mpc")] = strip.panel.size(); JsonArray panels = matrix.createNestedArray(F("panels")); for (size_t i = 0; i < strip.panel.size(); i++) { JsonObject pnl = panels.createNestedObject(); @@ -926,8 +1016,9 @@ void serializeConfig(JsonObject root) { JsonObject light = root.createNestedObject(F("light")); light[F("scale-bri")] = briMultiplier; - light[F("pal-mode")] = strip.paletteBlend; + light[F("pal-mode")] = paletteBlend; light[F("aseg")] = strip.autoSegments; + light[F("rw")] = useRainbowWheel; JsonObject light_gc = light.createNestedObject("gc"); light_gc["bri"] = (gammaCorrectBri) ? gammaCorrectVal : 1.0f; // keep compatibility diff --git a/wled00/colors.cpp b/wled00/colors.cpp index ba38d5a151..3b3d9ab704 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -208,14 +208,14 @@ CRGBPalette16 generateHarmonicRandomPalette(const CRGBPalette16 &basepalette) makepastelpalette = true; } - // apply saturation & gamma correction + // apply saturation CRGB RGBpalettecolors[4]; for (int i = 0; i < 4; i++) { 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 + RGBpalettecolors[i] = ((uint32_t)RGBpalettecolors[i]) & 0x00FFFFFFU; //strip alpha from CRGB } return CRGBPalette16(RGBpalettecolors[0], @@ -232,6 +232,54 @@ CRGBPalette16 generateRandomPalette() // generate fully random palette CHSV(hw_random8(), hw_random8(160, 255), hw_random8(128, 255))); } +void loadCustomPalettes() { + byte tcp[72]; //support gradient palettes with up to 18 entries + CRGBPalette16 targetPalette; + customPalettes.clear(); // start fresh + for (int index = 0; index<10; index++) { + char fileName[32]; + sprintf_P(fileName, PSTR("/palette%d.json"), index); + + StaticJsonDocument<1536> pDoc; // barely enough to fit 72 numbers + if (WLED_FS.exists(fileName)) { + DEBUGFX_PRINTF_P(PSTR("Reading palette from %s\n"), fileName); + if (readObjectFromFile(fileName, nullptr, &pDoc)) { + JsonArray pal = pDoc[F("palette")]; + if (!pal.isNull() && pal.size()>3) { // not an empty palette (at least 2 entries) + memset(tcp, 255, sizeof(tcp)); + if (pal[0].is() && pal[1].is()) { + // we have an array of index & hex strings + size_t palSize = MIN(pal.size(), 36); + palSize -= palSize % 2; // make sure size is multiple of 2 + for (size_t i=0, j=0; i()<256; i+=2) { + uint8_t rgbw[] = {0,0,0,0}; + if (colorFromHexString(rgbw, pal[i+1].as())) { // will catch non-string entires + tcp[ j ] = (uint8_t) pal[ i ].as(); // index + for (size_t c=0; c<3; c++) tcp[j+1+c] = rgbw[c]; // only use RGB component + DEBUGFX_PRINTF_P(PSTR("%2u -> %3d [%3d,%3d,%3d]\n"), i, int(tcp[j]), int(tcp[j+1]), int(tcp[j+2]), int(tcp[j+3])); + j += 4; + } + } + } else { + size_t palSize = MIN(pal.size(), 72); + palSize -= palSize % 4; // make sure size is multiple of 4 + for (size_t i=0; i()<256; i+=4) { + tcp[ i ] = (uint8_t) pal[ i ].as(); // index + for (size_t c=0; c<3; c++) tcp[i+1+c] = (uint8_t) pal[i+1+c].as(); + DEBUGFX_PRINTF_P(PSTR("%2u -> %3d [%3d,%3d,%3d]\n"), i, int(tcp[i]), int(tcp[i+1]), int(tcp[i+2]), int(tcp[i+3])); + } + } + customPalettes.push_back(targetPalette.loadDynamicGradientPalette(tcp)); + } else { + DEBUGFX_PRINTLN(F("Wrong palette format.")); + } + } + } else { + break; + } + } +} + void hsv2rgb(const CHSV32& hsv, uint32_t& rgb) // convert HSV (16bit hue) to RGB (32bit with white = 0) { unsigned int remainder, region, p, q, t; diff --git a/wled00/const.h b/wled00/const.h index 2b460f3f18..cfcd0a6b8e 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -1,3 +1,4 @@ +#pragma once #ifndef WLED_CONST_H #define WLED_CONST_H @@ -44,67 +45,52 @@ #endif #endif -#ifndef WLED_MAX_BUSSES - #ifdef ESP8266 - #define WLED_MAX_DIGITAL_CHANNELS 3 - #define WLED_MAX_ANALOG_CHANNELS 5 - #define WLED_MAX_BUSSES 4 // will allow 3 digital & 1 analog RGB - #define WLED_MIN_VIRTUAL_BUSSES 3 // no longer used for bus creation but used to distinguish S2/S3 in UI - #else - #define WLED_MAX_ANALOG_CHANNELS (LEDC_CHANNEL_MAX*LEDC_SPEED_MODE_MAX) - #if defined(CONFIG_IDF_TARGET_ESP32C3) // 2 RMT, 6 LEDC, only has 1 I2S but NPB does not support it ATM - #define WLED_MAX_BUSSES 6 // will allow 2 digital & 2 analog RGB or 6 PWM white - #define WLED_MAX_DIGITAL_CHANNELS 2 - //#define WLED_MAX_ANALOG_CHANNELS 6 - #define WLED_MIN_VIRTUAL_BUSSES 4 // no longer used for bus creation but used to distinguish S2/S3 in UI - #elif defined(CONFIG_IDF_TARGET_ESP32S2) // 4 RMT, 8 LEDC, only has 1 I2S bus, supported in NPB - // the 5th bus (I2S) will prevent Audioreactive usermod from functioning (it is last used though) - #define WLED_MAX_BUSSES 7 // will allow 5 digital & 2 analog RGB - #define WLED_MAX_DIGITAL_CHANNELS 5 - //#define WLED_MAX_ANALOG_CHANNELS 8 - #define WLED_MIN_VIRTUAL_BUSSES 4 // no longer used for bus creation but used to distinguish S2/S3 in UI - #elif defined(CONFIG_IDF_TARGET_ESP32S3) // 4 RMT, 8 LEDC, has 2 I2S but NPB supports parallel x8 LCD on I2S1 - #define WLED_MAX_BUSSES 14 // will allow 12 digital & 2 analog RGB - #define WLED_MAX_DIGITAL_CHANNELS 12 // x4 RMT + x8 I2S-LCD - //#define WLED_MAX_ANALOG_CHANNELS 8 - #define WLED_MIN_VIRTUAL_BUSSES 6 // no longer used for bus creation but used to distinguish S2/S3 in UI - #else - // the last digital bus (I2S0) will prevent Audioreactive usermod from functioning - #define WLED_MAX_BUSSES 19 // will allow 16 digital & 3 analog RGB - #define WLED_MAX_DIGITAL_CHANNELS 16 // x1/x8 I2S1 + x8 RMT - //#define WLED_MAX_ANALOG_CHANNELS 16 - #define WLED_MIN_VIRTUAL_BUSSES 6 // no longer used for bus creation but used to distinguish S2/S3 in UI - #endif - #endif +#ifdef ESP8266 + #define WLED_MAX_DIGITAL_CHANNELS 3 + #define WLED_MAX_ANALOG_CHANNELS 5 + #define WLED_MIN_VIRTUAL_BUSSES 3 // no longer used for bus creation but used to distinguish S2/S3 in UI #else - #ifdef ESP8266 - #if WLED_MAX_BUSSES > 5 - #error Maximum number of buses is 5. - #endif - #ifndef WLED_MAX_ANALOG_CHANNELS - #error You must also define WLED_MAX_ANALOG_CHANNELS. - #endif - #ifndef WLED_MAX_DIGITAL_CHANNELS - #error You must also define WLED_MAX_DIGITAL_CHANNELS. - #endif - #define WLED_MIN_VIRTUAL_BUSSES 3 + #if !defined(LEDC_CHANNEL_MAX) || !defined(LEDC_SPEED_MODE_MAX) + #include "driver/ledc.h" // needed for analog/LEDC channel counts + #endif + #define WLED_MAX_ANALOG_CHANNELS (LEDC_CHANNEL_MAX*LEDC_SPEED_MODE_MAX) + #if defined(CONFIG_IDF_TARGET_ESP32C3) // 2 RMT, 6 LEDC, only has 1 I2S but NPB does not support it ATM + #define WLED_MAX_DIGITAL_CHANNELS 2 + //#define WLED_MAX_ANALOG_CHANNELS 6 + #define WLED_MIN_VIRTUAL_BUSSES 4 // no longer used for bus creation but used to distinguish S2/S3 in UI + #elif defined(CONFIG_IDF_TARGET_ESP32S2) // 4 RMT, 8 LEDC, only has 1 I2S bus, supported in NPB + // the 5th bus (I2S) will prevent Audioreactive usermod from functioning (it is last used though) + #define WLED_MAX_DIGITAL_CHANNELS 12 // x4 RMT + x1/x8 I2S0 + //#define WLED_MAX_ANALOG_CHANNELS 8 + #define WLED_MIN_VIRTUAL_BUSSES 4 // no longer used for bus creation but used to distinguish S2/S3 in UI + #elif defined(CONFIG_IDF_TARGET_ESP32S3) // 4 RMT, 8 LEDC, has 2 I2S but NPB supports parallel x8 LCD on I2S1 + #define WLED_MAX_DIGITAL_CHANNELS 12 // x4 RMT + x8 I2S-LCD + //#define WLED_MAX_ANALOG_CHANNELS 8 + #define WLED_MIN_VIRTUAL_BUSSES 6 // no longer used for bus creation but used to distinguish S2/S3 in UI #else - #if WLED_MAX_BUSSES > 20 - #error Maximum number of buses is 20. - #endif - #ifndef WLED_MAX_ANALOG_CHANNELS - #error You must also define WLED_MAX_ANALOG_CHANNELS. - #endif - #ifndef WLED_MAX_DIGITAL_CHANNELS - #error You must also define WLED_MAX_DIGITAL_CHANNELS. - #endif - #if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3) - #define WLED_MIN_VIRTUAL_BUSSES 4 - #else - #define WLED_MIN_VIRTUAL_BUSSES 6 - #endif + // the last digital bus (I2S0) will prevent Audioreactive usermod from functioning + #define WLED_MAX_DIGITAL_CHANNELS 16 // x1/x8 I2S1 + x8 RMT + //#define WLED_MAX_ANALOG_CHANNELS 16 + #define WLED_MIN_VIRTUAL_BUSSES 6 // no longer used for bus creation but used to distinguish S2/S3 in UI #endif #endif +// WLED_MAX_BUSSES was used to define the size of busses[] array which is no longer needed +// instead it will help determine max number of buses that can be defined at compile time +#ifdef WLED_MAX_BUSSES + #undef WLED_MAX_BUSSES +#endif +#define WLED_MAX_BUSSES (WLED_MAX_DIGITAL_CHANNELS+WLED_MAX_ANALOG_CHANNELS) +static_assert(WLED_MAX_BUSSES <= 32, "WLED_MAX_BUSSES exceeds hard limit"); + +// Maximum number of pins per output. 5 for RGBCCT analog LEDs. +#define OUTPUT_MAX_PINS 5 + +// for pin manager +#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 #ifndef WLED_MAX_BUTTONS #ifdef ESP8266 @@ -151,6 +137,8 @@ #endif #endif +#define WLED_MAX_PANELS 18 // must not be more than 32 + //Usermod IDs #define USERMOD_ID_RESERVED 0 //Unused. Might indicate no usermod present #define USERMOD_ID_UNSPECIFIED 1 //Default value for a general user mod that does not specify a custom ID @@ -336,18 +324,6 @@ #define TYPE_NET_ARTNET_RGBW 89 //network ArtNet RGB bus (master broadcast bus, unused) #define TYPE_VIRTUAL_MAX 95 -/* -// old macros that have been moved to Bus class -#define IS_TYPE_VALID(t) ((t) > 15 && (t) < 128) -#define IS_DIGITAL(t) (((t) > 15 && (t) < 40) || ((t) > 47 && (t) < 64)) //digital are 16-39 and 48-63 -#define IS_2PIN(t) ((t) > 47 && (t) < 64) -#define IS_16BIT(t) ((t) == TYPE_UCS8903 || (t) == TYPE_UCS8904) -#define IS_ONOFF(t) ((t) == 40) -#define IS_PWM(t) ((t) > 40 && (t) < 46) //does not include on/Off type -#define NUM_PWM_PINS(t) ((t) - 40) //for analog PWM 41-45 only -#define IS_VIRTUAL(t) ((t) >= 80 && (t) < 96) //this was a poor choice a better would be 96-111 -*/ - //Color orders #define COL_ORDER_GRB 0 //GRB(w),defaut #define COL_ORDER_RGB 1 //common for WS2811 @@ -435,6 +411,7 @@ #define ERR_CONCURRENCY 2 // Conurrency (client active) #define ERR_NOBUF 3 // JSON buffer was not released in time, request cannot be handled at this time #define ERR_NOT_IMPL 4 // Not implemented +#define ERR_NORAM_PX 7 // not enough RAM for pixels #define ERR_NORAM 8 // effect RAM depleted #define ERR_JSON 9 // JSON parsing failed (input too large?) #define ERR_FS_BEGIN 10 // Could not init filesystem (no partition?) @@ -474,30 +451,29 @@ #define NTP_PACKET_SIZE 48 // size of NTP receive buffer #define NTP_MIN_PACKET_SIZE 48 // min expected size - NTP v4 allows for "extended information" appended to the standard fields -// Maximum number of pins per output. 5 for RGBCCT analog LEDs. -#define OUTPUT_MAX_PINS 5 - //maximum number of rendered LEDs - this does not have to match max. physical LEDs, e.g. if there are virtual busses #ifndef MAX_LEDS -#ifdef ESP8266 -#define MAX_LEDS 1664 //can't rely on memory limit to limit this to 1600 LEDs -#elif defined(CONFIG_IDF_TARGET_ESP32S2) -#define MAX_LEDS 2048 //due to memory constraints -#else -#define MAX_LEDS 8192 -#endif + #ifdef ESP8266 + #define MAX_LEDS 1536 //can't rely on memory limit to limit this to 1536 LEDs + #elif defined(CONFIG_IDF_TARGET_ESP32S2) + #define MAX_LEDS 2048 //due to memory constraints S2 + #elif defined(CONFIG_IDF_TARGET_ESP32C3) + #define MAX_LEDS 4096 + #else + #define MAX_LEDS 16384 + #endif #endif #ifndef MAX_LED_MEMORY #ifdef ESP8266 - #define MAX_LED_MEMORY 4000 + #define MAX_LED_MEMORY 4096 #else #if defined(ARDUINO_ARCH_ESP32S2) - #define MAX_LED_MEMORY 16000 + #define MAX_LED_MEMORY 16384 #elif defined(ARDUINO_ARCH_ESP32C3) - #define MAX_LED_MEMORY 32000 + #define MAX_LED_MEMORY 32768 #else - #define MAX_LED_MEMORY 64000 + #define MAX_LED_MEMORY 65536 #endif #endif #endif diff --git a/wled00/data/index.css b/wled00/data/index.css index 31e2daa920..c92c884abb 100644 --- a/wled00/data/index.css +++ b/wled00/data/index.css @@ -353,12 +353,12 @@ button { padding: 4px 0 0; } -#segutil, #segutil2, #segcont, #putil, #pcont, #pql, #fx, #palw, +#segutil, #segutil2, #segcont, #putil, #pcont, #pql, #fx, #palw, #bsp, .fnd { max-width: 280px; } -#putil, #segutil, #segutil2 { +#putil, #segutil, #segutil2, #bsp { min-height: 42px; margin: 0 auto; } diff --git a/wled00/data/index.htm b/wled00/data/index.htm index c55c983732..3716f7ccd8 100644 --- a/wled00/data/index.htm +++ b/wled00/data/index.htm @@ -268,28 +268,28 @@

Transition:  s

-

Blend: - -

+

@@ -363,7 +363,7 @@ diff --git a/wled00/data/index.js b/wled00/data/index.js index 147abf3892..295a3403b4 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -35,9 +35,10 @@ var cfg = { // [year, month (0 -> January, 11 -> December), day, duration in days, image url] var hol = [ [0, 11, 24, 4, "https://aircoookie.github.io/xmas.png"], // christmas - [0, 2, 17, 1, "https://images.alphacoders.com/491/491123.jpg"], // st. Patrick's day - [2025, 3, 20, 2, "https://aircoookie.github.io/easter.png"], // easter 2025 - [2024, 2, 31, 2, "https://aircoookie.github.io/easter.png"], // easter 2024 + [0, 2, 17, 1, "https://images.alphacoders.com/491/491123.jpg"], // st. Patrick's day + [2026, 3, 5, 2, "https://aircoookie.github.io/easter.png"], // easter 2026 + [2027, 2, 28, 2, "https://aircoookie.github.io/easter.png"], // easter 2027 + //[2028, 3, 16, 2, "https://aircoookie.github.io/easter.png"], // easter 2028 [0, 6, 4, 1, "https://images.alphacoders.com/516/516792.jpg"], // 4th of July [0, 0, 1, 1, "https://images.alphacoders.com/119/1198800.jpg"] // new year ]; @@ -57,7 +58,7 @@ function handleVisibilityChange() {if (!d.hidden && new Date () - lastUpdate > 3 function sCol(na, col) {d.documentElement.style.setProperty(na, col);} function gId(c) {return d.getElementById(c);} function gEBCN(c) {return d.getElementsByClassName(c);} -function isEmpty(o) {return Object.keys(o).length === 0;} +function isEmpty(o) {for (const i in o) return false; return true;} function isObj(i) {return (i && typeof i === 'object' && !Array.isArray(i));} function isNumeric(n) {return !isNaN(parseFloat(n)) && isFinite(n);} @@ -805,6 +806,26 @@ function populateSegments(s) ``+ ``+ ``; + let blend = `
Blend mode
`+ + `
`+ + `
`; let sndSim = `
Sound sim
`+ `

Make a segment for each output:
Custom bus start indices:
- Use global LED buffer:

Color Order Override: @@ -866,7 +863,6 @@

Defaults

Transitions

Default transition time: ms
Random Cycle Palette Time: s
- Use harmonic Random Cycle Palette:

Timed light

Default duration: min
Default target brightness:
@@ -903,8 +899,10 @@

Advanced


+ Use harmonic Random Cycle palette:
+ Use "rainbow" color wheel:
Target refresh rate: FPS - +
diff --git a/wled00/e131.cpp b/wled00/e131.cpp index c16ed9332e..98cfe28fb0 100644 --- a/wled00/e131.cpp +++ b/wled00/e131.cpp @@ -38,8 +38,7 @@ void handleDDPPacket(e131_packet_t* p) { if (realtimeMode != REALTIME_MODE_DDP) ddpSeenPush = false; // just starting, no push yet realtimeLock(realtimeTimeoutMs, REALTIME_MODE_DDP); - if (!realtimeOverride || (realtimeMode && useMainSegmentOnly)) { - if (useMainSegmentOnly) strip.getMainSegment().beginDraw(); + if (!realtimeOverride) { 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); } @@ -150,10 +149,9 @@ void handleDMXData(uint16_t uni, uint16_t dmxChannels, uint8_t* e131_data, uint8 realtimeLock(realtimeTimeoutMs, mde); - if (realtimeOverride && !(realtimeMode && useMainSegmentOnly)) return; + if (realtimeOverride) 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; @@ -163,7 +161,7 @@ void handleDMXData(uint16_t uni, uint16_t dmxChannels, uint8_t* e131_data, uint8 if (availDMXLen < 4) return; realtimeLock(realtimeTimeoutMs, mde); - if (realtimeOverride && !(realtimeMode && useMainSegmentOnly)) return; + if (realtimeOverride) return; wChannel = (availDMXLen > 4) ? e131_data[dataOffset+4] : 0; if (bri != e131_data[dataOffset+0]) { @@ -171,7 +169,6 @@ void handleDMXData(uint16_t uni, uint16_t dmxChannels, uint8_t* e131_data, uint8 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; @@ -228,16 +225,16 @@ void handleDMXData(uint16_t uni, uint16_t dmxChannels, uint8_t* e131_data, uint8 if (e131_data[dataOffset+3] != seg.intensity) seg.intensity = e131_data[dataOffset+3]; if (e131_data[dataOffset+4] != seg.palette) seg.setPalette(e131_data[dataOffset+4]); - if ((e131_data[dataOffset+5] & 0b00000010) != seg.reverse_y) { seg.setOption(SEG_OPTION_REVERSED_Y, e131_data[dataOffset+5] & 0b00000010); } - if ((e131_data[dataOffset+5] & 0b00000100) != seg.mirror_y) { seg.setOption(SEG_OPTION_MIRROR_Y, e131_data[dataOffset+5] & 0b00000100); } - if ((e131_data[dataOffset+5] & 0b00001000) != seg.transpose) { seg.setOption(SEG_OPTION_TRANSPOSED, e131_data[dataOffset+5] & 0b00001000); } - if ((e131_data[dataOffset+5] & 0b00110000) / 8 != seg.map1D2D) { - seg.map1D2D = (e131_data[dataOffset+5] & 0b00110000) / 8; + if (bool(e131_data[dataOffset+5] & 0b00000010) != seg.reverse_y) { seg.reverse_y = bool(e131_data[dataOffset+5] & 0b00000010); } + if (bool(e131_data[dataOffset+5] & 0b00000100) != seg.mirror_y) { seg.mirror_y = bool(e131_data[dataOffset+5] & 0b00000100); } + if (bool(e131_data[dataOffset+5] & 0b00001000) != seg.transpose) { seg.transpose = bool(e131_data[dataOffset+5] & 0b00001000); } + if ((e131_data[dataOffset+5] & 0b00110000) >> 4 != seg.map1D2D) { + seg.map1D2D = (e131_data[dataOffset+5] & 0b00110000) >> 4; } // To maintain backwards compatibility with prior e1.31 values, reverse is fixed to mask 0x01000000 - if ((e131_data[dataOffset+5] & 0b01000000) != seg.reverse) { seg.setOption(SEG_OPTION_REVERSED, e131_data[dataOffset+5] & 0b01000000); } + if ((e131_data[dataOffset+5] & 0b01000000) != seg.reverse) { seg.reverse = bool(e131_data[dataOffset+5] & 0b01000000); } // To maintain backwards compatibility with prior e1.31 values, mirror is fixed to mask 0x10000000 - if ((e131_data[dataOffset+5] & 0b10000000) != seg.mirror) { seg.setOption(SEG_OPTION_MIRROR, e131_data[dataOffset+5] & 0b10000000); } + if ((e131_data[dataOffset+5] & 0b10000000) != seg.mirror) { seg.mirror = bool(e131_data[dataOffset+5] & 0b10000000); } uint32_t colors[3]; byte whites[3] = {0,0,0}; @@ -271,7 +268,7 @@ void handleDMXData(uint16_t uni, uint16_t dmxChannels, uint8_t* e131_data, uint8 case DMX_MODE_MULTIPLE_RGB: case DMX_MODE_MULTIPLE_RGBW: { - bool is4Chan = (DMXMode == DMX_MODE_MULTIPLE_RGBW); + const bool is4Chan = (DMXMode == DMX_MODE_MULTIPLE_RGBW); const unsigned dmxChannelsPerLed = is4Chan ? 4 : 3; const unsigned ledsPerUniverse = is4Chan ? MAX_4_CH_LEDS_PER_UNIVERSE : MAX_3_CH_LEDS_PER_UNIVERSE; uint8_t stripBrightness = bri; @@ -303,7 +300,7 @@ void handleDMXData(uint16_t uni, uint16_t dmxChannels, uint8_t* e131_data, uint8 } realtimeLock(realtimeTimeoutMs, mde); - if (realtimeOverride && !(realtimeMode && useMainSegmentOnly)) return; + if (realtimeOverride) return; if (ledsTotal > totalLen) { ledsTotal = totalLen; @@ -316,17 +313,9 @@ void handleDMXData(uint16_t uni, uint16_t dmxChannels, uint8_t* e131_data, uint8 } } - 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); - dmxOffset+=3; - } - } else { - for (unsigned i = previousLeds; i < ledsTotal; i++) { - setRealtimePixel(i, e131_data[dmxOffset], e131_data[dmxOffset+1], e131_data[dmxOffset+2], e131_data[dmxOffset+3]); - dmxOffset+=4; - } + for (unsigned i = previousLeds; i < ledsTotal; i++) { + setRealtimePixel(i, e131_data[dmxOffset], e131_data[dmxOffset+1], e131_data[dmxOffset+2], is4Chan ? e131_data[dmxOffset+3] : 0); + dmxOffset += dmxChannelsPerLed; } break; } @@ -529,7 +518,7 @@ void sendArtnetPollReply(ArtPollReply *reply, IPAddress ipAddress, uint16_t port reply->reply_sub_sw = (uint8_t)((portAddress >> 4) & 0x000F); reply->reply_sw_out[0] = (uint8_t)(portAddress & 0x000F); - snprintf_P((char *)reply->reply_node_report, sizeof(reply->reply_node_report)-1, PSTR("#0001 [%04u] OK - WLED v" TOSTRING(WLED_VERSION)), pollReplyCount); + snprintf_P((char *)reply->reply_node_report, sizeof(reply->reply_node_report)-1, PSTR("#0001 [%04u] OK - WLED v%s"), pollReplyCount, versionString); if (pollReplyCount < 9999) { pollReplyCount++; diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index ff2443648d..486e5c5628 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -172,6 +172,9 @@ inline uint32_t color_blend16(uint32_t c1, uint32_t c2, uint16_t b) { return col [[gnu::hot, gnu::pure]] uint32_t ColorFromPaletteWLED(const CRGBPalette16 &pal, unsigned index, uint8_t brightness = (uint8_t)255U, TBlendType blendType = LINEARBLEND); CRGBPalette16 generateHarmonicRandomPalette(const CRGBPalette16 &basepalette); CRGBPalette16 generateRandomPalette(); +void loadCustomPalettes(); +extern std::vector customPalettes; +inline size_t getPaletteCount() { return 13 + GRADIENT_PALETTE_COUNT + customPalettes.size(); } 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 hsv2rgb(const CHSV32& hsv, uint32_t& rgb); void colorHStoRGB(uint16_t hue, byte sat, byte* rgb); @@ -223,9 +226,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 +class Segment; #ifdef WLED_ENABLE_GIF bool fileSeekCallback(unsigned long position); unsigned long filePositionCallback(void); @@ -261,9 +263,7 @@ void handleIR(); #include "ESPAsyncWebServer.h" #include "src/dependencies/json/ArduinoJson-v6.h" #include "src/dependencies/json/AsyncJson-v6.h" -#include "FX.h" -bool deserializeSegment(JsonObject elem, byte it, byte presetId = 0); bool deserializeState(JsonObject root, byte callMode = CALL_MODE_DIRECT_CHANGE, byte presetId = 0); void serializeSegment(const JsonObject& root, const Segment& seg, byte id, bool forPreset = false, bool segmentBounds = true); void serializeState(JsonObject root, bool forPreset = false, bool includeBri = true, bool segmentBounds = true, bool selectedSegmentsOnly = false); @@ -277,8 +277,8 @@ bool serveLiveLeds(AsyncWebServerRequest* request, uint32_t wsClient = 0); //led.cpp void setValuesFromSegment(uint8_t s); -void setValuesFromMainSeg(); -void setValuesFromFirstSelectedSeg(); +#define setValuesFromMainSeg() setValuesFromSegment(strip.getMainSegmentId()) +#define setValuesFromFirstSelectedSeg() setValuesFromSegment(strip.getFirstSelectedSegId()) void toggleOnOff(); void applyBri(); void applyFinalBri(); @@ -490,11 +490,11 @@ void userLoop(); #define inoise8 perlin8 // fastled legacy alias #define inoise16 perlin16 // fastled legacy alias #define hex2int(a) (((a)>='0' && (a)<='9') ? (a)-'0' : ((a)>='A' && (a)<='F') ? (a)-'A'+10 : ((a)>='a' && (a)<='f') ? (a)-'a'+10 : 0) -[[gnu::pure]] int getNumVal(const String* req, uint16_t pos); -void parseNumber(const char* str, byte* val, byte minv=0, byte maxv=255); -bool getVal(JsonVariant elem, byte* val, byte vmin=0, byte vmax=255); // getVal supports inc/decrementing and random ("X~Y(r|[w]~[-][Z])" form) +[[gnu::pure]] int getNumVal(const String &req, uint16_t pos); +void parseNumber(const char* str, byte &val, byte minv=0, byte maxv=255); +bool getVal(JsonVariant elem, byte &val, byte vmin=0, byte vmax=255); // getVal supports inc/decrementing and random ("X~Y(r|[w]~[-][Z])" form) [[gnu::pure]] bool getBoolVal(const JsonVariant &elem, bool dflt); -bool updateVal(const char* req, const char* key, byte* val, byte minv=0, byte maxv=255); +bool updateVal(const char* req, const char* key, byte &val, byte minv=0, byte maxv=255); size_t printSetFormCheckbox(Print& settingsScript, const char* key, int val); size_t printSetFormValue(Print& settingsScript, const char* key, int val); size_t printSetFormValue(Print& settingsScript, const char* key, const char* val); @@ -544,6 +544,29 @@ inline uint8_t hw_random8() { return HW_RND_REGISTER; }; inline uint8_t hw_random8(uint32_t upperlimit) { return (hw_random8() * upperlimit) >> 8; }; // input range 0-255 inline uint8_t hw_random8(uint32_t lowerlimit, uint32_t upperlimit) { uint32_t range = upperlimit - lowerlimit; return lowerlimit + hw_random8(range); }; // input range 0-255 +// PSRAM allocation wrappers +#ifndef ESP8266 +extern "C" { + void *p_malloc(size_t); // prefer PSRAM over DRAM + void *p_calloc(size_t, size_t); // prefer PSRAM over DRAM + void *p_realloc(void *, size_t); // prefer PSRAM over DRAM + inline void p_free(void *ptr) { heap_caps_free(ptr); } + void *d_malloc(size_t); // prefer DRAM over PSRAM + void *d_calloc(size_t, size_t); // prefer DRAM over PSRAM + void *d_realloc(void *, size_t); // prefer DRAM over PSRAM + inline void d_free(void *ptr) { heap_caps_free(ptr); } +} +#else +#define p_malloc malloc +#define p_calloc calloc +#define p_realloc realloc +#define p_free free +#define d_malloc malloc +#define d_calloc calloc +#define d_realloc realloc +#define d_free free +#endif + // RAII guard class for the JSON Buffer lock // Modeled after std::lock_guard class JSONBufferGuard { diff --git a/wled00/file.cpp b/wled00/file.cpp index d390207d45..c1960e616c 100644 --- a/wled00/file.cpp +++ b/wled00/file.cpp @@ -39,7 +39,7 @@ void closeFile() { uint32_t s = millis(); #endif f.close(); - DEBUGFS_PRINTF("took %d ms\n", millis() - s); + DEBUGFS_PRINTF("took %lu ms\n", millis() - s); doCloseFile = false; } @@ -69,14 +69,14 @@ static bool bufferedFind(const char *target, bool fromStart = true) { if(buf[count] == target[index]) { if(++index >= targetLen) { // return true if all chars in the target match f.seek((f.position() - bufsize) + count +1); - DEBUGFS_PRINTF("Found at pos %d, took %d ms", f.position(), millis() - s); + DEBUGFS_PRINTF("Found at pos %d, took %lu ms", f.position(), millis() - s); return true; } } count++; } } - DEBUGFS_PRINTF("No match, took %d ms\n", millis() - s); + DEBUGFS_PRINTF("No match, took %lu ms\n", millis() - s); return false; } @@ -111,7 +111,7 @@ static bool bufferedFindSpace(size_t targetLen, bool fromStart = true) { f.seek((f.position() - bufsize) + count +1 - targetLen); knownLargestSpace = MAX_SPACE; //there may be larger spaces after, so we don't know } - DEBUGFS_PRINTF("Found at pos %d, took %d ms", f.position(), millis() - s); + DEBUGFS_PRINTF("Found at pos %d, took %lu ms", f.position(), millis() - s); return true; } } else { @@ -125,7 +125,7 @@ static bool bufferedFindSpace(size_t targetLen, bool fromStart = true) { count++; } } - DEBUGFS_PRINTF("No match, took %d ms\n", millis() - s); + DEBUGFS_PRINTF("No match, took %lu ms\n", millis() - s); return false; } @@ -151,13 +151,13 @@ static bool bufferedFindObjectEnd() { if (buf[count] == '}') objDepth--; if (objDepth == 0) { f.seek((f.position() - bufsize) + count +1); - DEBUGFS_PRINTF("} at pos %d, took %d ms", f.position(), millis() - s); + DEBUGFS_PRINTF("} at pos %d, took %lu ms", f.position(), millis() - s); return true; } count++; } } - DEBUGFS_PRINTF("No match, took %d ms\n", millis() - s); + DEBUGFS_PRINTF("No match, took %lu ms\n", millis() - s); return false; } @@ -203,7 +203,7 @@ static bool appendObjectToFile(const char* key, const JsonDocument* content, uin if (f.position() > 2) f.write(','); //add comma if not first object f.print(key); serializeJson(*content, f); - DEBUGFS_PRINTF("Inserted, took %d ms (total %d)", millis() - s1, millis() - s); + DEBUGFS_PRINTF("Inserted, took %lu ms (total %lu)", millis() - s1, millis() - s); doCloseFile = true; return true; } @@ -251,7 +251,7 @@ static bool appendObjectToFile(const char* key, const JsonDocument* content, uin f.write('}'); doCloseFile = true; - DEBUGFS_PRINTF("Appended, took %d ms (total %d)", millis() - s1, millis() - s); + DEBUGFS_PRINTF("Appended, took %lu ms (total %lu)", millis() - s1, millis() - s); return true; } @@ -321,7 +321,7 @@ bool writeObjectToFile(const char* file, const char* key, const JsonDocument* co } doCloseFile = true; - DEBUGFS_PRINTF("Replaced/deleted, took %d ms\n", millis() - s); + DEBUGFS_PRINTF("Replaced/deleted, took %lu ms\n", millis() - s); return true; } @@ -356,7 +356,7 @@ bool readObjectFromFile(const char* file, const char* key, JsonDocument* dest, c else deserializeJson(*dest, f); f.close(); - DEBUGFS_PRINTF("Read, took %d ms\n", millis() - s); + DEBUGFS_PRINTF("Read, took %lu ms\n", millis() - s); return true; } @@ -392,7 +392,7 @@ static const uint8_t *getPresetCache(size_t &size) { if ((presetsModifiedTime != presetsCachedTime) || (presetsCachedValidate != cacheInvalidate)) { if (presetsCached) { - free(presetsCached); + p_free(presetsCached); presetsCached = nullptr; } } @@ -403,7 +403,7 @@ static const uint8_t *getPresetCache(size_t &size) { presetsCachedTime = presetsModifiedTime; presetsCachedValidate = cacheInvalidate; presetsCachedSize = 0; - presetsCached = (uint8_t*)ps_malloc(file.size() + 1); + presetsCached = (uint8_t*)p_malloc(file.size() + 1); if (presetsCached) { presetsCachedSize = file.size(); file.read(presetsCached, presetsCachedSize); @@ -419,7 +419,7 @@ static const uint8_t *getPresetCache(size_t &size) { #endif bool handleFileRead(AsyncWebServerRequest* request, String path){ - DEBUG_PRINT(F("WS FileRead: ")); DEBUG_PRINTLN(path); + DEBUGFS_PRINT(F("WS FileRead: ")); DEBUGFS_PRINTLN(path); if(path.endsWith("/")) path += "index.htm"; if(path.indexOf(F("sec")) > -1) return false; #ifdef ARDUINO_ARCH_ESP32 diff --git a/wled00/image_loader.cpp b/wled00/image_loader.cpp index 9665057942..aa4ae2e161 100644 --- a/wled00/image_loader.cpp +++ b/wled00/image_loader.cpp @@ -78,7 +78,7 @@ void drawPixelCallback(int16_t x, int16_t y, uint8_t red, uint8_t green, uint8_t 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 (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; diff --git a/wled00/ir.cpp b/wled00/ir.cpp index 9f7950a2fb..b2fec76f1f 100644 --- a/wled00/ir.cpp +++ b/wled00/ir.cpp @@ -425,8 +425,8 @@ static void decodeIR44(uint32_t code) case IR44_COLDWHITE2 : changeColor(COLOR_COLDWHITE2, 255); changeEffect(FX_MODE_STATIC); break; case IR44_REDPLUS : changeEffect(relativeChange(effectCurrent, 1, 0, strip.getModeCount() -1)); break; case IR44_REDMINUS : changeEffect(relativeChange(effectCurrent, -1, 0, strip.getModeCount() -1)); break; - case IR44_GREENPLUS : changePalette(relativeChange(effectPalette, 1, 0, strip.getPaletteCount() -1)); break; - case IR44_GREENMINUS : changePalette(relativeChange(effectPalette, -1, 0, strip.getPaletteCount() -1)); break; + case IR44_GREENPLUS : changePalette(relativeChange(effectPalette, 1, 0, getPaletteCount() -1)); break; + case IR44_GREENMINUS : changePalette(relativeChange(effectPalette, -1, 0, getPaletteCount() -1)); break; case IR44_BLUEPLUS : changeEffectIntensity( 16); break; case IR44_BLUEMINUS : changeEffectIntensity(-16); break; case IR44_QUICK : changeEffectSpeed( 16); break; @@ -435,7 +435,7 @@ static void decodeIR44(uint32_t code) case IR44_DIY2 : presetFallback(2, FX_MODE_BREATH, 0); break; case IR44_DIY3 : presetFallback(3, FX_MODE_FIRE_FLICKER, 0); break; case IR44_DIY4 : presetFallback(4, FX_MODE_RAINBOW, 0); break; - case IR44_DIY5 : presetFallback(5, FX_MODE_METEOR, 0); break; + case IR44_DIY5 : presetFallback(5, FX_MODE_METEOR, 0); break; case IR44_DIY6 : presetFallback(6, FX_MODE_RAIN, 0); break; case IR44_AUTO : changeEffect(FX_MODE_STATIC); break; case IR44_FLASH : changeEffect(FX_MODE_PALETTE); break; @@ -484,7 +484,7 @@ static void decodeIR6(uint32_t code) case IR6_CHANNEL_UP: incBrightness(); break; case IR6_CHANNEL_DOWN: decBrightness(); break; case IR6_VOLUME_UP: changeEffect(relativeChange(effectCurrent, 1, 0, strip.getModeCount() -1)); break; - case IR6_VOLUME_DOWN: changePalette(relativeChange(effectPalette, 1, 0, strip.getPaletteCount() -1)); + case IR6_VOLUME_DOWN: changePalette(relativeChange(effectPalette, 1, 0, getPaletteCount() -1)); switch(lastIR6ColourIdx) { case 0: changeColor(COLOR_RED); break; case 1: changeColor(COLOR_REDDISH); break; @@ -530,7 +530,7 @@ static void decodeIR9(uint32_t code) /* This allows users to customize IR actions without the need to edit C code and compile. -From the https://github.com/wled-dev/WLED/wiki/Infrared-Control page, download the starter +From the https://github.com/wled/WLED/wiki/Infrared-Control page, download the starter ir.json file that corresponds to the number of buttons on your remote. Many of the remotes with the same number of buttons emit the same codes, but will have different labels or colors. Once you edit the ir.json file, upload it to your controller diff --git a/wled00/json.cpp b/wled00/json.cpp index c09b543f1c..4414681023 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -2,14 +2,74 @@ #include "palettes.h" +#define JSON_PATH_STATE 1 +#define JSON_PATH_INFO 2 +#define JSON_PATH_STATE_INFO 3 +#define JSON_PATH_NODES 4 +#define JSON_PATH_PALETTES 5 +#define JSON_PATH_FXDATA 6 +#define JSON_PATH_NETWORKS 7 +#define JSON_PATH_EFFECTS 8 + /* * JSON API (De)serialization */ +namespace { + typedef struct { + uint32_t colors[NUM_COLORS]; + uint16_t start; + uint16_t stop; + uint16_t offset; + uint16_t grouping; + uint16_t spacing; + uint16_t startY; + uint16_t stopY; + uint16_t options; + uint8_t mode; + uint8_t palette; + uint8_t opacity; + uint8_t speed; + uint8_t intensity; + uint8_t custom1; + uint8_t custom2; + uint8_t custom3; + bool check1; + bool check2; + bool check3; + } SegmentCopy; + + uint8_t differs(const Segment& b, const SegmentCopy& a) { + uint8_t d = 0; + if (a.start != b.start) d |= SEG_DIFFERS_BOUNDS; + if (a.stop != b.stop) d |= SEG_DIFFERS_BOUNDS; + if (a.offset != b.offset) d |= SEG_DIFFERS_GSO; + if (a.grouping != b.grouping) d |= SEG_DIFFERS_GSO; + if (a.spacing != b.spacing) d |= SEG_DIFFERS_GSO; + if (a.opacity != b.opacity) d |= SEG_DIFFERS_BRI; + if (a.mode != b.mode) d |= SEG_DIFFERS_FX; + if (a.speed != b.speed) d |= SEG_DIFFERS_FX; + if (a.intensity != b.intensity) d |= SEG_DIFFERS_FX; + if (a.palette != b.palette) d |= SEG_DIFFERS_FX; + if (a.custom1 != b.custom1) d |= SEG_DIFFERS_FX; + if (a.custom2 != b.custom2) d |= SEG_DIFFERS_FX; + if (a.custom3 != b.custom3) d |= SEG_DIFFERS_FX; + if (a.startY != b.startY) d |= SEG_DIFFERS_BOUNDS; + if (a.stopY != b.stopY) d |= SEG_DIFFERS_BOUNDS; + + //bit pattern: (msb first) + // set:2, sound:2, mapping:3, transposed, mirrorY, reverseY, [reset,] paused, mirrored, on, reverse, [selected] + if ((a.options & 0b1111111111011110U) != (b.options & 0b1111111111011110U)) d |= SEG_DIFFERS_OPT; + if ((a.options & 0x0001U) != (b.options & 0x0001U)) d |= SEG_DIFFERS_SEL; + for (unsigned i = 0; i < NUM_COLORS; i++) if (a.colors[i] != b.colors[i]) d |= SEG_DIFFERS_COL; + + return d; + } +} -bool deserializeSegment(JsonObject elem, byte it, byte presetId) +static bool deserializeSegment(JsonObject elem, byte it, byte presetId = 0) { byte id = elem["id"] | it; - if (id >= strip.getMaxSegments()) return false; + if (id >= WS2812FX::getMaxSegments()) return false; bool newSeg = false; int stop = elem["stop"] | -1; @@ -17,16 +77,37 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId) // append segment if (id >= strip.getSegmentsNum()) { if (stop <= 0) return false; // ignore empty/inactive segments - strip.appendSegment(Segment(0, strip.getLengthTotal())); + strip.appendSegment(0, strip.getLengthTotal()); id = strip.getSegmentsNum()-1; // segments are added at the end of list newSeg = true; } //DEBUG_PRINTLN(F("-- JSON deserialize segment.")); Segment& seg = strip.getSegment(id); - //DEBUG_PRINTF_P(PSTR("-- Original segment: %p (%p)\n"), &seg, seg.data); - 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); + // we do not want to make segment copy as it may use a lot of RAM (effect data and pixel buffer) + // so we will create a copy of segment options and compare it with original segment when done processing + SegmentCopy prev = { + {seg.colors[0], seg.colors[1], seg.colors[2]}, + seg.start, + seg.stop, + seg.offset, + seg.grouping, + seg.spacing, + seg.startY, + seg.stopY, + seg.options, + seg.mode, + seg.palette, + seg.opacity, + seg.speed, + seg.intensity, + seg.custom1, + seg.custom2, + seg.custom3, + seg.check1, + seg.check2, + seg.check3 + }; int start = elem["start"] | seg.start; if (stop < 0) { @@ -44,7 +125,7 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId) elem.remove("rpt"); // remove for recursive call elem.remove("n"); // remove for recursive call unsigned len = stop - start; - for (size_t i=id+1; i= strip.getLengthTotal()) break; //TODO: add support for 2D @@ -58,28 +139,11 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId) if (elem["n"]) { // name field exists - if (seg.name) { //clear old name - free(seg.name); - seg.name = nullptr; - } - const char * name = elem["n"].as(); - size_t len = 0; - if (name != nullptr) len = strlen(name); - if (len > 0) { - if (len > WLED_MAX_SEGNAME_LEN) len = WLED_MAX_SEGNAME_LEN; - seg.name = static_cast(malloc(len+1)); - if (seg.name) strlcpy(seg.name, name, WLED_MAX_SEGNAME_LEN+1); - } else { - // but is empty (already deleted above) - elem.remove("n"); - } + seg.setName(name); // will resolve empty and null correctly } else if (start != seg.start || stop != seg.stop) { // clearing or setting segment without name field - if (seg.name) { - free(seg.name); - seg.name = nullptr; - } + seg.clearName(); } uint16_t grp = elem["grp"] | seg.grouping; @@ -97,6 +161,12 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId) bool transpose = getBoolVal(elem[F("tp")], seg.transpose); #endif + // if segment's virtual dimensions change we need to restart effect (segment blending and PS rely on dimensions) + if (seg.mirror != mirror) seg.markForReset(); + #ifndef WLED_DISABLE_2D + if (seg.mirror_y != mirror_y || seg.transpose != transpose) seg.markForReset(); + #endif + int len = (stop > start) ? stop - start : 1; int offset = elem[F("of")] | INT32_MAX; if (offset != INT32_MAX) { @@ -118,8 +188,8 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId) } byte segbri = seg.opacity; - if (getVal(elem["bri"], &segbri)) { - if (segbri > 0) seg.setOpacity(segbri); + if (getVal(elem["bri"], segbri)) { + if (segbri > 0) seg.setOpacity(segbri); // use transition seg.setOption(SEG_OPTION_ON, segbri); // use transition } @@ -175,13 +245,13 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId) if (!colValid) continue; - seg.setColor(i, RGBW32(rgbw[0],rgbw[1],rgbw[2],rgbw[3])); + seg.setColor(i, RGBW32(rgbw[0],rgbw[1],rgbw[2],rgbw[3])); // use transition if (seg.mode == FX_MODE_STATIC) strip.trigger(); //instant refresh } } else { // non RGB & non White segment (usually On/Off bus) - seg.setColor(0, ULTRAWHITE); - seg.setColor(1, BLACK); + seg.setColor(0, ULTRAWHITE); // use transition + seg.setColor(1, BLACK); // use transition } } @@ -197,7 +267,6 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId) } #endif - //seg.map1D2D = constrain(map1D2D, 0, 7); // done in setGeometry() seg.set = constrain(set, 0, 3); seg.soundSim = constrain(soundSim, 0, 3); seg.selected = selected; @@ -210,57 +279,58 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId) #endif byte fx = seg.mode; - if (getVal(elem["fx"], &fx, 0, strip.getModeCount())) { + if (getVal(elem["fx"], fx, 0, strip.getModeCount())) { if (!presetId && currentPlaylist>=0) unloadPlaylist(); - if (fx != seg.mode) seg.setMode(fx, elem[F("fxdef")]); + if (fx != seg.mode) seg.setMode(fx, elem[F("fxdef")]); // use transition (WARNING: may change map1D2D causing geometry change) } - getVal(elem["sx"], &seg.speed); - getVal(elem["ix"], &seg.intensity); + getVal(elem["sx"], seg.speed); + getVal(elem["ix"], seg.intensity); uint8_t pal = seg.palette; if (seg.getLightCapabilities() & 1) { // ignore palette for White and On/Off segments - if (getVal(elem["pal"], &pal, 0, strip.getPaletteCount())) seg.setPalette(pal); + if (getVal(elem["pal"], pal, 0, getPaletteCount())) seg.setPalette(pal); } - getVal(elem["c1"], &seg.custom1); - getVal(elem["c2"], &seg.custom2); + getVal(elem["c1"], seg.custom1); + getVal(elem["c2"], seg.custom2); uint8_t cust3 = seg.custom3; - getVal(elem["c3"], &cust3, 0, 31); // we can't pass reference to bitfield + getVal(elem["c3"], cust3, 0, 31); // we can't pass reference to bitfield seg.custom3 = constrain(cust3, 0, 31); seg.check1 = getBoolVal(elem["o1"], seg.check1); seg.check2 = getBoolVal(elem["o2"], seg.check2); seg.check3 = getBoolVal(elem["o3"], seg.check3); + uint8_t blend = seg.blendMode; + getVal(elem["bm"], blend, 0, 15); // we can't pass reference to bitfield + seg.blendMode = constrain(blend, 0, 15); + JsonArray iarr = elem[F("i")]; //set individual LEDs if (!iarr.isNull()) { - uint8_t oldMap1D2D = seg.map1D2D; - seg.map1D2D = M12_Pixels; // no mapping - // set brightness immediately and disable transition jsonTransitionOnce = true; - seg.stopTransition(); + if (seg.isInTransition()) seg.startTransition(0); // setting transition time to 0 will stop transition in next frame strip.setTransition(0); strip.setBrightness(scaledBri(bri), true); // freeze and init to black if (!seg.freeze) { seg.freeze = true; - seg.fill(BLACK); + seg.clear(); } - start = 0, stop = 0; - set = 0; //0 nothing set, 1 start set, 2 range set + unsigned iStart = 0, iStop = 0; + unsigned iSet = 0; //0 nothing set, 1 start set, 2 range set for (size_t i = 0; i < iarr.size(); i++) { - if(iarr[i].is()) { - if (!set) { - start = abs(iarr[i].as()); - set++; + if (iarr[i].is()) { + if (!iSet) { + iStart = abs(iarr[i].as()); + iSet++; } else { - stop = abs(iarr[i].as()); - set++; + iStop = abs(iarr[i].as()); + iSet++; } } else { //color uint8_t rgbw[] = {0,0,0,0}; @@ -276,17 +346,16 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId) } } - if (set < 2 || stop <= start) stop = start + 1; - uint32_t c = gamma32(RGBW32(rgbw[0], rgbw[1], rgbw[2], rgbw[3])); - while (start < stop) seg.setPixelColor(start++, c); - set = 0; + if (iSet < 2 || iStop <= iStart) iStop = iStart + 1; + uint32_t c = RGBW32(rgbw[0], rgbw[1], rgbw[2], rgbw[3]); + while (iStart < iStop) seg.setRawPixelColor(iStart++, c); // sets pixel color without 1D->2D expansion, grouping or spacing + iSet = 0; } } - seg.map1D2D = oldMap1D2D; // restore mapping strip.trigger(); // force segment update } // send UDP/WS if segment options changed (except selection; will also deselect current preset) - if (seg.differs(prev) & 0x7F) stateChanged = true; + if (differs(seg, prev) & ~SEG_DIFFERS_SEL) stateChanged = true; return true; } @@ -302,7 +371,8 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId) #endif bool onBefore = bri; - getVal(root["bri"], &bri); + getVal(root["bri"], bri); + if (bri != briOld) stateChanged = true; bool on = root["on"] | (bri > 0); if (!on != !bri) toggleOnOff(); @@ -329,10 +399,8 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId) } } -#ifndef WLED_DISABLE_MODE_BLEND blendingStyle = root[F("bs")] | blendingStyle; blendingStyle &= 0x1F; -#endif // temporary transition (applies only once) tr = root[F("tt")] | -1; @@ -345,6 +413,7 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId) if (tr >= 0) strip.timebase = (unsigned long)tr - millis(); JsonObject nl = root["nl"]; + if (!nl.isNull()) stateChanged = true; nightlightActive = getBoolVal(nl["on"], nightlightActive); nightlightDelayMins = nl["dur"] | nightlightDelayMins; nightlightMode = nl["mode"] | nightlightMode; @@ -371,6 +440,7 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId) if (realtimeOverride > 2) realtimeOverride = REALTIME_OVERRIDE_ALWAYS; if (realtimeMode && useMainSegmentOnly) { strip.getMainSegment().freeze = !realtimeOverride; + realtimeOverride = REALTIME_OVERRIDE_NONE; // ignore request for override if using main segment only } if (root.containsKey("live")) { @@ -388,18 +458,14 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId) if (!segVar.isNull()) { // we may be called during strip.service() so we must not modify segments while effects are executing strip.suspend(); - const unsigned long start = millis(); - while (strip.isServicing() && millis() - start < strip.getFrameTime()) yield(); // wait until frame is over - #ifdef WLED_DEBUG - if (millis() - start > 0) DEBUG_PRINTLN(F("JSON: Waited for strip to finish servicing.")); - #endif + strip.waitForIt(); if (segVar.is()) { int id = segVar["id"] | -1; //if "seg" is not an array and ID not specified, apply to all selected/checked segments if (id < 0) { //apply all selected segments for (size_t s = 0; s < strip.getSegmentsNum(); s++) { - Segment &sg = strip.getSegment(s); + const Segment &sg = strip.getSegment(s); if (sg.isActive() && sg.isSelected()) { deserializeSegment(segVar, s, presetId); } @@ -449,7 +515,7 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId) DEBUG_PRINTF_P(PSTR("Preset direct: %d\n"), currentPreset); } else if (!root["ps"].isNull()) { // we have "ps" call (i.e. from button or external API call) or "pd" that includes "ps" (i.e. from UI call) - if (root["win"].isNull() && getVal(root["ps"], &presetCycCurr, 1, 250) && presetCycCurr > 0 && presetCycCurr < 251 && presetCycCurr != currentPreset) { + if (root["win"].isNull() && getVal(root["ps"], presetCycCurr, 1, 250) && presetCycCurr > 0 && presetCycCurr < 251 && presetCycCurr != currentPreset) { DEBUG_PRINTF_P(PSTR("Preset select: %d\n"), presetCycCurr); // b) preset ID only or preset that does not change state (use embedded cycling limits if they exist in getVal()) applyPreset(presetCycCurr, callMode); // async load from file system (only preset ID was specified) @@ -465,11 +531,11 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId) } if (root.containsKey(F("rmcpal")) && root[F("rmcpal")].as()) { - if (strip.customPalettes.size()) { + if (customPalettes.size()) { char fileName[32]; - sprintf_P(fileName, PSTR("/palette%d.json"), strip.customPalettes.size()-1); + sprintf_P(fileName, PSTR("/palette%d.json"), customPalettes.size()-1); if (WLED_FS.exists(fileName)) WLED_FS.remove(fileName); - strip.loadCustomPalettes(); + loadCustomPalettes(); } } @@ -488,13 +554,13 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId) //if (restart) forceReconnect = true; } - stateUpdated(callMode); + if (stateChanged) stateUpdated(callMode); if (presetToRestore) currentPreset = presetToRestore; return stateResponse; } -void serializeSegment(const JsonObject& root, const Segment& seg, byte id, bool forPreset, bool segmentBounds) +static void serializeSegment(JsonObject& root, const Segment& seg, byte id, bool forPreset, bool segmentBounds) { root["id"] = id; if (segmentBounds) { @@ -517,6 +583,7 @@ void serializeSegment(const JsonObject& root, const Segment& seg, byte id, bool root["bri"] = (segbri) ? segbri : 255; root["cct"] = seg.cct; root[F("set")] = seg.set; + root["lc"] = seg.getLightCapabilities(); if (seg.name != nullptr) root["n"] = reinterpret_cast(seg.name); //not good practice, but decreases required JSON buffer else if (forPreset) root["n"] = ""; @@ -561,6 +628,7 @@ void serializeSegment(const JsonObject& root, const Segment& seg, byte id, bool root["o3"] = seg.check3; root["si"] = seg.soundSim; root["m12"] = seg.map1D2D; + root["bm"] = seg.blendMode; } void serializeState(JsonObject root, bool forPreset, bool includeBri, bool segmentBounds, bool selectedSegmentsOnly) @@ -569,9 +637,7 @@ 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) { @@ -602,7 +668,7 @@ void serializeState(JsonObject root, bool forPreset, bool includeBri, bool segme root[F("mainseg")] = strip.getMainSegmentId(); JsonArray seg = root.createNestedArray("seg"); - for (size_t s = 0; s < strip.getMaxSegments(); s++) { + for (size_t s = 0; s < WS2812FX::getMaxSegments(); s++) { if (s >= strip.getSegmentsNum()) { if (forPreset && segmentBounds && !selectedSegmentsOnly) { //disable segments not part of preset JsonObject seg0 = seg.createNestedObject(); @@ -611,7 +677,7 @@ void serializeState(JsonObject root, bool forPreset, bool includeBri, bool segme } else break; } - Segment &sg = strip.getSegment(s); + const Segment &sg = strip.getSegment(s); if (forPreset && selectedSegmentsOnly && !sg.isSelected()) continue; if (sg.isActive()) { JsonObject seg0 = seg.createNestedObject(); @@ -635,7 +701,7 @@ void serializeInfo(JsonObject root) leds[F("pwr")] = BusManager::currentMilliamps(); leds["fps"] = strip.getFps(); leds[F("maxpwr")] = BusManager::currentMilliamps()>0 ? BusManager::ablMilliampsMax() : 0; - leds[F("maxseg")] = strip.getMaxSegments(); + leds[F("maxseg")] = WS2812FX::getMaxSegments(); //leds[F("actseg")] = strip.getActiveSegmentsNum(); //leds[F("seglock")] = false; //might be used in the future to prevent modifications to segment config leds[F("bootps")] = bootPreset; @@ -649,13 +715,13 @@ void serializeInfo(JsonObject root) #endif unsigned totalLC = 0; - JsonArray lcarr = leds.createNestedArray(F("seglc")); + JsonArray lcarr = leds.createNestedArray(F("seglc")); // deprecated, use state.seg[].lc size_t nSegs = strip.getSegmentsNum(); for (size_t s = 0; s < nSegs; s++) { if (!strip.getSegment(s).isActive()) continue; unsigned lc = strip.getSegment(s).getLightCapabilities(); totalLC |= lc; - lcarr.add(lc); + lcarr.add(lc); // deprecated, use state.seg[].lc } leds["lc"] = totalLC; @@ -703,8 +769,8 @@ void serializeInfo(JsonObject root) #endif root[F("fxcount")] = strip.getModeCount(); - root[F("palcount")] = strip.getPaletteCount(); - root[F("cpalcount")] = strip.customPalettes.size(); //number of custom palettes + root[F("palcount")] = getPaletteCount(); + root[F("cpalcount")] = customPalettes.size(); //number of custom palettes JsonArray ledmaps = root.createNestedArray(F("maps")); for (size_t i=0; i maxPage) page = maxPage; int start = itemPerPage * page; int end = start + itemPerPage; - if (end > palettesCount + customPalettes) end = palettesCount + customPalettes; + if (end > palettesCount + customPalettesCount) end = palettesCount + customPalettesCount; root[F("m")] = maxPage; // inform caller how many pages there are JsonObject palettes = root.createNestedObject("p"); @@ -911,7 +977,7 @@ void serializePalettes(JsonObject root, int page) break; default: if (i >= palettesCount) - setPaletteColors(curPalette, strip.customPalettes[i - palettesCount]); + setPaletteColors(curPalette, customPalettes[i - palettesCount]); else if (i < 13) // palette 6 - 12, fastled palettes setPaletteColors(curPalette, *fastledPalettes[i-6]); else { diff --git a/wled00/led.cpp b/wled00/led.cpp index 2d2f5b6f21..43771f9d53 100644 --- a/wled00/led.cpp +++ b/wled00/led.cpp @@ -4,11 +4,9 @@ * LED methods */ -void setValuesFromMainSeg() { setValuesFromSegment(strip.getMainSegmentId()); } -void setValuesFromFirstSelectedSeg() { setValuesFromSegment(strip.getFirstSelectedSegId()); } -void setValuesFromSegment(uint8_t s) -{ - Segment& seg = strip.getSegment(s); + // applies chosen setment properties to legacy values +void setValuesFromSegment(uint8_t s) { + const Segment& seg = strip.getSegment(s); colPri[0] = R(seg.colors[0]); colPri[1] = G(seg.colors[0]); colPri[2] = B(seg.colors[0]); @@ -24,25 +22,19 @@ void setValuesFromSegment(uint8_t s) } -// applies global legacy values (col, colSec, effectCurrent...) -// problem: if the first selected segment already has the value to be set, other selected segments are not updated -void applyValuesToSelectedSegs() -{ - // copy of first selected segment to tell if value was updated - unsigned firstSel = strip.getFirstSelectedSegId(); - Segment selsegPrev = strip.getSegment(firstSel); +// applies global legacy values (colPri, colSec, effectCurrent...) to each selected segment +void applyValuesToSelectedSegs() { for (unsigned i = 0; i < strip.getSegmentsNum(); i++) { Segment& seg = strip.getSegment(i); - if (i != firstSel && (!seg.isActive() || !seg.isSelected())) continue; - - if (effectSpeed != selsegPrev.speed) {seg.speed = effectSpeed; stateChanged = true;} - if (effectIntensity != selsegPrev.intensity) {seg.intensity = effectIntensity; stateChanged = true;} - if (effectPalette != selsegPrev.palette) {seg.setPalette(effectPalette);} - if (effectCurrent != selsegPrev.mode) {seg.setMode(effectCurrent);} + if (!(seg.isActive() && seg.isSelected())) continue; + if (effectSpeed != seg.speed) {seg.speed = effectSpeed; stateChanged = true;} + if (effectIntensity != seg.intensity) {seg.intensity = effectIntensity; stateChanged = true;} + if (effectPalette != seg.palette) {seg.setPalette(effectPalette);} + if (effectCurrent != seg.mode) {seg.setMode(effectCurrent);} uint32_t col0 = RGBW32(colPri[0], colPri[1], colPri[2], colPri[3]); uint32_t col1 = RGBW32(colSec[0], colSec[1], colSec[2], colSec[3]); - if (col0 != selsegPrev.colors[0]) {seg.setColor(0, col0);} - if (col1 != selsegPrev.colors[1]) {seg.setColor(1, col1);} + if (col0 != seg.colors[0]) {seg.setColor(0, col0);} + if (col1 != seg.colors[1]) {seg.setColor(1, col1);} } } @@ -73,7 +65,8 @@ byte scaledBri(byte in) //applies global temporary brightness (briT) to strip void applyBri() { - if (!(realtimeMode && arlsForceMaxBri)) { + if (realtimeOverride || !(realtimeMode && arlsForceMaxBri)) + { //DEBUG_PRINTF_P(PSTR("Applying strip brightness: %d (%d,%d)\n"), (int)briT, (int)bri, (int)briOld); strip.setBrightness(scaledBri(briT)); } @@ -94,7 +87,7 @@ void applyFinalBri() { void stateUpdated(byte callMode) { //call for notifier -> 0: init 1: direct change 2: button 3: notification 4: nightlight 5: other (No notification) // 6: fx changed 7: hue 8: preset cycle 9: blynk 10: alexa 11: ws send only 12: button preset - setValuesFromFirstSelectedSeg(); + setValuesFromFirstSelectedSeg(); // a much better approach would be to use main segment: setValuesFromMainSeg() if (bri != briOld || stateChanged) { if (stateChanged) currentPreset = 0; //something changed, so we are no longer in the preset @@ -104,7 +97,6 @@ void stateUpdated(byte callMode) { //set flag to update ws and mqtt interfaceUpdateCallMode = callMode; - stateChanged = false; } else { if (nightlightActive && !nightlightActiveOld && callMode != CALL_MODE_NOTIFICATION && callMode != CALL_MODE_NO_NOTIFY) { notify(CALL_MODE_NIGHTLIGHT); @@ -134,15 +126,16 @@ void stateUpdated(byte callMode) { jsonTransitionOnce = false; transitionActive = false; applyFinalBri(); - return; + strip.trigger(); + } else { + if (transitionActive) { + briOld = briT; + } else if (bri != briOld || stateChanged) + strip.setTransitionMode(true); // force all segments to transition mode + transitionActive = true; + transitionStartTime = now; } - - if (transitionActive) { - briOld = briT; - } else - strip.setTransitionMode(true); // force all segments to transition mode - transitionActive = true; - transitionStartTime = now; + stateChanged = false; } diff --git a/wled00/mqtt.cpp b/wled00/mqtt.cpp index a462881ec7..19d4e889cc 100644 --- a/wled00/mqtt.cpp +++ b/wled00/mqtt.cpp @@ -68,8 +68,8 @@ static void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProp } if (index == 0) { // start (1st partial packet or the only packet) - if (payloadStr) free(payloadStr); // fail-safe: release buffer - payloadStr = static_cast(malloc(total+1)); // allocate new buffer + p_free(payloadStr); // release buffer if it exists + payloadStr = static_cast(p_malloc(total+1)); // allocate new buffer } if (payloadStr == nullptr) return; // buffer not allocated @@ -94,7 +94,7 @@ static void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProp } else { // Non-Wled Topic used here. Probably a usermod subscribed to this topic. UsermodManager::onMqttMessage(topic, payloadStr); - free(payloadStr); + p_free(payloadStr); payloadStr = nullptr; return; } @@ -124,7 +124,7 @@ static void onMqttMessage(char* topic, char* payload, AsyncMqttClientMessageProp // topmost topic (just wled/MAC) parseMQTTBriPayload(payloadStr); } - free(payloadStr); + p_free(payloadStr); payloadStr = nullptr; } @@ -196,7 +196,8 @@ bool initMqtt() if (!mqttEnabled || mqttServer[0] == 0 || !WLED_CONNECTED) return false; if (mqtt == nullptr) { - mqtt = new AsyncMqttClient(); + void *ptr = p_malloc(sizeof(AsyncMqttClient)); + mqtt = new (ptr) AsyncMqttClient(); // use placement new (into PSRAM), client will never be deleted if (!mqtt) return false; mqtt->onMessage(onMqttMessage); mqtt->onConnect(onMqttConnect); diff --git a/wled00/overlay.cpp b/wled00/overlay.cpp index fcd0a40c2c..3f6e631214 100644 --- a/wled00/overlay.cpp +++ b/wled00/overlay.cpp @@ -90,9 +90,8 @@ void _overlayAnalogCountdown() void handleOverlayDraw() { UsermodManager::handleOverlayDraw(); if (analogClockSolidBlack) { - const Segment* segments = strip.getSegments(); for (unsigned i = 0; i < strip.getSegmentsNum(); i++) { - const Segment& segment = segments[i]; + const Segment& segment = strip.getSegment(i); if (!segment.isActive()) continue; if (segment.mode > 0 || segment.colors[0] > 0) { return; diff --git a/wled00/palettes.h b/wled00/palettes.h old mode 100644 new mode 100755 index c84c1fb97a..70cf8418b8 --- a/wled00/palettes.h +++ b/wled00/palettes.h @@ -1,500 +1,371 @@ +#ifndef PalettesWLED_h +#define PalettesWLED_h + /* * Color palettes for FastLED effects (65-73). - * 4 bytes per color: index, red, green, blue */ // From ColorWavesWithPalettes by Mark Kriegsman: https://gist.github.com/kriegsman/8281905786e8b2632aeb // Unfortunately, these are stored in RAM! // Gradient palette "ib_jul01_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/ing/xmas/tn/ib_jul01.png.index.html -// converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 16 bytes of program space. - -#ifndef PalettesWLED_h -#define PalettesWLED_h - -const byte ib_jul01_gp[] PROGMEM = { - 0, 194, 1, 1, - 94, 1, 29, 18, - 132, 57,131, 28, - 255, 113, 1, 1}; +// http://seaviewsensing.com/pub/cpt-city/ing/xmas/ib_jul01.c3g +const uint8_t ib_jul01_gp[] PROGMEM = { + 0, 230, 6, 17, + 94, 37, 96, 90, + 132, 144, 189, 106, + 255, 187, 3, 13}; // Gradient palette "es_vintage_57_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/vintage/tn/es_vintage_57.png.index.html -// converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 20 bytes of program space. - -const byte es_vintage_57_gp[] PROGMEM = { - 0, 2, 1, 1, - 53, 18, 1, 0, - 104, 69, 29, 1, - 153, 167,135, 10, - 255, 46, 56, 4}; - +// http://seaviewsensing.com/pub/cpt-city/es/vintage/es_vintage_57.c3g +const uint8_t es_vintage_57_gp[] PROGMEM = { + 0, 41, 8, 5, + 53, 92, 1, 0, + 104, 155, 96, 36, + 153, 217, 191, 72, + 255, 132, 129, 52}; // Gradient palette "es_vintage_01_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/vintage/tn/es_vintage_01.png.index.html -// converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 32 bytes of program space. - -const byte es_vintage_01_gp[] PROGMEM = { - 0, 4, 1, 1, - 51, 16, 0, 1, - 76, 97,104, 3, - 101, 255,131, 19, - 127, 67, 9, 4, - 153, 16, 0, 1, - 229, 4, 1, 1, - 255, 4, 1, 1}; - +// http://seaviewsensing.com/pub/cpt-city/es/vintage/es_vintage_01.c3g +const uint8_t es_vintage_01_gp[] PROGMEM = { + 0, 54, 18, 32, + 51, 89, 0, 30, + 76, 176, 170, 48, + 101, 255, 189, 92, + 127, 153, 56, 50, + 153, 89, 0, 30, + 229, 54, 18, 32, + 255, 54, 18, 32}; // Gradient palette "es_rivendell_15_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/rivendell/tn/es_rivendell_15.png.index.html -// converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 20 bytes of program space. - -const byte es_rivendell_15_gp[] PROGMEM = { - 0, 1, 14, 5, - 101, 16, 36, 14, - 165, 56, 68, 30, - 242, 150,156, 99, - 255, 150,156, 99}; - +// http://seaviewsensing.com/pub/cpt-city/es/rivendell/es_rivendell_15.c3g +const uint8_t es_rivendell_15_gp[] PROGMEM = { + 0, 35, 69, 54, + 101, 88, 105, 82, + 165, 143, 140, 109, + 242, 208, 204, 175, + 255, 208, 204, 175}; // Gradient palette "rgi_15_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/ds/rgi/tn/rgi_15.png.index.html -// converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 36 bytes of program space. -// Edited to be brighter - -const byte rgi_15_gp[] PROGMEM = { - 0, 4, 1, 70, - 31, 55, 1, 30, - 63, 255, 4, 7, - 95, 59, 2, 29, - 127, 11, 3, 50, - 159, 39, 8, 60, - 191, 112, 19, 40, - 223, 78, 11, 39, - 255, 29, 8, 59}; - +// http://seaviewsensing.com/pub/cpt-city/ds/rgi/rgi_15.c3g +const uint8_t rgi_15_gp[] PROGMEM = { + 0, 54, 14, 111, + 31, 142, 24, 86, + 63, 231, 34, 61, + 95, 146, 31, 88, + 127, 61, 29, 114, + 159, 124, 47, 113, + 191, 186, 66, 112, + 223, 143, 57, 116, + 255, 100, 48, 120}; // Gradient palette "retro2_16_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/ma/retro2/tn/retro2_16.png.index.html -// converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 8 bytes of program space. - -const byte retro2_16_gp[] PROGMEM = { - 0, 188,135, 1, - 255, 46, 7, 1}; - +// http://seaviewsensing.com/pub/cpt-city/ma/retro2/retro2_16.c3g +const uint8_t retro2_16_gp[] PROGMEM = { + 0, 227, 191, 12, + 255, 132, 52, 2}; // Gradient palette "Analogous_1_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/red/tn/Analogous_1.png.index.html -// converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 20 bytes of program space. - -const byte Analogous_1_gp[] PROGMEM = { - 0, 3, 0,255, - 63, 23, 0,255, - 127, 67, 0,255, - 191, 142, 0, 45, - 255, 255, 0, 0}; - +// http://seaviewsensing.com/pub/cpt-city/nd/red/Analogous_1.c3g +const uint8_t Analogous_1_gp[] PROGMEM = { + 0, 51, 0, 255, + 63, 102, 0, 255, + 127, 153, 0, 255, + 191, 204, 0, 128, + 255, 255, 0, 0}; // Gradient palette "es_pinksplash_08_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/pink_splash/tn/es_pinksplash_08.png.index.html -// converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 20 bytes of program space. - -const byte es_pinksplash_08_gp[] PROGMEM = { - 0, 126, 11,255, - 127, 197, 1, 22, - 175, 210,157,172, - 221, 157, 3,112, - 255, 157, 3,112}; - +// http://seaviewsensing.com/pub/cpt-city/es/pink_splash/es_pinksplash_08.c3g +const uint8_t es_pinksplash_08_gp[] PROGMEM = { + 0, 195, 63, 255, + 127, 231, 9, 97, + 175, 237, 205, 218, + 221, 212, 38, 184, + 255, 212, 38, 184}; // Gradient palette "es_ocean_breeze_036_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/ocean_breeze/tn/es_ocean_breeze_036.png.index.html -// converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 16 bytes of program space. - -const byte es_ocean_breeze_036_gp[] PROGMEM = { - 0, 1, 6, 7, - 89, 1, 99,111, - 153, 144,209,255, - 255, 0, 73, 82}; - +// http://seaviewsensing.com/pub/cpt-city/es/ocean_breeze/es_ocean_breeze_036.c3g +const uint8_t es_ocean_breeze_036_gp[] PROGMEM = { + 0, 25, 48, 62, + 89, 38, 166, 183, + 153, 205, 233, 255, + 255, 0, 145, 162}; // Gradient palette "departure_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/mjf/tn/departure.png.index.html -// converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 88 bytes of program space. - -const byte departure_gp[] PROGMEM = { - 0, 8, 3, 0, - 42, 23, 7, 0, - 63, 75, 38, 6, - 84, 169, 99, 38, - 106, 213,169,119, - 116, 255,255,255, - 138, 135,255,138, - 148, 22,255, 24, - 170, 0,255, 0, - 191, 0,136, 0, - 212, 0, 55, 0, - 255, 0, 55, 0}; - +// http://seaviewsensing.com/pub/cpt-city/mjf/departure.c3g +const uint8_t departure_gp[] PROGMEM = { + 0, 68, 34, 0, + 42, 102, 51, 0, + 63, 160, 108, 60, + 84, 218, 166, 120, + 106, 238, 212, 188, + 116, 255, 255, 255, + 138, 200, 255, 200, + 148, 100, 255, 100, + 170, 0, 255, 0, + 191, 0, 192, 0, + 212, 0, 128, 0, + 255, 0, 128, 0}; // Gradient palette "es_landscape_64_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/landscape/tn/es_landscape_64.png.index.html -// converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 36 bytes of program space. - -const byte es_landscape_64_gp[] PROGMEM = { - 0, 0, 0, 0, - 37, 2, 25, 1, - 76, 15,115, 5, - 127, 79,213, 1, - 128, 126,211, 47, - 130, 188,209,247, - 153, 144,182,205, - 204, 59,117,250, - 255, 1, 37,192}; - +// http://seaviewsensing.com/pub/cpt-city/es/landscape/es_landscape_64.c3g +const uint8_t es_landscape_64_gp[] PROGMEM = { + 0, 0, 0, 0, + 37, 43, 89, 26, + 76, 87, 178, 53, + 127, 163, 235, 8, + 128, 195, 234, 130, + 130, 227, 233, 252, + 153, 205, 219, 234, + 204, 146, 179, 253, + 255, 39, 107, 228}; // Gradient palette "es_landscape_33_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/landscape/tn/es_landscape_33.png.index.html -// converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 24 bytes of program space. - -const byte es_landscape_33_gp[] PROGMEM = { - 0, 1, 5, 0, - 19, 32, 23, 1, - 38, 161, 55, 1, - 63, 229,144, 1, - 66, 39,142, 74, - 255, 1, 4, 1}; - +// http://seaviewsensing.com/pub/cpt-city/es/landscape/es_landscape_33.c3g +const uint8_t es_landscape_33_gp[] PROGMEM = { + 0, 19, 45, 0, + 19, 116, 86, 3, + 38, 214, 128, 7, + 63, 245, 197, 25, + 66, 124, 196, 156, + 255, 9, 39, 11}; // Gradient palette "rainbowsherbet_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/ma/icecream/tn/rainbowsherbet.png.index.html -// converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 28 bytes of program space. - -const byte rainbowsherbet_gp[] PROGMEM = { - 0, 255, 33, 4, - 43, 255, 68, 25, - 86, 255, 7, 25, - 127, 255, 82,103, - 170, 255,255,242, - 209, 42,255, 22, - 255, 87,255, 65}; - +// http://seaviewsensing.com/pub/cpt-city/ma/icecream/rainbowsherbet.c3g +const uint8_t rainbowsherbet_gp[] PROGMEM = { + 0, 255, 102, 51, + 43, 255, 140, 102, + 86, 255, 51, 102, + 127, 255, 153, 178, + 170, 255, 255, 250, + 209, 128, 255, 97, + 255, 169, 255, 148}; // Gradient palette "gr65_hult_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/hult/tn/gr65_hult.png.index.html -// converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 24 bytes of program space. - -const byte gr65_hult_gp[] PROGMEM = { - 0, 247,176,247, - 48, 255,136,255, - 89, 220, 29,226, - 160, 7, 82,178, - 216, 1,124,109, - 255, 1,124,109}; - +// http://seaviewsensing.com/pub/cpt-city/hult/gr65_hult.c3g +const uint8_t gr65_hult_gp[] PROGMEM = { + 0, 252, 216, 252, + 48, 255, 192, 255, + 89, 241, 95, 243, + 160, 65, 153, 221, + 216, 34, 184, 182, + 255, 34, 184, 182}; // Gradient palette "gr64_hult_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/hult/tn/gr64_hult.png.index.html -// converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 32 bytes of program space. - -const byte gr64_hult_gp[] PROGMEM = { - 0, 1,124,109, - 66, 1, 93, 79, - 104, 52, 65, 1, - 130, 115,127, 1, - 150, 52, 65, 1, - 201, 1, 86, 72, - 239, 0, 55, 45, - 255, 0, 55, 45}; - +// http://seaviewsensing.com/pub/cpt-city/hult/gr64_hult.c3g +const uint8_t gr64_hult_gp[] PROGMEM = { + 0, 34, 184, 182, + 66, 14, 162, 160, + 104, 139, 137, 11, + 130, 188, 186, 30, + 150, 139, 137, 11, + 201, 10, 156, 154, + 239, 0, 128, 128, + 255, 0, 128, 128}; // Gradient palette "GMT_drywet_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/gmt/tn/GMT_drywet.png.index.html -// converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 28 bytes of program space. - -const byte GMT_drywet_gp[] PROGMEM = { - 0, 47, 30, 2, - 42, 213,147, 24, - 84, 103,219, 52, - 127, 3,219,207, - 170, 1, 48,214, - 212, 1, 1,111, - 255, 1, 7, 33}; - +// http://seaviewsensing.com/pub/cpt-city/gmt/GMT_drywet.c3g +const uint8_t GMT_drywet_gp[] PROGMEM = { + 0, 134, 97, 42, + 42, 238, 199, 100, + 84, 180, 238, 135, + 127, 50, 238, 235, + 170, 12, 120, 238, + 212, 38, 1, 183, + 255, 8, 51, 113}; // Gradient palette "ib15_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/ing/general/tn/ib15.png.index.html -// converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 24 bytes of program space. - -const byte ib15_gp[] PROGMEM = { - 0, 113, 91,147, - 72, 157, 88, 78, - 89, 208, 85, 33, - 107, 255, 29, 11, - 141, 137, 31, 39, - 255, 59, 33, 89}; - +// http://seaviewsensing.com/pub/cpt-city/ing/general/ib15.c3g +const uint8_t ib15_gp[] PROGMEM = { + 0, 187, 160, 205, + 72, 212, 158, 159, + 89, 236, 155, 113, + 107, 255, 95, 74, + 141, 201, 98, 121, + 255, 146, 101, 168}; // Gradient palette "Tertiary_01_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/vermillion/tn/Tertiary_01.png.index.html -// converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 20 bytes of program space. - -const byte Tertiary_01_gp[] PROGMEM = { - 0, 0, 1,255, - 63, 3, 68, 45, - 127, 23,255, 0, - 191, 100, 68, 1, - 255, 255, 1, 4}; - +// http://seaviewsensing.com/pub/cpt-city/nd/vermillion/Tertiary_01.c3g +const uint8_t Tertiary_01_gp[] PROGMEM = { + 0, 0, 25, 255, + 63, 51, 140, 128, + 127, 102, 255, 0, + 191, 178, 140, 26, + 255, 255, 25, 51}; // Gradient palette "lava_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/neota/elem/tn/lava.png.index.html -// converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 52 bytes of program space. - -const byte lava_gp[] PROGMEM = { - 0, 0, 0, 0, - 46, 18, 0, 0, - 96, 113, 0, 0, - 108, 142, 3, 1, - 119, 175, 17, 1, - 146, 213, 44, 2, - 174, 255, 82, 4, - 188, 255,115, 4, - 202, 255,156, 4, - 218, 255,203, 4, - 234, 255,255, 4, - 244, 255,255, 71, - 255, 255,255,255}; - - -// Gradient palette "fierce_ice_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/neota/elem/tn/fierce-ice.png.index.html -// converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 28 bytes of program space. - -const byte fierce_ice_gp[] PROGMEM = { - 0, 0, 0, 0, - 59, 0, 9, 45, - 119, 0, 38,255, - 149, 3,100,255, - 180, 23,199,255, - 217, 100,235,255, - 255, 255,255,255}; - +// http://seaviewsensing.com/pub/cpt-city/neota/elem/lava.c3g +const uint8_t lava_gp[] PROGMEM = { + 0, 0, 0, 0, + 46, 93, 0, 0, + 96, 187, 0, 0, + 108, 204, 38, 13, + 119, 221, 76, 26, + 146, 238, 115, 38, + 174, 255, 153, 51, + 188, 255, 178, 51, + 202, 255, 204, 51, + 218, 255, 230, 51, + 234, 255, 255, 51, + 244, 255, 255, 153, + 255, 255, 255, 255}; + +// Gradient palette "fierce-ice_gp", originally from +// http://seaviewsensing.com/pub/cpt-city/neota/elem/fierce-ice.c3g +const uint8_t fierce_ice_gp[] PROGMEM = { + 0, 0, 0, 0, + 59, 0, 51, 128, + 119, 0, 102, 255, + 149, 51, 153, 255, + 180, 102, 204, 255, + 217, 178, 230, 255, + 255, 255, 255, 255}; // Gradient palette "Colorfull_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/atmospheric/tn/Colorfull.png.index.html -// converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 44 bytes of program space. - -const byte Colorfull_gp[] PROGMEM = { - 0, 10, 85, 5, - 25, 29,109, 18, - 60, 59,138, 42, - 93, 83, 99, 52, - 106, 110, 66, 64, - 109, 123, 49, 65, - 113, 139, 35, 66, - 116, 192,117, 98, - 124, 255,255,137, - 168, 100,180,155, - 255, 22,121,174}; - +// http://seaviewsensing.com/pub/cpt-city/nd/atmospheric/Colorfull.c3g +const uint8_t Colorfull_gp[] PROGMEM = { + 0, 76, 155, 54, + 25, 111, 174, 89, + 60, 146, 193, 125, + 93, 166, 166, 136, + 106, 185, 138, 147, + 109, 193, 121, 148, + 113, 202, 104, 149, + 116, 229, 179, 174, + 124, 255, 255, 199, + 168, 178, 218, 209, + 255, 100, 182, 219}; // Gradient palette "Pink_Purple_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/atmospheric/tn/Pink_Purple.png.index.html -// converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 44 bytes of program space. - -const byte Pink_Purple_gp[] PROGMEM = { - 0, 19, 2, 39, - 25, 26, 4, 45, - 51, 33, 6, 52, - 76, 68, 62,125, - 102, 118,187,240, - 109, 163,215,247, - 114, 217,244,255, - 122, 159,149,221, - 149, 113, 78,188, - 183, 128, 57,155, - 255, 146, 40,123}; - +// http://seaviewsensing.com/pub/cpt-city/nd/atmospheric/Pink_Purple.c3g +const uint8_t Pink_Purple_gp[] PROGMEM = { + 0, 95, 32, 121, + 25, 106, 40, 128, + 51, 117, 48, 135, + 76, 154, 135, 192, + 102, 190, 222, 249, + 109, 215, 236, 252, + 114, 240, 250, 255, + 122, 213, 200, 241, + 149, 187, 149, 226, + 183, 196, 130, 209, + 255, 206, 111, 191}; // Gradient palette "Sunset_Real_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/atmospheric/tn/Sunset_Real.png.index.html -// converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 28 bytes of program space. - -const byte Sunset_Real_gp[] PROGMEM = { - 0, 120, 0, 0, - 22, 179, 22, 0, - 51, 255,104, 0, - 85, 167, 22, 18, - 135, 100, 0,103, - 198, 16, 0,130, - 255, 0, 0,160}; - +// http://seaviewsensing.com/pub/cpt-city/nd/atmospheric/Sunset_Real.c3g +const uint8_t Sunset_Real_gp[] PROGMEM = { + 0, 191, 0, 0, + 22, 223, 85, 0, + 51, 255, 170, 0, + 85, 217, 85, 89, + 135, 178, 0, 178, + 198, 89, 0, 195, + 255, 0, 0, 212}; // Gradient palette "Sunset_Yellow_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/atmospheric/tn/Sunset_Yellow.png.index.html -// converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 44 bytes of program space. - -const byte Sunset_Yellow_gp[] PROGMEM = { - 0, 10, 62,123, - 36, 56,130,103, - 87, 153,225, 85, - 100, 199,217, 68, - 107, 255,207, 54, - 115, 247,152, 57, - 120, 239,107, 61, - 128, 247,152, 57, - 180, 255,207, 54, - 223, 255,227, 48, - 255, 255,248, 42}; - +// http://seaviewsensing.com/pub/cpt-city/nd/atmospheric/Sunset_Yellow.c3g +const uint8_t Sunset_Yellow_gp[] PROGMEM = { + 0, 76, 135, 191, + 36, 143, 188, 178, + 87, 210, 241, 165, + 100, 232, 237, 151, + 107, 255, 232, 138, + 115, 252, 202, 141, + 120, 249, 172, 144, + 128, 252, 202, 141, + 180, 255, 232, 138, + 223, 255, 242, 131, + 255, 255, 252, 125}; // Gradient palette "Beech_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/atmospheric/tn/Beech.png.index.html -// converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 60 bytes of program space. - -const byte Beech_gp[] PROGMEM = { - 0, 255,252,214, - 12, 255,252,214, - 22, 255,252,214, - 26, 190,191,115, - 28, 137,141, 52, - 28, 112,255,205, - 50, 51,246,214, - 71, 17,235,226, - 93, 2,193,199, - 120, 0,156,174, - 133, 1,101,115, - 136, 1, 59, 71, - 136, 7,131,170, - 208, 1, 90,151, - 255, 0, 56,133}; - +// http://seaviewsensing.com/pub/cpt-city/nd/atmospheric/Beech.c3g +const uint8_t Beech_gp[] PROGMEM = { + 0, 255, 254, 238, + 12, 255, 254, 238, + 22, 255, 254, 238, + 26, 228, 224, 186, + 28, 201, 195, 135, + 28, 186, 255, 234, + 50, 138, 251, 238, + 71, 90, 246, 243, + 93, 45, 225, 231, + 120, 0, 204, 219, + 133, 8, 168, 186, + 136, 16, 132, 153, + 136, 65, 189, 217, + 208, 33, 159, 207, + 255, 0, 129, 197}; // Gradient palette "Another_Sunset_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/atmospheric/tn/Another_Sunset.png.index.html -// converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 32 bytes of program space. - -const byte Another_Sunset_gp[] PROGMEM = { - 0, 110, 49, 11, - 29, 55, 34, 10, - 68, 22, 22, 9, - 68, 239,124, 8, - 97, 220,156, 27, - 124, 203,193, 61, - 178, 33, 53, 56, - 255, 0, 1, 52}; - - - - +// http://seaviewsensing.com/pub/cpt-city/nd/atmospheric/Another_Sunset.c3g +const uint8_t Another_Sunset_gp[] PROGMEM = { + 0, 185, 121, 73, + 29, 142, 103, 71, + 68, 100, 84, 69, + 68, 249, 184, 66, + 97, 241, 204, 105, + 124, 234, 225, 144, + 178, 117, 125, 140, + 255, 0, 26, 136}; // Gradient palette "es_autumn_19_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/es/autumn/tn/es_autumn_19.png.index.html -// converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 52 bytes of program space. - -const byte es_autumn_19_gp[] PROGMEM = { - 0, 26, 1, 1, - 51, 67, 4, 1, - 84, 118, 14, 1, - 104, 137,152, 52, - 112, 113, 65, 1, - 122, 133,149, 59, - 124, 137,152, 52, - 135, 113, 65, 1, - 142, 139,154, 46, - 163, 113, 13, 1, - 204, 55, 3, 1, - 249, 17, 1, 1, - 255, 17, 1, 1}; - +// http://seaviewsensing.com/pub/cpt-city/es/autumn/es_autumn_19.c3g +const uint8_t es_autumn_19_gp[] PROGMEM = { + 0, 106, 14, 8, + 51, 153, 41, 19, + 84, 190, 70, 24, + 104, 201, 202, 136, + 112, 187, 137, 5, + 122, 199, 200, 142, + 124, 201, 202, 135, + 135, 187, 137, 5, + 142, 202, 203, 129, + 163, 187, 68, 24, + 204, 142, 35, 17, + 249, 90, 5, 4, + 255, 90, 5, 4}; // Gradient palette "BlacK_Blue_Magenta_White_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/basic/tn/BlacK_Blue_Magenta_White.png.index.html -// converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 28 bytes of program space. - -const byte BlacK_Blue_Magenta_White_gp[] PROGMEM = { - 0, 0, 0, 0, - 42, 0, 0, 45, - 84, 0, 0,255, - 127, 42, 0,255, - 170, 255, 0,255, - 212, 255, 55,255, - 255, 255,255,255}; - +// http://seaviewsensing.com/pub/cpt-city/nd/basic/BlacK_Blue_Magenta_White.c3g +const uint8_t BlacK_Blue_Magenta_White_gp[] PROGMEM = { + 0, 0, 0, 0, + 42, 0, 0, 128, + 84, 0, 0, 255, + 127, 128, 0, 255, + 170, 255, 0, 255, + 212, 255, 128, 255, + 255, 255, 255, 255}; // Gradient palette "BlacK_Magenta_Red_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/basic/tn/BlacK_Magenta_Red.png.index.html -// converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 20 bytes of program space. - -const byte BlacK_Magenta_Red_gp[] PROGMEM = { - 0, 0, 0, 0, - 63, 42, 0, 45, - 127, 255, 0,255, - 191, 255, 0, 45, - 255, 255, 0, 0}; - +// http://seaviewsensing.com/pub/cpt-city/nd/basic/BlacK_Magenta_Red.c3g +const uint8_t BlacK_Magenta_Red_gp[] PROGMEM = { + 0, 0, 0, 0, + 63, 128, 0, 128, + 127, 255, 0, 255, + 191, 255, 0, 128, + 255, 255, 0, 0}; // Gradient palette "BlacK_Red_Magenta_Yellow_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/basic/tn/BlacK_Red_Magenta_Yellow.png.index.html -// converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 28 bytes of program space. - -const byte BlacK_Red_Magenta_Yellow_gp[] PROGMEM = { - 0, 0, 0, 0, - 42, 42, 0, 0, - 84, 255, 0, 0, - 127, 255, 0, 45, - 170, 255, 0,255, - 212, 255, 55, 45, - 255, 255,255, 0}; - +// http://seaviewsensing.com/pub/cpt-city/nd/basic/BlacK_Red_Magenta_Yellow.c3g +const uint8_t BlacK_Red_Magenta_Yellow_gp[] PROGMEM = { + 0, 0, 0, 0, + 42, 128, 0, 0, + 84, 255, 0, 0, + 127, 255, 0, 128, + 170, 255, 0, 255, + 212, 255, 128, 128, + 255, 255, 255, 0}; // Gradient palette "Blue_Cyan_Yellow_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/nd/basic/tn/Blue_Cyan_Yellow.png.index.html -// converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 20 bytes of program space. - -const byte Blue_Cyan_Yellow_gp[] PROGMEM = { - 0, 0, 0,255, - 63, 0, 55,255, - 127, 0,255,255, - 191, 42,255, 45, - 255, 255,255, 0}; - +// http://seaviewsensing.com/pub/cpt-city/nd/basic/Blue_Cyan_Yellow.c3g +const uint8_t Blue_Cyan_Yellow_gp[] PROGMEM = { + 0, 0, 0, 255, + 63, 0, 128, 255, + 127, 0, 255, 255, + 191, 128, 255, 128, + 255, 255, 255, 0}; //Custom palette by Aircoookie - const byte Orange_Teal_gp[] PROGMEM = { 0, 0,150, 92, 55, 0,150, 92, @@ -502,7 +373,6 @@ const byte Orange_Teal_gp[] PROGMEM = { 255, 255, 72, 0}; //Custom palette by Aircoookie - const byte Tiamat_gp[] PROGMEM = { 0, 1, 2, 14, //gc 33, 2, 5, 35, //gc from 47, 61,126 @@ -517,7 +387,6 @@ const byte Tiamat_gp[] PROGMEM = { 255, 255,249,255}; //Custom palette by Aircoookie - const byte April_Night_gp[] PROGMEM = { 0, 1, 5, 45, //deep blue 10, 1, 5, 45, @@ -585,271 +454,215 @@ const byte Atlantica_gp[] PROGMEM = { const byte C9_2_gp[] PROGMEM = { 0, 6, 126, 2, //green 45, 6, 126, 2, - 45, 4, 30, 114, //blue + 46, 4, 30, 114, //blue 90, 4, 30, 114, - 90, 255, 5, 0, //red + 91, 255, 5, 0, //red 135, 255, 5, 0, - 135, 196, 57, 2, //amber + 136, 196, 57, 2, //amber 180, 196, 57, 2, - 180, 137, 85, 2, //yellow + 181, 137, 85, 2, //yellow 255, 137, 85, 2}; //C9, but brighter and with a less purple blue const byte C9_new_gp[] PROGMEM = { 0, 255, 5, 0, //red 60, 255, 5, 0, - 60, 196, 57, 2, //amber (start 61?) + 61, 196, 57, 2, //amber (start 61?) 120, 196, 57, 2, - 120, 6, 126, 2, //green (start 126?) + 121, 6, 126, 2, //green (start 126?) 180, 6, 126, 2, - 180, 4, 30, 114, //blue (start 191?) + 181, 4, 30, 114, //blue (start 191?) 255, 4, 30, 114}; // Gradient palette "temperature_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/arendal/tn/temperature.png.index.html -// converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 144 bytes of program space. - -const byte temperature_gp[] PROGMEM = { - 0, 1, 27,105, - 14, 1, 40,127, - 28, 1, 70,168, - 42, 1, 92,197, - 56, 1,119,221, - 70, 3,130,151, - 84, 23,156,149, - 99, 67,182,112, - 113, 121,201, 52, - 127, 142,203, 11, - 141, 224,223, 1, - 155, 252,187, 2, - 170, 247,147, 1, - 184, 237, 87, 1, - 198, 229, 43, 1, - 226, 171, 2, 2, - 240, 80, 3, 3, - 255, 80, 3, 3}; - - const byte Aurora2_gp[] PROGMEM = { - 0, 17, 177, 13, //Greenish - 64, 121, 242, 5, //Greenish - 128, 25, 173, 121, //Turquoise - 192, 250, 77, 127, //Pink - 255, 171, 101, 221 //Purple - }; - - // Gradient palette "bhw1_01_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw1/tn/bhw1_01.png.index.html -// converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 12 bytes of program space. - -const byte retro_clown_gp[] PROGMEM = { - 0, 227,101, 3, - 117, 194, 18, 19, - 255, 92, 8,192}; +// http://seaviewsensing.com/pub/cpt-city/arendal/temperature.c3g +const uint8_t temperature_gp[] PROGMEM = { + 0, 30, 92, 179, + 14, 23, 111, 193, + 28, 11, 142, 216, + 42, 4, 161, 230, + 56, 25, 181, 241, + 70, 51, 188, 207, + 84, 102, 204, 206, + 99, 153, 219, 184, + 113, 192, 229, 136, + 127, 204, 230, 75, + 141, 243, 240, 29, + 155, 254, 222, 39, + 170, 252, 199, 7, + 184, 248, 157, 14, + 198, 245, 114, 21, + 226, 219, 30, 38, + 240, 164, 38, 44, + 255, 164, 38, 44}; + +// Gradient palette "bhw1_01_gp", originally from +// http://seaviewsensing.com/pub/cpt-city/bhw/bhw1/bhw1_01.c3g +const uint8_t retro_clown_gp[] PROGMEM = { + 0, 244, 168, 48, + 117, 230, 78, 92, + 255, 173, 54, 228}; // Gradient palette "bhw1_04_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw1/tn/bhw1_04.png.index.html -// converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 20 bytes of program space. - -const byte candy_gp[] PROGMEM = { - 0, 229,227, 1, - 15, 227,101, 3, - 142, 40, 1, 80, - 198, 17, 1, 79, - 255, 0, 0, 45}; +// http://seaviewsensing.com/pub/cpt-city/bhw/bhw1/bhw1_04.c3g +const uint8_t candy_gp[] PROGMEM = { + 0, 245, 242, 31, + 15, 244, 168, 48, + 142, 126, 21, 161, + 198, 90, 22, 160, + 255, 0, 0, 128}; // Gradient palette "bhw1_05_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw1/tn/bhw1_05.png.index.html -// converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 8 bytes of program space. - -const byte toxy_reaf_gp[] PROGMEM = { - 0, 1,221, 53, - 255, 73, 3,178}; +// http://seaviewsensing.com/pub/cpt-city/bhw/bhw1/bhw1_05.c3g +const uint8_t toxy_reaf_gp[] PROGMEM = { + 0, 5, 239, 137, + 255, 158, 35, 221}; // Gradient palette "bhw1_06_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw1/tn/bhw1_06.png.index.html -// converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 16 bytes of program space. - -const byte fairy_reaf_gp[] PROGMEM = { - 0, 184, 1,128, - 160, 1,193,182, - 219, 153,227,190, - 255, 255,255,255}; +// http://seaviewsensing.com/pub/cpt-city/bhw/bhw1/bhw1_06.c3g +const uint8_t fairy_reaf_gp[] PROGMEM = { + 0, 225, 19, 194, + 160, 19, 225, 223, + 219, 210, 242, 227, + 255, 255, 255, 255}; // Gradient palette "bhw1_14_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw1/tn/bhw1_14.png.index.html -// converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 36 bytes of program space. - -const byte semi_blue_gp[] PROGMEM = { - 0, 0, 0, 0, - 12, 1, 1, 3, - 53, 8, 1, 22, - 80, 4, 6, 89, - 119, 2, 25,216, - 145, 7, 10, 99, - 186, 15, 2, 31, - 233, 2, 1, 5, - 255, 0, 0, 0}; +// http://seaviewsensing.com/pub/cpt-city/bhw/bhw1/bhw1_14.c3g +const uint8_t semi_blue_gp[] PROGMEM = { + 0, 0, 0, 0, + 12, 35, 4, 48, + 53, 70, 8, 96, + 80, 56, 48, 168, + 119, 43, 89, 239, + 145, 64, 59, 175, + 186, 86, 30, 110, + 233, 43, 15, 55, + 255, 0, 0, 0}; // Gradient palette "bhw1_three_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw1/tn/bhw1_three.png.index.html -// converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 32 bytes of program space. - -const byte pink_candy_gp[] PROGMEM = { - 0, 255,255,255, - 45, 7, 12,255, - 112, 227, 1,127, - 112, 227, 1,127, - 140, 255,255,255, - 155, 227, 1,127, - 196, 45, 1, 99, - 255, 255,255,255}; +// http://seaviewsensing.com/pub/cpt-city/bhw/bhw1/bhw1_three.c3g +const uint8_t pink_candy_gp[] PROGMEM = { + 0, 255, 255, 255, + 45, 64, 64, 255, + 112, 244, 16, 193, + 140, 255, 255, 255, + 155, 244, 16, 193, + 196, 131, 13, 175, + 255, 255, 255, 255}; // Gradient palette "bhw1_w00t_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw1/tn/bhw1_w00t.png.index.html -// converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 16 bytes of program space. - -const byte red_reaf_gp[] PROGMEM = { - 0, 3, 13, 43, - 104, 78,141,240, - 188, 255, 0, 0, - 255, 28, 1, 1}; - +// http://seaviewsensing.com/pub/cpt-city/bhw/bhw1/bhw1_w00t.c3g +const uint8_t red_reaf_gp[] PROGMEM = { + 0, 49, 68, 126, + 104, 162, 195, 249, + 188, 255, 0, 0, + 255, 110, 14, 14}; // Gradient palette "bhw2_23_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw2/tn/bhw2_23.png.index.html -// converted for FastLED with gammas (2.6, 2.2, 2.5) -// Red & Flash in SR -// Size: 28 bytes of program space. - -const byte aqua_flash_gp[] PROGMEM = { - 0, 0, 0, 0, - 66, 57,227,233, - 96, 255,255, 8, - 124, 255,255,255, - 153, 255,255, 8, - 188, 57,227,233, - 255, 0, 0, 0}; +// http://seaviewsensing.com/pub/cpt-city/bhw/bhw2/bhw2_23.c3g +const uint8_t aqua_flash_gp[] PROGMEM = { + 0, 0, 0, 0, + 66, 144, 242, 246, + 96, 255, 255, 64, + 124, 255, 255, 255, + 153, 255, 255, 64, + 188, 144, 242, 246, + 255, 0, 0, 0}; // Gradient palette "bhw2_xc_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw2/tn/bhw2_xc.png.index.html -// converted for FastLED with gammas (2.6, 2.2, 2.5) -// YBlue in SR -// Size: 28 bytes of program space. - -const byte yelblu_hot_gp[] PROGMEM = { - 0, 4, 2, 9, - 58, 16, 0, 47, - 122, 24, 0, 16, - 158, 144, 9, 1, - 183, 179, 45, 1, - 219, 220,114, 2, - 255, 234,237, 1}; - - // Gradient palette "bhw2_45_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw2/tn/bhw2_45.png.index.html -// converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 24 bytes of program space. - -const byte lite_light_gp[] PROGMEM = { - 0, 0, 0, 0, - 9, 1, 1, 1, - 40, 5, 5, 6, - 66, 5, 5, 6, - 101, 10, 1, 12, - 255, 0, 0, 0}; +// http://seaviewsensing.com/pub/cpt-city/bhw/bhw2/bhw2_xc.c3g +const uint8_t yelblu_hot_gp[] PROGMEM = { + 0, 56, 30, 68, + 58, 89, 0, 130, + 122, 103, 0, 86, + 158, 205, 57, 29, + 183, 223, 117, 35, + 219, 241, 177, 41, + 255, 247, 247, 35}; + +// Gradient palette "bhw2_45_gp", originally from +// http://seaviewsensing.com/pub/cpt-city/bhw/bhw2/bhw2_45.c3g +const uint8_t lite_light_gp[] PROGMEM = { + 0, 0, 0, 0, + 9, 30, 21, 30, + 40, 60, 43, 60, + 66, 60, 43, 60, + 101, 76, 16, 77, + 255, 0, 0, 0}; // Gradient palette "bhw2_22_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw2/tn/bhw2_22.png.index.html -// converted for FastLED with gammas (2.6, 2.2, 2.5) -// Pink Plasma in SR -// Size: 20 bytes of program space. - -const byte red_flash_gp[] PROGMEM = { - 0, 0, 0, 0, - 99, 227, 1, 1, - 130, 249,199, 95, - 155, 227, 1, 1, - 255, 0, 0, 0}; +// http://seaviewsensing.com/pub/cpt-city/bhw/bhw2/bhw2_22.c3g +const uint8_t red_flash_gp[] PROGMEM = { + 0, 0, 0, 0, + 99, 244, 12, 12, + 130, 253, 228, 172, + 155, 244, 12, 12, + 255, 0, 0, 0}; // Gradient palette "bhw3_40_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw3/tn/bhw3_40.png.index.html -// converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 32 bytes of program space. - -const byte blink_red_gp[] PROGMEM = { - 0, 1, 1, 1, - 43, 4, 1, 11, - 76, 10, 1, 3, - 109, 161, 4, 29, - 127, 255, 86,123, - 165, 125, 16,160, - 204, 35, 13,223, - 255, 18, 2, 18}; +// http://seaviewsensing.com/pub/cpt-city/bhw/bhw3/bhw3_40.c3g +const uint8_t blink_red_gp[] PROGMEM = { + 0, 7, 7, 7, + 43, 53, 25, 73, + 76, 76, 15, 46, + 109, 214, 39, 108, + 127, 255, 156, 191, + 165, 194, 73, 212, + 204, 120, 66, 242, + 255, 93, 29, 90}; // Gradient palette "bhw3_52_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw3/tn/bhw3_52.png.index.html -// converted for FastLED with gammas (2.6, 2.2, 2.5) -// Yellow2Blue in SR -// Size: 28 bytes of program space. - -const byte red_shift_gp[] PROGMEM = { - 0, 31, 1, 27, - 45, 34, 1, 16, - 99, 137, 5, 9, - 132, 213,128, 10, - 175, 199, 22, 1, - 201, 199, 9, 6, - 255, 1, 0, 1}; +// http://seaviewsensing.com/pub/cpt-city/bhw/bhw3/bhw3_52.c3g +const uint8_t red_shift_gp[] PROGMEM = { + 0, 114, 22, 105, + 45, 118, 22, 85, + 99, 201, 45, 67, + 132, 238, 187, 70, + 175, 232, 85, 34, + 201, 232, 56, 59, + 255, 5, 0, 4}; // Gradient palette "bhw4_097_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw4/tn/bhw4_097.png.index.html -// converted for FastLED with gammas (2.6, 2.2, 2.5) -// Yellow2Red in SR -// Size: 44 bytes of program space. - -const byte red_tide_gp[] PROGMEM = { - 0, 247, 5, 0, - 28, 255, 67, 1, - 43, 234, 88, 11, - 58, 234,176, 51, - 84, 229, 28, 1, - 114, 113, 12, 1, - 140, 255,225, 44, - 168, 113, 12, 1, - 196, 244,209, 88, - 216, 255, 28, 1, - 255, 53, 1, 1}; +// http://seaviewsensing.com/pub/cpt-city/bhw/bhw4/bhw4_097.c3g +const uint8_t red_tide_gp[] PROGMEM = { + 0, 252, 46, 0, + 28, 255, 139, 33, + 43, 247, 158, 74, + 58, 247, 216, 134, + 84, 245, 94, 15, + 114, 187, 65, 16, + 140, 255, 241, 127, + 168, 187, 65, 16, + 196, 251, 233, 167, + 216, 255, 94, 9, + 255, 140, 8, 6}; // Gradient palette "bhw4_017_gp", originally from -// http://soliton.vm.bytemark.co.uk/pub/cpt-city/bhw/bhw4/tn/bhw4_017.png.index.html -// converted for FastLED with gammas (2.6, 2.2, 2.5) -// Size: 40 bytes of program space. - -const byte candy2_gp[] PROGMEM = { - 0, 39, 33, 34, - 25, 4, 6, 15, - 48, 49, 29, 22, - 73, 224,173, 1, - 89, 177, 35, 5, - 130, 4, 6, 15, - 163, 255,114, 6, - 186, 224,173, 1, - 211, 39, 33, 34, - 255, 1, 1, 1}; +// http://seaviewsensing.com/pub/cpt-city/bhw/bhw4/bhw4_017.c3g +const uint8_t candy2_gp[] PROGMEM = { + 0, 124, 102, 114, + 25, 55, 49, 83, + 48, 136, 96, 96, + 73, 243, 214, 34, + 89, 222, 104, 54, + 130, 55, 49, 83, + 163, 255, 177, 58, + 186, 243, 214, 34, + 211, 124, 102, 114, + 255, 29, 19, 18}; const byte trafficlight_gp[] PROGMEM = { - 0, 0, 0, 0, //black - 85, 0, 255, 0, //green - 170, 255, 255, 0, //yellow - 255, 255, 0, 0}; //red + 0, 0, 0, 0, //black + 85, 0, 255, 0, //green + 170, 255, 255, 0, //yellow + 255, 255, 0, 0}; //red + +const byte Aurora2_gp[] PROGMEM = { + 0, 17, 177, 13, //Greenish + 64, 121, 242, 5, //Greenish + 128, 25, 173, 121, //Turquoise + 192, 250, 77, 127, //Pink + 255, 171, 101, 221}; //Purple // array of fastled palettes (palette 6 - 12) const TProgmemRGBPalette16 *const fastledPalettes[] PROGMEM = { @@ -866,7 +679,7 @@ const TProgmemRGBPalette16 *const fastledPalettes[] PROGMEM = { // This will let us programmatically choose one based on // a number, rather than having to activate each explicitly // by name every time. -const byte* const gGradientPalettes[] PROGMEM = { +const uint8_t* const gGradientPalettes[] PROGMEM = { Sunset_Real_gp, //13-00 Sunset es_rivendell_15_gp, //14-01 Rivendell es_ocean_breeze_036_gp, //15-02 Breeze diff --git a/wled00/pin_manager.cpp b/wled00/pin_manager.cpp index 6f16523010..cdbb852670 100644 --- a/wled00/pin_manager.cpp +++ b/wled00/pin_manager.cpp @@ -1,5 +1,5 @@ -#include "pin_manager.h" #include "wled.h" +#include "pin_manager.h" #ifdef ARDUINO_ARCH_ESP32 #ifdef bitRead diff --git a/wled00/pin_manager.h b/wled00/pin_manager.h index b285b6ee5d..662e499b2a 100644 --- a/wled00/pin_manager.h +++ b/wled00/pin_manager.h @@ -3,11 +3,6 @@ /* * Registers pins so there is no attempt for two interfaces to use the same pin */ -#include -#ifdef ARDUINO_ARCH_ESP32 -#include "driver/ledc.h" // needed for analog/LEDC channel counts -#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) diff --git a/wled00/presets.cpp b/wled00/presets.cpp index b749289bd8..fed2c1ed92 100644 --- a/wled00/presets.cpp +++ b/wled00/presets.cpp @@ -29,8 +29,9 @@ bool presetNeedsSaving() { static void doSaveState() { bool persist = (presetToSave < 251); - unsigned long start = millis(); - while (strip.isUpdating() && millis()-start < (2*FRAMETIME_FIXED)+1) yield(); // wait 2 frames + unsigned long maxWait = millis() + strip.getFrameTime(); + while (strip.isUpdating() && millis() < maxWait) delay(1); // wait for strip to finish updating, accessing FS during sendout causes glitches + if (!requestJSONBufferLock(10)) return; initPresetsFile(); // just in case if someone deleted presets.json using /edit @@ -56,14 +57,10 @@ static void doSaveState() { */ #if defined(ARDUINO_ARCH_ESP32) if (!persist) { - if (tmpRAMbuffer!=nullptr) free(tmpRAMbuffer); + p_free(tmpRAMbuffer); size_t len = measureJson(*pDoc) + 1; - DEBUG_PRINTLN(len); // if possible use SPI RAM on ESP32 - if (psramSafe && psramFound()) - tmpRAMbuffer = (char*) ps_malloc(len); - else - tmpRAMbuffer = (char*) malloc(len); + tmpRAMbuffer = (char*)p_malloc(len); if (tmpRAMbuffer!=nullptr) { serializeJson(*pDoc, tmpRAMbuffer, len); } else { @@ -80,8 +77,8 @@ static void doSaveState() { // clean up saveLedmap = -1; presetToSave = 0; - free(saveName); - free(quickLoad); + p_free(saveName); + p_free(quickLoad); saveName = nullptr; quickLoad = nullptr; playlistSave = false; @@ -168,9 +165,9 @@ void handlePresets() DEBUG_PRINTF_P(PSTR("Applying preset: %u\n"), (unsigned)tmpPreset); - #if defined(ARDUINO_ARCH_ESP32S3) || defined(ARDUINO_ARCH_ESP32S2) || defined(ARDUINO_ARCH_ESP32C3) - unsigned long start = millis(); - while (strip.isUpdating() && millis() - start < FRAMETIME_FIXED) yield(); // wait for strip to finish updating, accessing FS during sendout causes glitches + #if defined(ARDUINO_ARCH_ESP32S2) || defined(ARDUINO_ARCH_ESP32C3) + unsigned long maxWait = millis() + strip.getFrameTime(); + while (strip.isUpdating() && millis() < maxWait) delay(1); // wait for strip to finish updating, accessing FS during sendout causes glitches #endif #ifdef ARDUINO_ARCH_ESP32 @@ -206,7 +203,7 @@ void handlePresets() #if defined(ARDUINO_ARCH_ESP32) //Aircoookie recommended not to delete buffer if (tmpPreset==255 && tmpRAMbuffer!=nullptr) { - free(tmpRAMbuffer); + p_free(tmpRAMbuffer); tmpRAMbuffer = nullptr; } #endif @@ -220,8 +217,8 @@ void handlePresets() //called from handleSet(PS=) [network callback (sObj is empty), IR (irrational), deserializeState, UDP] and deserializeState() [network callback (filedoc!=nullptr)] void savePreset(byte index, const char* pname, JsonObject sObj) { - if (!saveName) saveName = static_cast(malloc(33)); - if (!quickLoad) quickLoad = static_cast(malloc(9)); + if (!saveName) saveName = static_cast(p_malloc(33)); + if (!quickLoad) quickLoad = static_cast(p_malloc(9)); if (!saveName || !quickLoad) return; if (index == 0 || (index > 250 && index < 255)) return; @@ -267,8 +264,8 @@ void savePreset(byte index, const char* pname, JsonObject sObj) presetsModifiedTime = toki.second(); //unix time updateFSInfo(); } - free(saveName); - free(quickLoad); + p_free(saveName); + p_free(quickLoad); saveName = nullptr; quickLoad = nullptr; } else { diff --git a/wled00/set.cpp b/wled00/set.cpp index c817f2553c..018d349bcc 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -28,7 +28,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) char gw[5] = "GW"; gw[2] = 48+n; gw[4] = 0; //GW address char sn[5] = "SN"; sn[2] = 48+n; sn[4] = 0; //subnet mask if (request->hasArg(cs)) { - if (n >= multiWiFi.size()) multiWiFi.push_back(WiFiConfig()); // expand vector by one + if (n >= multiWiFi.size()) multiWiFi.emplace_back(); // expand vector by one char oldSSID[33]; strcpy(oldSSID, multiWiFi[n].clientSSID); char oldPass[65]; strcpy(oldPass, multiWiFi[n].clientPass); @@ -129,6 +129,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) unsigned length, start, maMax; uint8_t pins[5] = {255, 255, 255, 255, 255}; + // this will set global ABL max current used when per-port ABL is not used unsigned ablMilliampsMax = request->arg(F("MA")).toInt(); BusManager::setMilliampsMax(ablMilliampsMax); @@ -136,10 +137,10 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) strip.correctWB = request->hasArg(F("CCT")); strip.cctFromRgb = request->hasArg(F("CR")); cctICused = request->hasArg(F("IC")); - Bus::setCCTBlend(request->arg(F("CB")).toInt()); + 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")); #if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3) useParallelI2S = request->hasArg(F("PR")); #endif @@ -207,12 +208,12 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) maMax = 0; } else { maPerLed = request->arg(la).toInt(); - maMax = request->arg(ma).toInt(); // if ABL is disabled this will be 0 + maMax = request->arg(ma).toInt() * request->hasArg(F("PPL")); // if PP-ABL is disabled maMax (per bus) must be 0 } type |= request->hasArg(rf) << 7; // off refresh override // actual finalization is done in WLED::loop() (removing old busses and adding new) // this may happen even before this loop is finished so we do "doInitBusses" after the loop - busConfigs.emplace_back(type, pins, start, length, colorOrder | (channelSwap<<4), request->hasArg(cv), skip, awmode, freq, useGlobalLedBuffer, maPerLed, maMax); + busConfigs.emplace_back(type, pins, start, length, colorOrder | (channelSwap<<4), request->hasArg(cv), skip, awmode, freq, maPerLed, maMax); busesChanged = true; } //doInitBusses = busesChanged; // we will do that below to ensure all input data is processed @@ -334,6 +335,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) t = request->arg(F("TP")).toInt(); randomPaletteChangeTime = MIN(255,MAX(1,t)); useHarmonicRandomPalette = request->hasArg(F("TH")); + useRainbowWheel = request->hasArg(F("RW")); nightlightTargetBri = request->arg(F("TB")).toInt(); t = request->arg(F("TL")).toInt(); @@ -342,7 +344,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) nightlightMode = request->arg(F("TW")).toInt(); t = request->arg(F("PB")).toInt(); - if (t >= 0 && t < 4) strip.paletteBlend = t; + if (t >= 0 && t < 4) paletteBlend = t; t = request->arg(F("BF")).toInt(); if (t > 0) briMultiplier = t; @@ -358,7 +360,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) DEBUG_PRINTLN(F("Enumerating ledmaps")); enumerateLedmaps(); DEBUG_PRINTLN(F("Loading custom palettes")); - strip.loadCustomPalettes(); // (re)load all custom palettes + loadCustomPalettes(); // (re)load all custom palettes } //SYNC @@ -771,14 +773,14 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage) if (subPage == SUBPAGE_2D) { strip.isMatrix = request->arg(F("SOMP")).toInt(); - strip.panel.clear(); // release memory if allocated + strip.panel.clear(); if (strip.isMatrix) { - strip.panels = MAX(1,MIN(WLED_MAX_PANELS,request->arg(F("MPC")).toInt())); - strip.panel.reserve(strip.panels); // pre-allocate memory - for (unsigned i=0; iarg(F("MPC")).toInt(), 1, WLED_MAX_PANELS); + strip.panel.reserve(panels); // pre-allocate memory + for (unsigned i=0; iarg(pO).toInt(); strip.panel.push_back(p); } - strip.setUpMatrix(); // will check limits - strip.makeAutoSegments(true); - strip.deserializeMap(); - } else { - Segment::maxWidth = strip.getLengthTotal(); - Segment::maxHeight = 1; } + strip.panel.shrink_to_fit(); // release unused memory + strip.deserializeMap(); // (re)load default ledmap (will also setUpMatrix() if ledmap does not exist) + strip.makeAutoSegments(true); // force re-creation of segments } #endif @@ -824,7 +823,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply) //segment select (sets main segment) pos = req.indexOf(F("SM=")); if (pos > 0 && !realtimeMode) { - strip.setMainSegmentId(getNumVal(&req, pos)); + strip.setMainSegmentId(getNumVal(req, pos)); } byte selectedSeg = strip.getFirstSelectedSegId(); @@ -833,7 +832,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply) pos = req.indexOf(F("SS=")); if (pos > 0) { - unsigned t = getNumVal(&req, pos); + unsigned t = getNumVal(req, pos); if (t < strip.getSegmentsNum()) { selectedSeg = t; singleSegment = true; @@ -843,7 +842,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply) Segment& selseg = strip.getSegment(selectedSeg); pos = req.indexOf(F("SV=")); //segment selected if (pos > 0) { - unsigned t = getNumVal(&req, pos); + unsigned t = getNumVal(req, pos); if (t == 2) for (unsigned i = 0; i < strip.getSegmentsNum(); i++) strip.getSegment(i).selected = false; // unselect other segments selseg.selected = t; } @@ -872,19 +871,19 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply) uint16_t spcI = selseg.spacing; pos = req.indexOf(F("&S=")); //segment start if (pos > 0) { - startI = std::abs(getNumVal(&req, pos)); + startI = std::abs(getNumVal(req, pos)); } pos = req.indexOf(F("S2=")); //segment stop if (pos > 0) { - stopI = std::abs(getNumVal(&req, pos)); + stopI = std::abs(getNumVal(req, pos)); } pos = req.indexOf(F("GP=")); //segment grouping if (pos > 0) { - grpI = std::max(1,getNumVal(&req, pos)); + grpI = std::max(1,getNumVal(req, pos)); } pos = req.indexOf(F("SP=")); //segment spacing if (pos > 0) { - spcI = std::max(0,getNumVal(&req, pos)); + spcI = std::max(0,getNumVal(req, pos)); } strip.suspend(); // must suspend strip operations before changing geometry selseg.setGeometry(startI, stopI, grpI, spcI, UINT16_MAX, startY, stopY, selseg.map1D2D); @@ -898,7 +897,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply) pos = req.indexOf(F("SB=")); //Segment brightness/opacity if (pos > 0) { - byte segbri = getNumVal(&req, pos); + byte segbri = getNumVal(req, pos); selseg.setOption(SEG_OPTION_ON, segbri); // use transition if (segbri) { selseg.setOpacity(segbri); @@ -907,7 +906,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply) pos = req.indexOf(F("SW=")); //segment power if (pos > 0) { - switch (getNumVal(&req, pos)) { + switch (getNumVal(req, pos)) { case 0: selseg.setOption(SEG_OPTION_ON, false); break; // use transition case 1: selseg.setOption(SEG_OPTION_ON, true); break; // use transition default: selseg.setOption(SEG_OPTION_ON, !selseg.on); break; // use transition @@ -915,16 +914,16 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply) } pos = req.indexOf(F("PS=")); //saves current in preset - if (pos > 0) savePreset(getNumVal(&req, pos)); + if (pos > 0) savePreset(getNumVal(req, pos)); pos = req.indexOf(F("P1=")); //sets first preset for cycle - if (pos > 0) presetCycMin = getNumVal(&req, pos); + if (pos > 0) presetCycMin = getNumVal(req, pos); pos = req.indexOf(F("P2=")); //sets last preset for cycle - if (pos > 0) presetCycMax = getNumVal(&req, pos); + if (pos > 0) presetCycMax = getNumVal(req, pos); //apply preset - if (updateVal(req.c_str(), "PL=", &presetCycCurr, presetCycMin, presetCycMax)) { + if (updateVal(req.c_str(), "PL=", presetCycCurr, presetCycMin, presetCycMax)) { applyPreset(presetCycCurr); } @@ -932,25 +931,25 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply) if (pos > 0) doAdvancePlaylist = true; //set brightness - updateVal(req.c_str(), "&A=", &bri); + updateVal(req.c_str(), "&A=", bri); 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]); - col0Changed |= updateVal(req.c_str(), "&B=", &colIn[2]); - col0Changed |= updateVal(req.c_str(), "&W=", &colIn[3]); + col0Changed |= updateVal(req.c_str(), "&R=", colIn[0]); + col0Changed |= updateVal(req.c_str(), "&G=", colIn[1]); + col0Changed |= updateVal(req.c_str(), "&B=", colIn[2]); + col0Changed |= updateVal(req.c_str(), "&W=", colIn[3]); - col1Changed |= updateVal(req.c_str(), "R2=", &colInSec[0]); - col1Changed |= updateVal(req.c_str(), "G2=", &colInSec[1]); - col1Changed |= updateVal(req.c_str(), "B2=", &colInSec[2]); - col1Changed |= updateVal(req.c_str(), "W2=", &colInSec[3]); + col1Changed |= updateVal(req.c_str(), "R2=", colInSec[0]); + col1Changed |= updateVal(req.c_str(), "G2=", colInSec[1]); + col1Changed |= updateVal(req.c_str(), "B2=", colInSec[2]); + col1Changed |= updateVal(req.c_str(), "W2=", colInSec[3]); #ifdef WLED_ENABLE_LOXONE //lox parser pos = req.indexOf(F("LX=")); // Lox primary color if (pos > 0) { - int lxValue = getNumVal(&req, pos); + int lxValue = getNumVal(req, pos); if (parseLx(lxValue, colIn)) { bri = 255; nightlightActive = false; //always disable nightlight when toggling @@ -959,7 +958,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply) } pos = req.indexOf(F("LY=")); // Lox secondary color if (pos > 0) { - int lxValue = getNumVal(&req, pos); + int lxValue = getNumVal(req, pos); if(parseLx(lxValue, colInSec)) { bri = 255; nightlightActive = false; //always disable nightlight when toggling @@ -971,11 +970,11 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply) //set hue pos = req.indexOf(F("HU=")); if (pos > 0) { - uint16_t temphue = getNumVal(&req, pos); + uint16_t temphue = getNumVal(req, pos); byte tempsat = 255; pos = req.indexOf(F("SA=")); if (pos > 0) { - tempsat = getNumVal(&req, pos); + tempsat = getNumVal(req, pos); } byte sec = req.indexOf(F("H2")); colorHStoRGB(temphue, tempsat, (sec>0) ? colInSec : colIn); @@ -986,25 +985,25 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply) pos = req.indexOf(F("&K=")); if (pos > 0) { byte sec = req.indexOf(F("K2")); - colorKtoRGB(getNumVal(&req, pos), (sec>0) ? colInSec : colIn); + colorKtoRGB(getNumVal(req, pos), (sec>0) ? colInSec : colIn); col0Changed |= (!sec); col1Changed |= sec; } //set color from HEX or 32bit DEC pos = req.indexOf(F("CL=")); if (pos > 0) { - colorFromDecOrHexString(colIn, req.substring(pos + 3).c_str()); + colorFromDecOrHexString(colIn, (char*)req.substring(pos + 3).c_str()); col0Changed = true; } pos = req.indexOf(F("C2=")); if (pos > 0) { - colorFromDecOrHexString(colInSec, req.substring(pos + 3).c_str()); + colorFromDecOrHexString(colInSec, (char*)req.substring(pos + 3).c_str()); col1Changed = true; } pos = req.indexOf(F("C3=")); if (pos > 0) { byte tmpCol[4]; - colorFromDecOrHexString(tmpCol, req.substring(pos + 3).c_str()); + colorFromDecOrHexString(tmpCol, (char*)req.substring(pos + 3).c_str()); col2 = RGBW32(tmpCol[0], tmpCol[1], tmpCol[2], tmpCol[3]); selseg.setColor(2, col2); // defined above (SS= or main) col2Changed = true; @@ -1013,7 +1012,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply) //set to random hue SR=0->1st SR=1->2nd pos = req.indexOf(F("SR")); if (pos > 0) { - byte sec = getNumVal(&req, pos); + byte sec = getNumVal(req, pos); setRandomColor(sec? colInSec : colIn); col0Changed |= (!sec); col1Changed |= sec; } @@ -1039,19 +1038,19 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply) bool fxModeChanged = false, speedChanged = false, intensityChanged = false, paletteChanged = false; bool custom1Changed = false, custom2Changed = false, custom3Changed = false, check1Changed = false, check2Changed = false, check3Changed = false; // set effect parameters - if (updateVal(req.c_str(), "FX=", &effectIn, 0, strip.getModeCount()-1)) { + if (updateVal(req.c_str(), "FX=", effectIn, 0, strip.getModeCount()-1)) { if (request != nullptr) unloadPlaylist(); // unload playlist if changing FX using web request fxModeChanged = true; } - speedChanged = updateVal(req.c_str(), "SX=", &speedIn); - intensityChanged = updateVal(req.c_str(), "IX=", &intensityIn); - paletteChanged = updateVal(req.c_str(), "FP=", &paletteIn, 0, strip.getPaletteCount()-1); - custom1Changed = updateVal(req.c_str(), "X1=", &custom1In); - custom2Changed = updateVal(req.c_str(), "X2=", &custom2In); - custom3Changed = updateVal(req.c_str(), "X3=", &custom3In); - check1Changed = updateVal(req.c_str(), "M1=", &check1In); - check2Changed = updateVal(req.c_str(), "M2=", &check2In); - check3Changed = updateVal(req.c_str(), "M3=", &check3In); + speedChanged = updateVal(req.c_str(), "SX=", speedIn); + intensityChanged = updateVal(req.c_str(), "IX=", intensityIn); + paletteChanged = updateVal(req.c_str(), "FP=", paletteIn, 0, getPaletteCount()-1); + custom1Changed = updateVal(req.c_str(), "X1=", custom1In); + custom2Changed = updateVal(req.c_str(), "X2=", custom2In); + custom3Changed = updateVal(req.c_str(), "X3=", custom3In); + check1Changed = updateVal(req.c_str(), "M1=", check1In); + check2Changed = updateVal(req.c_str(), "M2=", check2In); + check3Changed = updateVal(req.c_str(), "M3=", check3In); stateChanged |= (fxModeChanged || speedChanged || intensityChanged || paletteChanged || custom1Changed || custom2Changed || custom3Changed || check1Changed || check2Changed || check3Changed); @@ -1077,13 +1076,13 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply) //set advanced overlay pos = req.indexOf(F("OL=")); if (pos > 0) { - overlayCurrent = getNumVal(&req, pos); + overlayCurrent = getNumVal(req, pos); } //apply macro (deprecated, added for compatibility with pre-0.11 automations) pos = req.indexOf(F("&M=")); if (pos > 0) { - applyPreset(getNumVal(&req, pos) + 16); + applyPreset(getNumVal(req, pos) + 16); } //toggle send UDP direct notifications @@ -1102,7 +1101,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply) pos = req.indexOf(F("&T=")); if (pos > 0) { nightlightActive = false; //always disable nightlight when toggling - switch (getNumVal(&req, pos)) + switch (getNumVal(req, pos)) { case 0: if (bri != 0){briLast = bri; bri = 0;} break; //off, only if it was previously on case 1: if (bri == 0) bri = briLast; break; //on, only if it was previously off @@ -1121,7 +1120,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply) nightlightActive = false; } else { nightlightActive = true; - if (!aNlDef) nightlightDelayMins = getNumVal(&req, pos); + if (!aNlDef) nightlightDelayMins = getNumVal(req, pos); else nightlightDelayMins = nightlightDelayMinsDefault; nightlightStartTime = millis(); } @@ -1135,7 +1134,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply) //set nightlight target brightness pos = req.indexOf(F("NT=")); if (pos > 0) { - nightlightTargetBri = getNumVal(&req, pos); + nightlightTargetBri = getNumVal(req, pos); nightlightActiveOld = false; //re-init } @@ -1143,35 +1142,36 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply) pos = req.indexOf(F("NF=")); if (pos > 0) { - nightlightMode = getNumVal(&req, pos); + nightlightMode = getNumVal(req, pos); nightlightActiveOld = false; //re-init } if (nightlightMode > NL_MODE_SUN) nightlightMode = NL_MODE_SUN; pos = req.indexOf(F("TT=")); - if (pos > 0) transitionDelay = getNumVal(&req, pos); + if (pos > 0) transitionDelay = getNumVal(req, pos); strip.setTransition(transitionDelay); //set time (unix timestamp) pos = req.indexOf(F("ST=")); if (pos > 0) { - setTimeFromAPI(getNumVal(&req, pos)); + setTimeFromAPI(getNumVal(req, pos)); } //set countdown goal (unix timestamp) pos = req.indexOf(F("CT=")); if (pos > 0) { - countdownTime = getNumVal(&req, pos); + countdownTime = getNumVal(req, pos); if (countdownTime - toki.second() > 0) countdownOverTriggered = false; } pos = req.indexOf(F("LO=")); if (pos > 0) { - realtimeOverride = getNumVal(&req, pos); + realtimeOverride = getNumVal(req, pos); if (realtimeOverride > 2) realtimeOverride = REALTIME_OVERRIDE_ALWAYS; if (realtimeMode && useMainSegmentOnly) { strip.getMainSegment().freeze = !realtimeOverride; + realtimeOverride = REALTIME_OVERRIDE_NONE; // ignore request for override if using main segment only } } @@ -1184,12 +1184,12 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply) pos = req.indexOf(F("U0=")); //user var 0 if (pos > 0) { - userVar0 = getNumVal(&req, pos); + userVar0 = getNumVal(req, pos); } pos = req.indexOf(F("U1=")); //user var 1 if (pos > 0) { - userVar1 = getNumVal(&req, pos); + userVar1 = getNumVal(req, pos); } // you can add more if you need diff --git a/wled00/udp.cpp b/wled00/udp.cpp index 4395b285d0..ed608da344 100644 --- a/wled00/udp.cpp +++ b/wled00/udp.cpp @@ -6,7 +6,7 @@ #define UDP_SEG_SIZE 36 #define SEG_OFFSET (41) -#define WLEDPACKETSIZE (41+(MAX_NUM_SEGMENTS*UDP_SEG_SIZE)+0) +#define WLEDPACKETSIZE (41+(WS2812FX::getMaxSegments()*UDP_SEG_SIZE)+0) #define UDP_IN_MAXSIZE 1472 #define PRESUMED_NETWORK_DELAY 3 //how many ms could it take on avg to reach the receiver? This will be added to transmitted times @@ -55,7 +55,7 @@ void notify(byte callMode, bool followUp) //0: old 1: supports white 2: supports secondary color //3: supports FX intensity, 24 byte packet 4: supports transitionDelay 5: sup palette //6: supports timebase syncing, 29 byte packet 7: supports tertiary color 8: supports sys time sync, 36 byte packet - //9: supports sync groups, 37 byte packet 10: supports CCT, 39 byte packet 11: per segment options, variable packet length (40+MAX_NUM_SEGMENTS*3) + //9: supports sync groups, 37 byte packet 10: supports CCT, 39 byte packet 11: per segment options, variable packet length (40+WS2812FX::getMaxSegments()*3) //12: enhanced effect sliders, 2D & mapping options udpOut[11] = 12; col = mainseg.colors[1]; @@ -104,7 +104,7 @@ void notify(byte callMode, bool followUp) udpOut[40] = UDP_SEG_SIZE; //size of each loop iteration (one segment) size_t s = 0, nsegs = strip.getSegmentsNum(); for (size_t i = 0; i < nsegs; i++) { - Segment &selseg = strip.getSegment(i); + const Segment &selseg = strip.getSegment(i); if (!selseg.isActive()) continue; unsigned ofs = 41 + s*UDP_SEG_SIZE; //start of segment offset byte udpOut[0 +ofs] = s; @@ -177,7 +177,7 @@ void notify(byte callMode, bool followUp) memcpy(buffer.data + packetSize, &udpOut[41+i*UDP_SEG_SIZE], UDP_SEG_SIZE); packetSize += UDP_SEG_SIZE; if (packetSize + UDP_SEG_SIZE < bufferSize) continue; - DEBUG_PRINTF_P(PSTR("ESP-NOW sending packet: %d (%d)\n"), (int)buffer.packet, packetSize+3); + DEBUG_PRINTF_P(PSTR("ESP-NOW sending packet: %d (%u)\n"), (int)buffer.packet, packetSize+3); err = quickEspNow.send(ESPNOW_BROADCAST_ADDRESS, reinterpret_cast(&buffer), packetSize+3); buffer.packet++; packetSize = 0; @@ -266,13 +266,13 @@ static void parseNotifyPacket(const uint8_t *udpIn) { strip.resume(); } size_t inactiveSegs = 0; - for (size_t i = 0; i < numSrcSegs && i < strip.getMaxSegments(); i++) { + for (size_t i = 0; i < numSrcSegs && i < WS2812FX::getMaxSegments(); i++) { unsigned ofs = 41 + i*udpIn[40]; //start of segment offset byte unsigned id = udpIn[0 +ofs]; DEBUG_PRINTF_P(PSTR("UDP segment received: %u\n"), id); if (id > strip.getSegmentsNum()) break; else if (id == strip.getSegmentsNum()) { - if (receiveSegmentBounds && id < strip.getMaxSegments()) strip.appendSegment(); + if (receiveSegmentBounds && id < WS2812FX::getMaxSegments()) strip.appendSegment(); else break; } DEBUG_PRINTF_P(PSTR("UDP segment check: %u\n"), id); @@ -327,7 +327,7 @@ static void parseNotifyPacket(const uint8_t *udpIn) { // freeze, reset should never be synced // LSB to MSB: select, reverse, on, mirror, freeze, reset, reverse_y, mirror_y, transpose, map1d2d (3), ssim (2), set (2) DEBUG_PRINTF_P(PSTR("Apply options: %u\n"), id); - selseg.options = (selseg.options & 0b0000000000110001U) | (udpIn[28+ofs]<<8) | (udpIn[9 +ofs] & 0b11001110U); // ignore selected, freeze, reset + selseg.options = (selseg.options & 0b0000000000110001U) | ((uint16_t)udpIn[28+ofs]<<8) | (udpIn[9 +ofs] & 0b11001110U); // ignore selected, freeze, reset if (applyEffects) { DEBUG_PRINTF_P(PSTR("Apply sliders: %u\n"), id); selseg.custom1 = udpIn[29+ofs]; @@ -406,31 +406,26 @@ static void parseNotifyPacket(const uint8_t *udpIn) { stateUpdated(CALL_MODE_NOTIFICATION); } +// realtimeLock() is called from UDP notifications, JSON API or serial Ada void realtimeLock(uint32_t timeoutMs, byte md) { if (!realtimeMode && !realtimeOverride) { - unsigned stop, start; if (useMainSegmentOnly) { Segment& mainseg = strip.getMainSegment(); - start = mainseg.start; - stop = mainseg.stop; + mainseg.clear(); // clear entire segment (in case sender transmits less pixels) 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; - } + for (size_t s = 0; s < strip.getSegmentsNum(); s++) strip.getSegment(s).freeze = true; } } else { - start = 0; - stop = strip.getLengthTotal(); + // clear entire strip + strip.fill(BLACK); + } + // if strip is off (bri==0) and not already in RTM + if (briT == 0) { + strip.setBrightness(scaledBri(briLast), true); } - // clear strip/segment - for (size_t i = start; i < stop; i++) strip.setPixelColor(i,BLACK); - } - // if strip is off (bri==0) and not already in RTM - if (briT == 0 && !realtimeMode && !realtimeOverride) { - strip.setBrightness(scaledBri(briLast), true); } if (realtimeTimeout != UINT32_MAX) { @@ -452,6 +447,7 @@ void exitRealtime() { realtimeIP[0] = 0; if (useMainSegmentOnly) { // unfreeze live segment again strip.getMainSegment().freeze = false; + strip.trigger(); } else { strip.show(); // possible fix for #3589 } @@ -481,7 +477,8 @@ void handleNotifications() if (e131NewData && millis() - strip.getLastShow() > 15) { e131NewData = false; - strip.show(); + if (useMainSegmentOnly) strip.trigger(); + else strip.show(); } //unlock strip when realtime UDP times out @@ -508,13 +505,13 @@ void handleNotifications() uint8_t lbuf[packetSize]; rgbUdp.read(lbuf, packetSize); realtimeLock(realtimeTimeoutMs, REALTIME_MODE_HYPERION); - if (realtimeOverride && !(realtimeMode && useMainSegmentOnly)) return; + if (realtimeOverride) return; unsigned totalLen = strip.getLengthTotal(); - 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); } - if (!(realtimeMode && useMainSegmentOnly)) strip.show(); + if (useMainSegmentOnly) strip.trigger(); + else strip.show(); return; } } @@ -583,7 +580,7 @@ void handleNotifications() realtimeIP = (isSupp) ? notifier2Udp.remoteIP() : notifierUdp.remoteIP(); realtimeLock(realtimeTimeoutMs, REALTIME_MODE_TPM2NET); - if (realtimeOverride && !(realtimeMode && useMainSegmentOnly)) return; + if (realtimeOverride) return; tpmPacketCount++; //increment the packet count if (tpmPacketCount == 1) tpmPayloadFrameSize = (udpIn[2] << 8) + udpIn[3]; //save frame size for the whole payload if this is the first packet @@ -592,13 +589,13 @@ void handleNotifications() unsigned id = (tpmPayloadFrameSize/3)*(packetNum-1); //start LED unsigned totalLen = strip.getLengthTotal(); - 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 tpmPacketCount = 0; - strip.show(); + if (useMainSegmentOnly) strip.trigger(); + else strip.show(); } return; } @@ -610,17 +607,15 @@ void handleNotifications() DEBUG_PRINTLN(realtimeIP); if (packetSize < 2) return; - if (udpIn[1] == 0) - { - realtimeTimeout = 0; + if (udpIn[1] == 0) { + realtimeTimeout = 0; // cancel realtime mode immediately return; } else { realtimeLock(udpIn[1]*1000 +1, REALTIME_MODE_UDP); } - if (realtimeOverride && !(realtimeMode && useMainSegmentOnly)) return; + if (realtimeOverride) 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) @@ -654,7 +649,8 @@ void handleNotifications() setRealtimePixel(id, udpIn[i], udpIn[i+1], udpIn[i+2], udpIn[i+3]); } } - strip.show(); + if (useMainSegmentOnly) strip.trigger(); + else strip.show(); return; } @@ -679,20 +675,7 @@ void handleNotifications() void setRealtimePixel(uint16_t i, byte r, byte g, byte b, byte w) { unsigned pix = i + arlsOffset; - if (pix < strip.getLengthTotal()) { - if (!arlsDisableGammaCorrection && gammaCorrectCol) { - r = gamma8(r); - g = gamma8(g); - b = gamma8(b); - w = gamma8(w); - } - uint32_t col = RGBW32(r,g,b,w); - if (useMainSegmentOnly) { - strip.getMainSegment().setPixelColor(pix, col); // this expects that strip.getMainSegment().beginDraw() has been called in handleNotification() - } else { - strip.setPixelColor(pix, col); - } - } + strip.setRealtimePixelColor(pix, RGBW32(r,g,b,w)); } /*********************************************************************************************\ @@ -808,7 +791,7 @@ static size_t sequenceNumber = 0; // this needs to be shared across all ou static const size_t ART_NET_HEADER_SIZE = 12; static const byte ART_NET_HEADER[] PROGMEM = {0x41,0x72,0x74,0x2d,0x4e,0x65,0x74,0x00,0x00,0x50,0x00,0x0e}; -uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, const uint8_t* buffer, uint8_t bri, bool isRGBW) { +uint8_t realtimeBroadcast(uint8_t type, IPAddress client, uint16_t length, const uint8_t *buffer, uint8_t bri, bool isRGBW) { if (!(apActive || interfacesInited) || !client[0] || !length) return 1; // network not initialised or dummy/unset IP address 031522 ajn added check for ap WiFiUDP ddpUdp; diff --git a/wled00/util.cpp b/wled00/util.cpp index ac8a162073..97e1e3b035 100644 --- a/wled00/util.cpp +++ b/wled00/util.cpp @@ -4,17 +4,17 @@ //helper to get int value at a position in string -int getNumVal(const String* req, uint16_t pos) +int getNumVal(const String &req, uint16_t pos) { - return req->substring(pos+3).toInt(); + return req.substring(pos+3).toInt(); } //helper to get int value with in/decrementing support via ~ syntax -void parseNumber(const char* str, byte* val, byte minv, byte maxv) +void parseNumber(const char* str, byte &val, byte minv, byte maxv) { if (str == nullptr || str[0] == '\0') return; - if (str[0] == 'r') {*val = hw_random8(minv,maxv?maxv:255); return;} // maxv for random cannot be 0 + if (str[0] == 'r') {val = hw_random8(minv,maxv?maxv:255); return;} // maxv for random cannot be 0 bool wrap = false; if (str[0] == 'w' && strlen(str) > 1) {str++; wrap = true;} if (str[0] == '~') { @@ -22,19 +22,19 @@ void parseNumber(const char* str, byte* val, byte minv, byte maxv) if (out == 0) { if (str[1] == '0') return; if (str[1] == '-') { - *val = (int)(*val -1) < (int)minv ? maxv : min((int)maxv,(*val -1)); //-1, wrap around + val = (int)(val -1) < (int)minv ? maxv : min((int)maxv,(val -1)); //-1, wrap around } else { - *val = (int)(*val +1) > (int)maxv ? minv : max((int)minv,(*val +1)); //+1, wrap around + val = (int)(val +1) > (int)maxv ? minv : max((int)minv,(val +1)); //+1, wrap around } } else { - if (wrap && *val == maxv && out > 0) out = minv; - else if (wrap && *val == minv && out < 0) out = maxv; + if (wrap && val == maxv && out > 0) out = minv; + else if (wrap && val == minv && out < 0) out = maxv; else { - out += *val; + out += val; if (out > maxv) out = maxv; if (out < minv) out = minv; } - *val = out; + val = out; } return; } else if (minv == maxv && minv == 0) { // limits "unset" i.e. both 0 @@ -49,14 +49,14 @@ void parseNumber(const char* str, byte* val, byte minv, byte maxv) } } } - *val = atoi(str); + val = atoi(str); } //getVal supports inc/decrementing and random ("X~Y(r|~[w][-][Z])" form) -bool getVal(JsonVariant elem, byte* val, byte vmin, byte vmax) { +bool getVal(JsonVariant elem, byte &val, byte vmin, byte vmax) { if (elem.is()) { if (elem < 0) return false; //ignore e.g. {"ps":-1} - *val = elem; + val = elem; return true; } else if (elem.is()) { const char* str = elem; @@ -82,7 +82,7 @@ bool getBoolVal(const JsonVariant &elem, bool dflt) { } -bool updateVal(const char* req, const char* key, byte* val, byte minv, byte maxv) +bool updateVal(const char* req, const char* key, byte &val, byte minv, byte maxv) { const char *v = strstr(req, key); if (v) v += strlen(key); @@ -619,6 +619,68 @@ int32_t hw_random(int32_t lowerlimit, int32_t upperlimit) { return hw_random(diff) + lowerlimit; } +#ifndef ESP8266 +void *p_malloc(size_t size) { + int caps1 = MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT; + int caps2 = MALLOC_CAP_DEFAULT | MALLOC_CAP_8BIT; + if (psramSafe) { + if (heap_caps_get_free_size(caps2) > 3*MIN_HEAP_SIZE && size < 512) std::swap(caps1, caps2); // use DRAM for small alloactions & when heap is plenty + return heap_caps_malloc_prefer(size, 2, caps1, caps2); // otherwise prefer PSRAM if it exists + } + return heap_caps_malloc(size, caps2); +} + +void *p_realloc(void *ptr, size_t size) { + int caps1 = MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT; + int caps2 = MALLOC_CAP_DEFAULT | MALLOC_CAP_8BIT; + if (psramSafe) { + if (heap_caps_get_free_size(caps2) > 3*MIN_HEAP_SIZE && size < 512) std::swap(caps1, caps2); // use DRAM for small alloactions & when heap is plenty + return heap_caps_realloc_prefer(ptr, size, 2, caps1, caps2); // otherwise prefer PSRAM if it exists + } + return heap_caps_realloc(ptr, size, caps2); +} + +void *p_calloc(size_t count, size_t size) { + int caps1 = MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT; + int caps2 = MALLOC_CAP_DEFAULT | MALLOC_CAP_8BIT; + if (psramSafe) { + if (heap_caps_get_free_size(caps2) > 3*MIN_HEAP_SIZE && size < 512) std::swap(caps1, caps2); // use DRAM for small alloactions & when heap is plenty + return heap_caps_calloc_prefer(count, size, 2, caps1, caps2); // otherwise prefer PSRAM if it exists + } + return heap_caps_calloc(count, size, caps2); +} + +void *d_malloc(size_t size) { + int caps1 = MALLOC_CAP_DEFAULT | MALLOC_CAP_8BIT; + int caps2 = MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT; + if (psramSafe) { + if (size > MIN_HEAP_SIZE) std::swap(caps1, caps2); // prefer PSRAM for large alloactions + return heap_caps_malloc_prefer(size, 2, caps1, caps2); // otherwise prefer DRAM + } + return heap_caps_malloc(size, caps1); +} + +void *d_realloc(void *ptr, size_t size) { + int caps1 = MALLOC_CAP_DEFAULT | MALLOC_CAP_8BIT; + int caps2 = MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT; + if (psramSafe) { + if (size > MIN_HEAP_SIZE) std::swap(caps1, caps2); // prefer PSRAM for large alloactions + return heap_caps_realloc_prefer(ptr, size, 2, caps1, caps2); // otherwise prefer DRAM + } + return heap_caps_realloc(ptr, size, caps1); +} + +void *d_calloc(size_t count, size_t size) { + int caps1 = MALLOC_CAP_DEFAULT | MALLOC_CAP_8BIT; + int caps2 = MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT; + if (psramSafe) { + if (size > MIN_HEAP_SIZE) std::swap(caps1, caps2); // prefer PSRAM for large alloactions + return heap_caps_calloc_prefer(count, size, 2, caps1, caps2); // otherwise prefer DRAM + } + return heap_caps_calloc(count, size, caps1); +} +#endif + /* * Fixed point integer based Perlin noise functions by @dedehai * Note: optimized for speed and to mimic fastled inoise functions, not for accuracy or best randomness diff --git a/wled00/wled.cpp b/wled00/wled.cpp index cc338d23f2..111fc12e97 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -529,6 +529,7 @@ void WLED::setup() void WLED::beginStrip() { // Initialize NeoPixel Strip and button + strip.setTransition(0); // temporarily prevent transitions to reduce segment copies strip.finalizeInit(); // busses created during deserializeConfig() if config existed strip.makeAutoSegments(); strip.setBrightness(0); @@ -557,6 +558,8 @@ void WLED::beginStrip() applyPreset(bootPreset, CALL_MODE_INIT); } + strip.setTransition(transitionDelayDefault); // restore transitions + // init relay pin if (rlyPin >= 0) { pinMode(rlyPin, rlyOpenDrain ? OUTPUT_OPEN_DRAIN : OUTPUT); @@ -750,7 +753,9 @@ void WLED::handleConnection() static bool scanDone = true; static byte stacO = 0; const unsigned long now = millis(); + #ifdef WLED_DEBUG const unsigned long nowS = now/1000; + #endif const bool wifiConfigured = WLED_WIFI_CONFIGURED; // ignore connection handling if WiFi is configured and scan still running diff --git a/wled00/wled.h b/wled00/wled.h index f8dc1252a8..600ed010b3 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -64,6 +64,9 @@ //This is generally a terrible idea, but improves boot success on boards with a 3.3v regulator + cap setup that can't provide 400mA peaks //#define WLED_DISABLE_BROWNOUT_DET +#include +#include + // Library inclusions. #include #ifdef ESP8266 @@ -602,6 +605,8 @@ WLED_GLOBAL bool wasConnected _INIT(false); // color WLED_GLOBAL byte lastRandomIndex _INIT(0); // used to save last random color so the new one is not the same +WLED_GLOBAL std::vector customPalettes; // custom palettes +WLED_GLOBAL uint8_t paletteBlend _INIT(0); // determines blending and wrapping of palette: 0: blend, wrap if moving (SEGMENT.speed>0); 1: blend, always wrap; 2: blend, never wrap; 3: don't blend or wrap // transitions WLED_GLOBAL uint8_t blendingStyle _INIT(0); // effect blending/transitionig style @@ -612,6 +617,7 @@ WLED_GLOBAL unsigned long transitionStartTime; 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 +WLED_GLOBAL bool useRainbowWheel _INIT(false); // use "rainbow" color wheel instead of "spectrum" color wheel // nightlight WLED_GLOBAL bool nightlightActive _INIT(false); diff --git a/wled00/wled_eeprom.cpp b/wled00/wled_eeprom.cpp index d0451d9a93..fb63bc6460 100644 --- a/wled00/wled_eeprom.cpp +++ b/wled00/wled_eeprom.cpp @@ -225,7 +225,7 @@ void loadSettingsFromEEPROM() if (lastEEPROMversion > 7) { //strip.paletteFade = EEPROM.read(374); - strip.paletteBlend = EEPROM.read(382); + paletteBlend = EEPROM.read(382); for (int i = 0; i < 8; ++i) { diff --git a/wled00/wled_server.cpp b/wled00/wled_server.cpp index 06750838f3..a41eab835f 100644 --- a/wled00/wled_server.cpp +++ b/wled00/wled_server.cpp @@ -176,7 +176,7 @@ static void handleUpload(AsyncWebServerRequest *request, const String& filename, doReboot = true; request->send(200, FPSTR(CONTENT_TYPE_PLAIN), F("Configuration restore successful.\nRebooting...")); } else { - if (filename.indexOf(F("palette")) >= 0 && filename.indexOf(F(".json")) >= 0) strip.loadCustomPalettes(); + if (filename.indexOf(F("palette")) >= 0 && filename.indexOf(F(".json")) >= 0) loadCustomPalettes(); request->send(200, FPSTR(CONTENT_TYPE_PLAIN), F("File Uploaded!")); } cacheInvalidate++; diff --git a/wled00/xml.cpp b/wled00/xml.cpp index 19868d01d9..ce0662ca14 100644 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -291,12 +291,11 @@ void getSettingsJS(byte subPage, Print& settingsScript) printSetFormValue(settingsScript,PSTR("CB"),Bus::getCCTBlend()); printSetFormValue(settingsScript,PSTR("FR"),strip.getTargetFps()); printSetFormValue(settingsScript,PSTR("AW"),Bus::getGlobalAWMode()); - printSetFormCheckbox(settingsScript,PSTR("LD"),useGlobalLedBuffer); printSetFormCheckbox(settingsScript,PSTR("PR"),BusManager::hasParallelOutput()); // get it from bus manager not global variable unsigned sumMa = 0; - for (int s = 0; s < BusManager::getNumBusses(); s++) { - const Bus* bus = BusManager::getBus(s); + for (size_t s = 0; s < BusManager::getNumBusses(); s++) { + const Bus *bus = BusManager::getBus(s); if (!bus || !bus->isOk()) break; // should not happen but for safety int offset = s < 10 ? '0' : 'A'; char lp[4] = "L0"; lp[2] = offset+s; lp[3] = 0; //ascii 0-9 //strip data pin @@ -380,7 +379,8 @@ void getSettingsJS(byte subPage, Print& settingsScript) printSetFormValue(settingsScript,PSTR("TB"),nightlightTargetBri); printSetFormValue(settingsScript,PSTR("TL"),nightlightDelayMinsDefault); printSetFormValue(settingsScript,PSTR("TW"),nightlightMode); - printSetFormIndex(settingsScript,PSTR("PB"),strip.paletteBlend); + printSetFormIndex(settingsScript,PSTR("PB"),paletteBlend); + printSetFormCheckbox(settingsScript,PSTR("RW"),useRainbowWheel); printSetFormValue(settingsScript,PSTR("RL"),rlyPin); printSetFormCheckbox(settingsScript,PSTR("RM"),rlyMde); printSetFormCheckbox(settingsScript,PSTR("RO"),rlyOpenDrain); @@ -666,16 +666,14 @@ void getSettingsJS(byte subPage, Print& settingsScript) #ifndef WLED_DISABLE_2D 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 - printSetFormValue(settingsScript,PSTR("PH"),strip.panel[0].height); - } - printSetFormValue(settingsScript,PSTR("MPC"),strip.panels); + printSetFormValue(settingsScript,PSTR("PW"),strip.panel.size()>0?strip.panel[0].width:8); //Set generator Width and Height to first panel size for convenience + printSetFormValue(settingsScript,PSTR("PH"),strip.panel.size()>0?strip.panel[0].height:8); + printSetFormValue(settingsScript,PSTR("MPC"),strip.panel.size()); // panels - for (unsigned i=0; i