diff --git a/package-lock.json b/package-lock.json index e775dc41..3b8bb25f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "wled", - "version": "0.14.0", + "version": "0.14.1-a1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 3135c5f3..add1b02e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wled", - "version": "0.14.0", + "version": "0.14.1-a1", "description": "Tools for WLED project", "main": "tools/cdata.js", "directories": { diff --git a/platformio.ini b/platformio.ini index f3caa585..4c78707d 100644 --- a/platformio.ini +++ b/platformio.ini @@ -217,7 +217,7 @@ build_flags = ; restrict to minimal mime-types -DMIMETYPE_MINIMAL ; other special-purpose framework flags (see https://docs.platformio.org/en/latest/platforms/espressif8266.html) - ; -D PIO_FRAMEWORK_ARDUINO_MMU_CACHE16_IRAM48 ;; in case of linker errors like "section `.text1' will not fit in region `iram1_0_seg'" + -D PIO_FRAMEWORK_ARDUINO_MMU_CACHE16_IRAM48 ;; in case of linker errors like "section `.text1' will not fit in region `iram1_0_seg'" ; -D PIO_FRAMEWORK_ARDUINO_MMU_CACHE16_IRAM48_SECHEAP_SHARED ;; (experimental) adds some extra heap, but may cause slowdown lib_deps = diff --git a/usermods/PWM_fan/usermod_PWM_fan.h b/usermods/PWM_fan/usermod_PWM_fan.h index f7fe0e10..05b6d9b3 100644 --- a/usermods/PWM_fan/usermod_PWM_fan.h +++ b/usermods/PWM_fan/usermod_PWM_fan.h @@ -52,9 +52,15 @@ class PWMFanUsermod : public Usermod { uint8_t tachoUpdateSec = 30; float targetTemperature = 35.0; uint8_t minPWMValuePct = 0; + uint8_t maxPWMValuePct = 100; uint8_t numberOfInterrupsInOneSingleRotation = 2; // Number of interrupts ESP32 sees on tacho signal on a single fan rotation. All the fans I've seen trigger two interrups. uint8_t pwmValuePct = 0; + // constant values + static const uint8_t _pwmMaxValue = 255; + static const uint8_t _pwmMaxStepCount = 7; + float _pwmTempStepSize = 0.5f; + // strings to reduce flash memory usage (used more than twice) static const char _name[]; static const char _enabled[]; @@ -63,6 +69,7 @@ class PWMFanUsermod : public Usermod { static const char _temperature[]; static const char _tachoUpdateSec[]; static const char _minPWMValuePct[]; + static const char _maxPWMValuePct[]; static const char _IRQperRotation[]; static const char _speed[]; static const char _lock[]; @@ -156,31 +163,25 @@ class PWMFanUsermod : public Usermod { void setFanPWMbasedOnTemperature(void) { float temp = getActualTemperature(); - float difftemp = temp - targetTemperature; - // Default to run fan at full speed. - int newPWMvalue = 255; - int pwmStep = ((100 - minPWMValuePct) * newPWMvalue) / (7*100); - int pwmMinimumValue = (minPWMValuePct * newPWMvalue) / 100; + // dividing minPercent and maxPercent into equal pwmvalue sizes + int pwmStepSize = ((maxPWMValuePct - minPWMValuePct) * _pwmMaxValue) / (_pwmMaxStepCount*100); + int pwmStep = calculatePwmStep(temp - targetTemperature); + // minimum based on full speed - not entered MaxPercent + int pwmMinimumValue = (minPWMValuePct * _pwmMaxValue) / 100; + updateFanSpeed(pwmMinimumValue + pwmStep*pwmStepSize); + } - if ((temp == NAN) || (temp <= -100.0)) { + uint8_t calculatePwmStep(float diffTemp){ + if ((diffTemp == NAN) || (diffTemp <= -100.0)) { DEBUG_PRINTLN(F("WARNING: no temperature value available. Cannot do temperature control. Will set PWM fan to 255.")); - } else if (difftemp <= 0.0) { - // Temperature is below target temperature. Run fan at minimum speed. - newPWMvalue = pwmMinimumValue; - } else if (difftemp <= 0.5) { - newPWMvalue = pwmMinimumValue + pwmStep; - } else if (difftemp <= 1.0) { - newPWMvalue = pwmMinimumValue + 2*pwmStep; - } else if (difftemp <= 1.5) { - newPWMvalue = pwmMinimumValue + 3*pwmStep; - } else if (difftemp <= 2.0) { - newPWMvalue = pwmMinimumValue + 4*pwmStep; - } else if (difftemp <= 2.5) { - newPWMvalue = pwmMinimumValue + 5*pwmStep; - } else if (difftemp <= 3.0) { - newPWMvalue = pwmMinimumValue + 6*pwmStep; + return _pwmMaxStepCount; } - updateFanSpeed(newPWMvalue); + if(diffTemp <=0){ + return 0; + } + int calculatedStep = (diffTemp / _pwmTempStepSize)+1; + // anything greater than max stepcount gets max + return (uint8_t)min((int)_pwmMaxStepCount,calculatedStep); } public: @@ -312,6 +313,7 @@ class PWMFanUsermod : public Usermod { top[FPSTR(_tachoUpdateSec)] = tachoUpdateSec; top[FPSTR(_temperature)] = targetTemperature; top[FPSTR(_minPWMValuePct)] = minPWMValuePct; + top[FPSTR(_maxPWMValuePct)] = maxPWMValuePct; top[FPSTR(_IRQperRotation)] = numberOfInterrupsInOneSingleRotation; DEBUG_PRINTLN(F("Autosave config saved.")); } @@ -345,6 +347,8 @@ class PWMFanUsermod : public Usermod { targetTemperature = top[FPSTR(_temperature)] | targetTemperature; minPWMValuePct = top[FPSTR(_minPWMValuePct)] | minPWMValuePct; minPWMValuePct = (uint8_t) min(100,max(0,(int)minPWMValuePct)); // bounds checking + maxPWMValuePct = top[FPSTR(_maxPWMValuePct)] | maxPWMValuePct; + maxPWMValuePct = (uint8_t) min(100,max((int)minPWMValuePct,(int)maxPWMValuePct)); // bounds checking numberOfInterrupsInOneSingleRotation = top[FPSTR(_IRQperRotation)] | numberOfInterrupsInOneSingleRotation; numberOfInterrupsInOneSingleRotation = (uint8_t) max(1,(int)numberOfInterrupsInOneSingleRotation); // bounds checking @@ -389,6 +393,7 @@ const char PWMFanUsermod::_pwmPin[] PROGMEM = "PWM-pin"; const char PWMFanUsermod::_temperature[] PROGMEM = "target-temp-C"; const char PWMFanUsermod::_tachoUpdateSec[] PROGMEM = "tacho-update-s"; const char PWMFanUsermod::_minPWMValuePct[] PROGMEM = "min-PWM-percent"; +const char PWMFanUsermod::_maxPWMValuePct[] PROGMEM = "max-PWM-percent"; const char PWMFanUsermod::_IRQperRotation[] PROGMEM = "IRQs-per-rotation"; const char PWMFanUsermod::_speed[] PROGMEM = "speed"; const char PWMFanUsermod::_lock[] PROGMEM = "lock"; diff --git a/usermods/seven_segment_display_reloaded/readme.md b/usermods/seven_segment_display_reloaded/readme.md index d373a7ee..a3398c3e 100644 --- a/usermods/seven_segment_display_reloaded/readme.md +++ b/usermods/seven_segment_display_reloaded/readme.md @@ -24,6 +24,9 @@ Enables the inverted mode in which the background should be enabled and the digi ### Colon-blinking Enables the blinking colon(s) if they are defined +### Leading-Zero +Shows the leading zero of the hour if it exists (i.e. shows `07` instead of `7`) + ### enable-auto-brightness Enables the auto brightness feature. Can be used only when the usermod SN_Photoresistor is installed. diff --git a/usermods/seven_segment_display_reloaded/usermod_seven_segment_reloaded.h b/usermods/seven_segment_display_reloaded/usermod_seven_segment_reloaded.h index 27977405..bf0b4703 100644 --- a/usermods/seven_segment_display_reloaded/usermod_seven_segment_reloaded.h +++ b/usermods/seven_segment_display_reloaded/usermod_seven_segment_reloaded.h @@ -17,6 +17,7 @@ private: bool umSSDRDisplayTime = false; bool umSSDRInverted = false; bool umSSDRColonblink = true; + bool umSSDRLeadingZero = false; bool umSSDREnableLDR = false; String umSSDRHours = ""; String umSSDRMinutes = ""; @@ -79,6 +80,7 @@ private: static const char _str_timeEnabled[]; static const char _str_inverted[]; static const char _str_colonblink[]; + static const char _str_leadingZero[]; static const char _str_displayMask[]; static const char _str_hours[]; static const char _str_minutes[]; @@ -105,15 +107,15 @@ private: switch (umSSDRDisplayMask[index]) { case 'h': timeVar = hourFormat12(localTime); - _showElements(&umSSDRHours, timeVar, 0, 1); + _showElements(&umSSDRHours, timeVar, 0, !umSSDRLeadingZero); break; case 'H': timeVar = hour(localTime); - _showElements(&umSSDRHours, timeVar, 0, 1); + _showElements(&umSSDRHours, timeVar, 0, !umSSDRLeadingZero); break; case 'k': timeVar = hour(localTime) + 1; - _showElements(&umSSDRHours, timeVar, 0, 0); + _showElements(&umSSDRHours, timeVar, 0, !umSSDRLeadingZero); break; case 'm': timeVar = minute(localTime); @@ -309,6 +311,9 @@ private: if (_cmpIntSetting_P(topic, payload, _str_colonblink, &umSSDRColonblink)) { return true; } + if (_cmpIntSetting_P(topic, payload, _str_leadingZero, &umSSDRLeadingZero)) { + return true; + } if (strcmp_P(topic, _str_displayMask) == 0) { umSSDRDisplayMask = String(payload); _publishMQTTstr_P(_str_displayMask, umSSDRDisplayMask); @@ -323,6 +328,7 @@ private: _publishMQTTint_P(_str_ldrEnabled, umSSDREnableLDR); _publishMQTTint_P(_str_inverted, umSSDRInverted); _publishMQTTint_P(_str_colonblink, umSSDRColonblink); + _publishMQTTint_P(_str_leadingZero, umSSDRLeadingZero); _publishMQTTstr_P(_str_hours, umSSDRHours); _publishMQTTstr_P(_str_minutes, umSSDRMinutes); @@ -347,6 +353,7 @@ private: ssdrObj[FPSTR(_str_ldrEnabled)] = umSSDREnableLDR; ssdrObj[FPSTR(_str_inverted)] = umSSDRInverted; ssdrObj[FPSTR(_str_colonblink)] = umSSDRColonblink; + ssdrObj[FPSTR(_str_leadingZero)] = umSSDRLeadingZero; ssdrObj[FPSTR(_str_displayMask)] = umSSDRDisplayMask; ssdrObj[FPSTR(_str_hours)] = umSSDRHours; ssdrObj[FPSTR(_str_minutes)] = umSSDRMinutes; @@ -425,6 +432,8 @@ public: invert.add(umSSDRInverted); JsonArray blink = user.createNestedArray("Blinking colon"); blink.add(umSSDRColonblink); + JsonArray zero = user.createNestedArray("Show the hour leading zero"); + zero.add(umSSDRLeadingZero); JsonArray ldrEnable = user.createNestedArray("Auto Brightness enabled"); ldrEnable.add(umSSDREnableLDR); @@ -454,6 +463,7 @@ public: umSSDREnableLDR = ssdrObj[FPSTR(_str_ldrEnabled)] | umSSDREnableLDR; umSSDRInverted = ssdrObj[FPSTR(_str_inverted)] | umSSDRInverted; umSSDRColonblink = ssdrObj[FPSTR(_str_colonblink)] | umSSDRColonblink; + umSSDRLeadingZero = ssdrObj[FPSTR(_str_leadingZero)] | umSSDRLeadingZero; umSSDRDisplayMask = ssdrObj[FPSTR(_str_displayMask)] | umSSDRDisplayMask; } } @@ -516,6 +526,7 @@ public: umSSDREnableLDR = (top[FPSTR(_str_ldrEnabled)] | umSSDREnableLDR); umSSDRInverted = (top[FPSTR(_str_inverted)] | umSSDRInverted); umSSDRColonblink = (top[FPSTR(_str_colonblink)] | umSSDRColonblink); + umSSDRLeadingZero = (top[FPSTR(_str_leadingZero)] | umSSDRLeadingZero); umSSDRDisplayMask = top[FPSTR(_str_displayMask)] | umSSDRDisplayMask; umSSDRHours = top[FPSTR(_str_hours)] | umSSDRHours; @@ -546,6 +557,7 @@ const char UsermodSSDR::_str_name[] PROGMEM = "UsermodSSDR"; const char UsermodSSDR::_str_timeEnabled[] PROGMEM = "enabled"; const char UsermodSSDR::_str_inverted[] PROGMEM = "inverted"; const char UsermodSSDR::_str_colonblink[] PROGMEM = "Colon-blinking"; +const char UsermodSSDR::_str_leadingZero[] PROGMEM = "Leading-Zero"; const char UsermodSSDR::_str_displayMask[] PROGMEM = "Display-Mask"; const char UsermodSSDR::_str_hours[] PROGMEM = "LED-Numbers-Hours"; const char UsermodSSDR::_str_minutes[] PROGMEM = "LED-Numbers-Minutes"; diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 188022b0..2b7cd726 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -177,11 +177,11 @@ uint16_t color_wipe(bool rev, bool useRandomColors) { SEGENV.step = 3; } if (SEGENV.step == 1) { //if flag set, change to new random color - SEGENV.aux1 = SEGMENT.get_random_wheel_index(SEGENV.aux0); + SEGENV.aux1 = get_random_wheel_index(SEGENV.aux0); SEGENV.step = 2; } if (SEGENV.step == 3) { - SEGENV.aux0 = SEGMENT.get_random_wheel_index(SEGENV.aux1); + SEGENV.aux0 = get_random_wheel_index(SEGENV.aux1); SEGENV.step = 0; } } @@ -271,7 +271,7 @@ uint16_t mode_random_color(void) { if (it != SEGENV.step) //new color { SEGENV.aux1 = SEGENV.aux0; - SEGENV.aux0 = SEGMENT.get_random_wheel_index(SEGENV.aux0); //aux0 will store our random color wheel index + SEGENV.aux0 = get_random_wheel_index(SEGENV.aux0); //aux0 will store our random color wheel index SEGENV.step = it; } @@ -816,7 +816,7 @@ uint16_t chase(uint32_t color1, uint32_t color2, uint32_t color3, bool do_palett if (a < SEGENV.step) //we hit the start again, choose new color for Chase random { SEGENV.aux1 = SEGENV.aux0; //store previous random color - SEGENV.aux0 = SEGMENT.get_random_wheel_index(SEGENV.aux0); + SEGENV.aux0 = get_random_wheel_index(SEGENV.aux0); } color1 = SEGMENT.color_wheel(SEGENV.aux0); } @@ -1056,7 +1056,7 @@ uint16_t mode_chase_flash_random(void) { SEGENV.aux1 = (SEGENV.aux1 + 1) % SEGLEN; if (SEGENV.aux1 == 0) { - SEGENV.aux0 = SEGMENT.get_random_wheel_index(SEGENV.aux0); + SEGENV.aux0 = get_random_wheel_index(SEGENV.aux0); } } return delay; @@ -2590,14 +2590,14 @@ uint16_t mode_twinklefox() { return twinklefox_base(false); } -static const char _data_FX_MODE_TWINKLEFOX[] PROGMEM = "Twinklefox@!,Twinkle rate,,,,Cool;;!"; +static const char _data_FX_MODE_TWINKLEFOX[] PROGMEM = "Twinklefox@!,Twinkle rate,,,,Cool;!,!;!"; uint16_t mode_twinklecat() { return twinklefox_base(true); } -static const char _data_FX_MODE_TWINKLECAT[] PROGMEM = "Twinklecat@!,Twinkle rate,,,,Cool;;!"; +static const char _data_FX_MODE_TWINKLECAT[] PROGMEM = "Twinklecat@!,Twinkle rate,,,,Cool;!,!;!"; //inspired by https://www.tweaking4all.com/hardware/arduino/adruino-led-strip-effects/#LEDStripEffectBlinkingHalloweenEyes @@ -5946,6 +5946,8 @@ uint16_t mode_2Dscrollingtext(void) { else if (!strncmp_P(text,PSTR("#MMDD"),5)) sprintf_P(text, zero?PSTR("%02d/%02d") :PSTR("%d/%d"), month(localTime), day(localTime)); else if (!strncmp_P(text,PSTR("#TIME"),5)) sprintf_P(text, zero?PSTR("%02d:%02d%s") :PSTR("%2d:%02d%s"), AmPmHour, minute(localTime), sec); else if (!strncmp_P(text,PSTR("#HHMM"),5)) sprintf_P(text, zero?PSTR("%02d:%02d") :PSTR("%d:%02d"), AmPmHour, minute(localTime)); + else if (!strncmp_P(text,PSTR("#HH"),3)) sprintf_P(text, zero?PSTR("%02d") :PSTR("%d"), AmPmHour); + else if (!strncmp_P(text,PSTR("#MM"),3)) sprintf_P(text, zero?PSTR("%02d") :PSTR("%d"), minute(localTime)); } const int numberOfLetters = strlen(text); diff --git a/wled00/FX.h b/wled00/FX.h index c9306e80..d3be5a6c 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -109,20 +109,15 @@ #define PINK (uint32_t)0xFF1493 #define ULTRAWHITE (uint32_t)0xFFFFFFFF #define DARKSLATEGRAY (uint32_t)0x2F4F4F -#define DARKSLATEGREY (uint32_t)0x2F4F4F +#define DARKSLATEGREY DARKSLATEGRAY -// options -// bit 7: segment is in transition mode -// bits 4-6: TBD -// bit 3: mirror effect within segment -// bit 2: segment is on -// bit 1: reverse segment -// bit 0: segment is selected +// segment options #define NO_OPTIONS (uint16_t)0x0000 -#define TRANSPOSED (uint16_t)0x0400 // rotated 90deg & reversed -#define REVERSE_Y_2D (uint16_t)0x0200 -#define MIRROR_Y_2D (uint16_t)0x0100 -#define TRANSITIONAL (uint16_t)0x0080 +#define TRANSPOSED (uint16_t)0x0100 // rotated 90deg & reversed +#define MIRROR_Y_2D (uint16_t)0x0080 +#define REVERSE_Y_2D (uint16_t)0x0040 +#define RESET_REQ (uint16_t)0x0020 +#define FROZEN (uint16_t)0x0010 #define MIRROR (uint16_t)0x0008 #define SEGMENT_ON (uint16_t)0x0004 #define REVERSE (uint16_t)0x0002 @@ -348,12 +343,11 @@ typedef struct Segment { bool mirror : 1; // 3 : mirrored bool freeze : 1; // 4 : paused/frozen bool reset : 1; // 5 : indicates that Segment runtime requires reset - bool transitional: 1; // 6 : transitional (there is transition occuring) - bool reverse_y : 1; // 7 : reversed Y (2D) - bool mirror_y : 1; // 8 : mirrored Y (2D) - bool transpose : 1; // 9 : transposed (2D, swapped X & Y) - uint8_t map1D2D : 3; // 10-12 : mapping for 1D effect on 2D (0-use as strip, 1-expand vertically, 2-circular/arc, 3-rectangular/corner, ...) - uint8_t soundSim : 1; // 13 : 0-1 sound simulation types ("soft" & "hard" or "on"/"off") + 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 }; }; @@ -484,7 +478,6 @@ typedef struct Segment { _dataLen(0), _t(nullptr) { - //refreshLightCapabilities(); #ifdef WLED_DEBUG //Serial.printf("-- Creating segment: %p\n", this); #endif @@ -519,6 +512,7 @@ typedef struct Segment { inline bool getOption(uint8_t n) const { return ((options >> n) & 0x01); } inline bool isSelected(void) const { return selected; } + inline bool isInTransition(void) const { return _t != nullptr; } inline bool isActive(void) const { return stop > start; } inline bool is2D(void) const { return (width()>1 && height()>1); } inline bool hasRGB(void) const { return _isRGB; } @@ -569,15 +563,16 @@ typedef struct Segment { void restoreSegenv(tmpsegd_t &tmpSegD); #endif uint16_t progress(void); //transition progression between 0-65535 - uint8_t currentBri(uint8_t briNew, bool useCct = false); - uint8_t currentMode(uint8_t modeNew); - uint32_t currentColor(uint8_t slot, uint32_t colorNew); + uint8_t currentBri(bool useCct = false); + uint8_t currentMode(void); + uint32_t currentColor(uint8_t slot); CRGBPalette16 &loadPalette(CRGBPalette16 &tgt, uint8_t pal); CRGBPalette16 ¤tPalette(CRGBPalette16 &tgt, uint8_t paletteID); // 1D strip uint16_t virtualLength(void) const; void setPixelColor(int n, uint32_t c); // set relative pixel within segment with color + void setPixelColor(unsigned n, uint32_t c) { setPixelColor(int(n), c); } void setPixelColor(int n, byte r, byte g, byte b, byte w = 0) { setPixelColor(n, RGBW32(r,g,b,w)); } // automatically inline void setPixelColor(int n, CRGB c) { setPixelColor(n, RGBW32(c.r,c.g,c.b,0)); } // automatically inline void setPixelColor(float i, uint32_t c, bool aa = true); @@ -595,7 +590,6 @@ typedef struct Segment { void addPixelColor(int n, byte r, byte g, byte b, byte w = 0, bool fast = false) { addPixelColor(n, RGBW32(r,g,b,w), fast); } // automatically inline void addPixelColor(int n, CRGB c, bool fast = false) { addPixelColor(n, RGBW32(c.r,c.g,c.b,0), fast); } // automatically inline void fadePixelColor(uint16_t n, uint8_t fade); - uint8_t get_random_wheel_index(uint8_t pos); uint32_t color_from_palette(uint16_t, bool mapping, bool wrap, uint8_t mcol, uint8_t pbri = 255); uint32_t color_wheel(uint8_t pos); @@ -606,6 +600,7 @@ typedef struct Segment { #ifndef WLED_DISABLE_2D uint16_t XY(uint16_t x, uint16_t y); // support function to get relative index within segment void setPixelColorXY(int x, int y, uint32_t c); // set relative pixel within segment with color + void setPixelColorXY(unsigned x, unsigned y, uint32_t c) { setPixelColorXY(int(x), int(y), c); } void setPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) { setPixelColorXY(x, y, RGBW32(r,g,b,w)); } // automatically inline void setPixelColorXY(int x, int y, CRGB c) { setPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0)); } // automatically inline void setPixelColorXY(float x, float y, uint32_t c, bool aa = true); @@ -881,16 +876,14 @@ class WS2812FX { // 96 bytes std::vector panel; #endif - void - setUpMatrix(), - setPixelColorXY(int x, int y, uint32_t c); + void setUpMatrix(); // outsmart the compiler :) by correctly overloading - inline void setPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) { setPixelColorXY(x, y, RGBW32(r,g,b,w)); } // automatically inline - inline void setPixelColorXY(int x, int y, CRGB c) { setPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0)); } + inline void setPixelColorXY(int x, int y, uint32_t c) { setPixelColor(y * Segment::maxWidth + x, c); } + inline void setPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) { setPixelColorXY(x, y, RGBW32(r,g,b,w)); } + inline void setPixelColorXY(int x, int y, CRGB c) { setPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0)); } - uint32_t - getPixelColorXY(uint16_t, uint16_t); + inline uint32_t getPixelColorXY(uint16_t x, uint16_t y) { return getPixelColor(isMatrix ? y * Segment::maxWidth + x : x);} // end 2D support diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp index 60a7e68f..5dc9e9ff 100644 --- a/wled00/FX_2Dfcn.cpp +++ b/wled00/FX_2Dfcn.cpp @@ -134,7 +134,7 @@ void WS2812FX::setUpMatrix() { #ifdef WLED_DEBUG DEBUG_PRINT(F("Matrix ledmap:")); - for (uint16_t i=0; i= _length) return; - busses.setPixelColor(index, col); -} - -// returns RGBW values of pixel -uint32_t WS2812FX::getPixelColorXY(uint16_t x, uint16_t y) { -#ifndef WLED_DISABLE_2D - uint16_t index = (y * Segment::maxWidth + x); -#else - uint16_t index = x; -#endif - if (index < customMappingSize) index = customMappingTable[index]; - if (index >= _length) return 0; - return busses.getPixelColor(index); -} /////////////////////////////////////////////////////////// // Segment:: routines @@ -188,18 +163,19 @@ uint32_t WS2812FX::getPixelColorXY(uint16_t x, uint16_t y) { #ifndef WLED_DISABLE_2D // XY(x,y) - gets pixel index within current segment (often used to reference leds[] array element) -uint16_t /*IRAM_ATTR*/ Segment::XY(uint16_t x, uint16_t y) { +uint16_t IRAM_ATTR Segment::XY(uint16_t x, uint16_t y) +{ uint16_t width = virtualWidth(); // segment width in logical pixels (can be 0 if segment is inactive) uint16_t height = virtualHeight(); // segment height in logical pixels (is always >= 1) return isActive() ? (x%width) + (y%height) * width : 0; } -void /*IRAM_ATTR*/ Segment::setPixelColorXY(int x, int y, uint32_t col) +void IRAM_ATTR Segment::setPixelColorXY(int x, int y, uint32_t col) { if (!isActive()) return; // not active if (x >= virtualWidth() || y >= virtualHeight() || x<0 || y<0) return; // if pixel would fall out of virtual segment just exit - uint8_t _bri_t = currentBri(on ? opacity : 0); + uint8_t _bri_t = currentBri(); if (_bri_t < 255) { byte r = scale8(R(col), _bri_t); byte g = scale8(G(col), _bri_t); @@ -310,32 +286,17 @@ void Segment::blendPixelColorXY(uint16_t x, uint16_t y, uint32_t color, uint8_t void Segment::addPixelColorXY(int x, int y, uint32_t color, bool fast) { if (!isActive()) return; // not active if (x >= virtualWidth() || y >= virtualHeight() || x<0 || y<0) return; // if pixel would fall out of virtual segment just exit - uint32_t col = getPixelColorXY(x,y); - uint8_t r = R(col); - uint8_t g = G(col); - uint8_t b = B(col); - uint8_t w = W(col); - if (fast) { - r = qadd8(r, R(color)); - g = qadd8(g, G(color)); - b = qadd8(b, B(color)); - w = qadd8(w, W(color)); - col = RGBW32(r,g,b,w); - } else { - col = color_add(col, color); - } - setPixelColorXY(x, y, col); + setPixelColorXY(x, y, color_add(getPixelColorXY(x,y), color, fast)); } void Segment::fadePixelColorXY(uint16_t x, uint16_t y, uint8_t fade) { if (!isActive()) return; // not active - CRGB pix = CRGB(getPixelColorXY(x,y)).nscale8_video(fade); - setPixelColorXY(x, y, pix); + setPixelColorXY(x, y, color_fade(getPixelColorXY(x,y), fade, true)); } // blurRow: perform a blur on a row of a rectangular matrix void Segment::blurRow(uint16_t row, fract8 blur_amount) { - if (!isActive()) return; // not active + if (!isActive() || blur_amount == 0) return; // not active const uint_fast16_t cols = virtualWidth(); const uint_fast16_t rows = virtualHeight(); @@ -344,7 +305,7 @@ void Segment::blurRow(uint16_t row, fract8 blur_amount) { uint8_t keep = 255 - blur_amount; uint8_t seep = blur_amount >> 1; CRGB carryover = CRGB::Black; - for (uint_fast16_t x = 0; x < cols; x++) { + for (unsigned x = 0; x < cols; x++) { CRGB cur = getPixelColorXY(x, row); CRGB before = cur; // remember color before blur CRGB part = cur; @@ -363,7 +324,7 @@ void Segment::blurRow(uint16_t row, fract8 blur_amount) { // blurCol: perform a blur on a column of a rectangular matrix void Segment::blurCol(uint16_t col, fract8 blur_amount) { - if (!isActive()) return; // not active + if (!isActive() || blur_amount == 0) return; // not active const uint_fast16_t cols = virtualWidth(); const uint_fast16_t rows = virtualHeight(); @@ -372,7 +333,7 @@ void Segment::blurCol(uint16_t col, fract8 blur_amount) { uint8_t keep = 255 - blur_amount; uint8_t seep = blur_amount >> 1; CRGB carryover = CRGB::Black; - for (uint_fast16_t y = 0; y < rows; y++) { + for (unsigned y = 0; y < rows; y++) { CRGB cur = getPixelColorXY(col, y); CRGB part = cur; CRGB before = cur; // remember color before blur @@ -391,7 +352,7 @@ void Segment::blurCol(uint16_t col, fract8 blur_amount) { // 1D Box blur (with added weight - blur_amount: [0=no blur, 255=max blur]) void Segment::box_blur(uint16_t i, bool vertical, fract8 blur_amount) { - if (!isActive()) return; // not active + if (!isActive() || blur_amount == 0) return; // not active const uint16_t cols = virtualWidth(); const uint16_t rows = virtualHeight(); const uint16_t dim1 = vertical ? rows : cols; @@ -401,7 +362,7 @@ void Segment::box_blur(uint16_t i, bool vertical, fract8 blur_amount) { const float keep = 3.f - 2.f*seep; // 1D box blur CRGB tmp[dim1]; - for (uint16_t j = 0; j < dim1; j++) { + for (int j = 0; j < dim1; j++) { uint16_t x = vertical ? i : j; uint16_t y = vertical ? j : i; int16_t xp = vertical ? x : x-1; // "signed" to prevent underflow @@ -417,7 +378,7 @@ void Segment::box_blur(uint16_t i, bool vertical, fract8 blur_amount) { b = (curr.b*keep + (prev.b + next.b)*seep) / 3; tmp[j] = CRGB(r,g,b); } - for (uint16_t j = 0; j < dim1; j++) { + for (int j = 0; j < dim1; j++) { uint16_t x = vertical ? i : j; uint16_t y = vertical ? j : i; setPixelColorXY(x, y, tmp[j]); @@ -440,7 +401,7 @@ void Segment::box_blur(uint16_t i, bool vertical, fract8 blur_amount) { void Segment::blur1d(fract8 blur_amount) { const uint16_t rows = virtualHeight(); - for (uint16_t y = 0; y < rows; y++) blurRow(y, blur_amount); + for (unsigned y = 0; y < rows; y++) blurRow(y, blur_amount); } void Segment::moveX(int8_t delta, bool wrap) { @@ -498,7 +459,7 @@ void Segment::move(uint8_t dir, uint8_t delta, bool wrap) { } void Segment::draw_circle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB col) { - if (!isActive()) return; // not active + if (!isActive() || radius == 0) return; // not active // Bresenham’s Algorithm int d = 3 - (2*radius); int y = radius, x = 0; @@ -523,7 +484,7 @@ void Segment::draw_circle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB col) { // by stepko, taken from https://editor.soulmatelights.com/gallery/573-blobs void Segment::fill_circle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB col) { - if (!isActive()) return; // not active + if (!isActive() || radius == 0) return; // not active const uint16_t cols = virtualWidth(); const uint16_t rows = virtualHeight(); for (int16_t y = -radius; y <= radius; y++) { @@ -540,7 +501,7 @@ void Segment::nscale8(uint8_t scale) { if (!isActive()) return; // not active const uint16_t cols = virtualWidth(); const uint16_t rows = virtualHeight(); - for(uint16_t y = 0; y < rows; y++) for (uint16_t x = 0; x < cols; x++) { + for (int y = 0; y < rows; y++) for (int x = 0; x < cols; x++) { setPixelColorXY(x, y, CRGB(getPixelColorXY(x, y)).nscale8(scale)); } } diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index dc4f3211..930f1f34 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -89,25 +89,19 @@ bool Segment::_modeBlend = false; Segment::Segment(const Segment &orig) { //DEBUG_PRINTF("-- Copy segment constructor: %p -> %p\n", &orig, this); memcpy((void*)this, (void*)&orig, sizeof(Segment)); - transitional = false; // copied segment cannot be in transition - name = nullptr; - data = nullptr; - _dataLen = 0; - _t = nullptr; - if (orig.name) { name = new char[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._t) { _t = new Transition(orig._t->_dur); } + _t = nullptr; // copied segment cannot be in transition + if (orig.name) { name = new char[strlen(orig.name)+1]; if (name) strcpy(name, orig.name); } else { name = nullptr; } + if (orig.data) { if (allocateData(orig._dataLen)) memcpy(data, orig.data, orig._dataLen); } else { data = nullptr; _dataLen = 0; } } // move constructor Segment::Segment(Segment &&orig) noexcept { //DEBUG_PRINTF("-- Move segment constructor: %p -> %p\n", &orig, this); memcpy((void*)this, (void*)&orig, sizeof(Segment)); - orig.transitional = false; // old segment cannot be in transition any more orig.name = nullptr; orig.data = nullptr; orig._dataLen = 0; - orig._t = nullptr; + orig._t = nullptr; // old segment cannot be in transition any more } // copy assignment @@ -115,27 +109,23 @@ Segment& Segment::operator= (const Segment &orig) { //DEBUG_PRINTF("-- Copying segment: %p -> %p\n", &orig, this); if (this != &orig) { // clean destination - transitional = false; // copied segment cannot be in transition - if (name) delete[] name; + if (name) { delete[] name; name = nullptr; } + if (orig.name) { name = new char[strlen(orig.name)+1]; if (name) strcpy(name, orig.name); } if (_t) { #ifndef WLED_DISABLE_MODE_BLEND if (_t->_segT._dataT) free(_t->_segT._dataT); #endif delete _t; + _t = nullptr; // copied segment cannot be in transition } deallocateData(); // copy source memcpy((void*)this, (void*)&orig, sizeof(Segment)); - transitional = false; // erase pointers to allocated data - name = nullptr; data = nullptr; _dataLen = 0; - _t = nullptr; // copy source data - if (orig.name) { name = new char[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._t) { _t = new Transition(orig._t->_dur, orig._t->_briT, orig._t->_cctT, orig._t->_colorT); } } return *this; } @@ -144,9 +134,7 @@ Segment& Segment::operator= (const Segment &orig) { Segment& Segment::operator= (Segment &&orig) noexcept { //DEBUG_PRINTF("-- Moving segment: %p -> %p\n", &orig, this); if (this != &orig) { - transitional = false; // just temporary if (name) { delete[] name; name = nullptr; } // free old name - deallocateData(); // free old runtime data if (_t) { #ifndef WLED_DISABLE_MODE_BLEND if (_t->_segT._dataT) free(_t->_segT._dataT); @@ -154,12 +142,12 @@ Segment& Segment::operator= (Segment &&orig) noexcept { delete _t; _t = nullptr; } + deallocateData(); // free old runtime data memcpy((void*)this, (void*)&orig, sizeof(Segment)); - orig.transitional = false; // old segment cannot be in transition orig.name = nullptr; orig.data = nullptr; orig._dataLen = 0; - orig._t = nullptr; + orig._t = nullptr; // old segment cannot be in transition } return *this; } @@ -237,7 +225,7 @@ CRGBPalette16 &Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) { switch (pal) { case 0: //default palette. Exceptions for specific effects above targetPalette = PartyColors_p; break; - case 1: {//periodically replace palette with a random one. Transition palette change in 500ms + case 1: {//periodically replace palette with a random one unsigned long timeSinceLastChange = millis() - _lastPaletteChange; if (timeSinceLastChange > randomPaletteChangeTime * 1000U) { _randomPalette = _newRandomPalette; @@ -301,47 +289,45 @@ CRGBPalette16 &Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) { } void Segment::startTransition(uint16_t dur) { - if (!dur) { - if (_t) _t->_dur = dur; // this will stop transition in next handleTransisiton() - else transitional = false; + if (dur == 0) { + if (isInTransition()) _t->_dur = dur; // this will stop transition in next handleTransisiton() return; } - if (transitional && _t) return; // already in transition no need to store anything + 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 Transition(dur); // no previous transition running if (!_t) return; // failed to allocate data //DEBUG_PRINTF("-- Started transition: %p\n", this); - CRGBPalette16 _palT = CRGBPalette16(DEFAULT_COLOR); loadPalette(_palT, palette); - _t->_palT = _palT; + loadPalette(_t->_palT, palette); _t->_briT = on ? opacity : 0; _t->_cctT = cct; #ifndef WLED_DISABLE_MODE_BLEND - swapSegenv(_t->_segT); - _t->_modeT = mode; - _t->_segT._optionsT |= 0b0000000001000000; // mark old segment transitional - _t->_segT._dataLenT = 0; - _t->_segT._dataT = nullptr; - if (_dataLen > 0 && data) { - _t->_segT._dataT = (byte *)malloc(_dataLen); - if (_t->_segT._dataT) { - //DEBUG_PRINTF("-- Allocated duplicate data (%d): %p\n", _dataLen, _t->_segT._dataT); - memcpy(_t->_segT._dataT, data, _dataLen); - _t->_segT._dataLenT = _dataLen; + if (modeBlending) { + swapSegenv(_t->_segT); + _t->_modeT = mode; + _t->_segT._dataLenT = 0; + _t->_segT._dataT = nullptr; + if (_dataLen > 0 && data) { + _t->_segT._dataT = (byte *)malloc(_dataLen); + if (_t->_segT._dataT) { + //DEBUG_PRINTF("-- Allocated duplicate data (%d): %p\n", _dataLen, _t->_segT._dataT); + memcpy(_t->_segT._dataT, data, _dataLen); + _t->_segT._dataLenT = _dataLen; + } } + } else { + for (size_t i=0; i_segT._colorT[i] = colors[i]; } #else for (size_t i=0; i_colorT[i] = colors[i]; #endif - transitional = true; // setOption(SEG_OPTION_TRANSITIONAL, true); } void Segment::stopTransition() { - if (!transitional) return; - transitional = false; // finish transitioning segment //DEBUG_PRINTF("-- Stopping transition: %p\n", this); - if (_t) { + if (isInTransition()) { #ifndef WLED_DISABLE_MODE_BLEND if (_t->_segT._dataT && _t->_segT._dataLenT > 0) { //DEBUG_PRINTF("-- Released duplicate data (%d): %p\n", _t->_segT._dataLenT, _t->_segT._dataT); @@ -356,14 +342,13 @@ void Segment::stopTransition() { } void Segment::handleTransition() { - if (!transitional) return; uint16_t _progress = progress(); if (_progress == 0xFFFFU) stopTransition(); } // transition progression between 0-65535 uint16_t Segment::progress() { - if (transitional && _t) { + if (isInTransition()) { unsigned long timeNow = millis(); if (_t->_dur > 0 && timeNow - _t->_start < _t->_dur) return (timeNow - _t->_start) * 0xFFFFU / _t->_dur; } @@ -420,8 +405,8 @@ void Segment::restoreSegenv(tmpsegd_t &tmpSeg) { _t->_segT._stepT = step; _t->_segT._callT = call; //if (_t->_segT._dataT != data) DEBUG_PRINTF("--- data re-allocated: (%p) %p -> %p\n", this, _t->_segT._dataT, data); - _t->_segT._dataT = data; // sometimes memory gets re-allocated (!! INVESTIGATE WHY !!) - _t->_segT._dataLenT = _dataLen; // sometimes memory gets re-allocated (!! INVESTIGATE WHY !!) + _t->_segT._dataT = data; + _t->_segT._dataLenT = _dataLen; } options = tmpSeg._optionsT; for (size_t i=0; i_cctT * (0xFFFFU - prog)) >> 16; - else return ((briNew * prog) + _t->_briT * (0xFFFFU - prog)) >> 16; + uint32_t curBri = (useCct ? cct : (on ? opacity : 0)) * prog; + curBri += (useCct ? _t->_cctT : (on ? _t->_briT : 0)) * (0xFFFFU - prog); + return curBri / 0xFFFFU; } - return briNew; + return (useCct ? cct : (on ? opacity : 0)); } -uint8_t Segment::currentMode(uint8_t newMode) { +uint8_t Segment::currentMode() { #ifndef WLED_DISABLE_MODE_BLEND - uint16_t prog = progress(); // implicit check for transitional & _t in progress() - if (prog < 0xFFFFU) return _t->_modeT; + uint16_t prog = progress(); + if (modeBlending && prog < 0xFFFFU) return _t->_modeT; #endif - return newMode; + return mode; } -uint32_t Segment::currentColor(uint8_t slot, uint32_t colorNew) { +uint32_t Segment::currentColor(uint8_t slot) { #ifndef WLED_DISABLE_MODE_BLEND - return transitional && _t ? color_blend(_t->_segT._colorT[slot], colorNew, progress(), true) : colorNew; + return isInTransition() ? color_blend(_t->_segT._colorT[slot], colors[slot], progress(), true) : colors[slot]; #else - return transitional && _t ? color_blend(_t->_colorT[slot], colorNew, progress(), true) : colorNew; + return isInTransition() ? color_blend(_t->_colorT[slot], colors[slot], progress(), true) : colors[slot]; #endif } CRGBPalette16 &Segment::currentPalette(CRGBPalette16 &targetPalette, uint8_t pal) { loadPalette(targetPalette, pal); - if (progress() < 0xFFFFU) { + uint16_t prog = progress(); + if (strip.paletteFade && prog < 0xFFFFU) { // blend palettes // there are about 255 blend passes of 48 "blends" to completely blend two palettes (in _dur time) // minimum blend time is 100ms maximum is 65535ms - unsigned long timeMS = millis() - _t->_start; - uint16_t noOfBlends = (255U * timeMS / _t->_dur) - _t->_prevPaletteBlends; + uint16_t noOfBlends = ((255U * prog) / 0xFFFFU) - _t->_prevPaletteBlends; for (int i=0; i_prevPaletteBlends++) nblendPaletteTowardPalette(_t->_palT, targetPalette, 48); targetPalette = _t->_palT; // copy transitioning/temporary palette } @@ -500,6 +486,8 @@ void Segment::setUp(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, uint16_t && (!grp || (grouping == grp && spacing == spc)) && (ofs == UINT16_MAX || ofs == offset)) return; + stateChanged = true; // send UDP/WS broadcast + if (stop) fill(BLACK); // turn old segment range off (clears pixels if changing spacing) if (grp) { // prevent assignment of 0 grouping = grp; @@ -510,6 +498,10 @@ void Segment::setUp(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, uint16_t } if (ofs < UINT16_MAX) offset = ofs; + DEBUG_PRINT(F("setUp segment: ")); DEBUG_PRINT(i1); + DEBUG_PRINT(','); DEBUG_PRINT(i2); + DEBUG_PRINT(F(" -> ")); DEBUG_PRINT(i1Y); + DEBUG_PRINT(','); DEBUG_PRINTLN(i2Y); markForReset(); if (boundsUnchanged) return; @@ -564,7 +556,6 @@ void Segment::setCCT(uint16_t k) { void Segment::setOpacity(uint8_t o) { if (opacity == o) return; if (fadeTransition) startTransition(strip.getTransition()); // start transition prior to change - DEBUG_PRINT(F("-- Setting opacity: ")); DEBUG_PRINTLN(o); opacity = o; stateChanged = true; // send UDP/WS broadcast } @@ -574,14 +565,16 @@ void Segment::setOption(uint8_t n, bool val) { if (fadeTransition && n == SEG_OPTION_ON && val != prevOn) startTransition(strip.getTransition()); // start transition prior to change if (val) options |= 0x01 << n; else options &= ~(0x01 << n); - if (!(n == SEG_OPTION_SELECTED || n == SEG_OPTION_RESET || n == SEG_OPTION_TRANSITIONAL)) stateChanged = true; // send UDP/WS broadcast + if (!(n == SEG_OPTION_SELECTED || n == SEG_OPTION_RESET)) stateChanged = true; // send UDP/WS broadcast } void Segment::setMode(uint8_t fx, bool loadDefaults) { // if we have a valid mode & is not reserved if (fx < strip.getModeCount() && strncmp_P("RSVD", strip.getModeData(fx), 4)) { if (fx != mode) { - if (fadeTransition) startTransition(strip.getTransition()); // set effect transitions +#ifndef WLED_DISABLE_MODE_BLEND + if (modeBlending) startTransition(strip.getTransition()); // set effect transitions +#endif mode = fx; // load default values from effect string if (loadDefaults) { @@ -743,7 +736,7 @@ void IRAM_ATTR Segment::setPixelColor(int i, uint32_t col) #endif uint16_t len = length(); - uint8_t _bri_t = currentBri(on ? opacity : 0); + uint8_t _bri_t = currentBri(); if (_bri_t < 255) { byte r = scale8(R(col), _bri_t); byte g = scale8(G(col), _bri_t); @@ -877,10 +870,11 @@ uint8_t Segment::differs(Segment& b) const { if (startY != b.startY) d |= SEG_DIFFERS_BOUNDS; if (stopY != b.stopY) d |= SEG_DIFFERS_BOUNDS; - //bit pattern: (msb first) set:2, sound:1, mapping:3, transposed, mirrorY, reverseY, [transitional, reset,] paused, mirrored, on, reverse, [selected] - if ((options & 0b1111111110011110U) != (b.options & 0b1111111110011110U)) d |= SEG_DIFFERS_OPT; + //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 (uint8_t i = 0; i < NUM_COLORS; i++) if (colors[i] != b.colors[i]) d |= SEG_DIFFERS_COL; + for (unsigned i = 0; i < NUM_COLORS; i++) if (colors[i] != b.colors[i]) d |= SEG_DIFFERS_COL; return d; } @@ -912,7 +906,7 @@ void Segment::refreshLightCapabilities() { segStopIdx = stop; } - for (uint8_t b = 0; b < busses.getNumBusses(); b++) { + for (unsigned b = 0; b < busses.getNumBusses(); b++) { Bus *bus = busses.getBus(b); if (bus == nullptr || bus->getLength()==0) break; if (!bus->isOk()) continue; @@ -942,7 +936,7 @@ void Segment::fill(uint32_t c) { if (!isActive()) return; // not active const uint16_t cols = is2D() ? virtualWidth() : virtualLength(); const uint16_t rows = virtualHeight(); // will be 1 for 1D - for(uint16_t y = 0; y < rows; y++) for (uint16_t x = 0; x < cols; x++) { + for (int y = 0; y < rows; y++) for (int x = 0; x < cols; x++) { if (is2D()) setPixelColorXY(x, y, c); else setPixelColor(x, c); } @@ -956,27 +950,12 @@ void Segment::blendPixelColor(int n, uint32_t color, uint8_t blend) { // Adds the specified color with the existing pixel color perserving color balance. void Segment::addPixelColor(int n, uint32_t color, bool fast) { if (!isActive()) return; // not active - uint32_t col = getPixelColor(n); - uint8_t r = R(col); - uint8_t g = G(col); - uint8_t b = B(col); - uint8_t w = W(col); - if (fast) { - r = qadd8(r, R(color)); - g = qadd8(g, G(color)); - b = qadd8(b, B(color)); - w = qadd8(w, W(color)); - col = RGBW32(r,g,b,w); - } else { - col = color_add(col, color); - } - setPixelColor(n, col); + setPixelColor(n, color_add(getPixelColor(n), color, fast)); } void Segment::fadePixelColor(uint16_t n, uint8_t fade) { if (!isActive()) return; // not active - CRGB pix = CRGB(getPixelColor(n)).nscale8_video(fade); - setPixelColor(n, pix); + setPixelColor(n, color_fade(getPixelColor(n), fade, true)); } /* @@ -996,7 +975,7 @@ void Segment::fade_out(uint8_t rate) { int g2 = G(color); int b2 = B(color); - for (uint16_t y = 0; y < rows; y++) for (uint16_t x = 0; x < cols; x++) { + for (int y = 0; y < rows; y++) for (int x = 0; x < cols; x++) { color = is2D() ? getPixelColorXY(x, y) : getPixelColor(x); int w1 = W(color); int r1 = R(color); @@ -1025,9 +1004,9 @@ void Segment::fadeToBlackBy(uint8_t fadeBy) { const uint16_t cols = is2D() ? virtualWidth() : virtualLength(); const uint16_t rows = virtualHeight(); // will be 1 for 1D - for (uint16_t y = 0; y < rows; y++) for (uint16_t x = 0; x < cols; x++) { - if (is2D()) setPixelColorXY(x, y, CRGB(getPixelColorXY(x,y)).nscale8(255-fadeBy)); - else setPixelColor(x, CRGB(getPixelColor(x)).nscale8(255-fadeBy)); + for (int y = 0; y < rows; y++) for (int x = 0; x < cols; x++) { + if (is2D()) setPixelColorXY(x, y, color_fade(getPixelColorXY(x,y), 255-fadeBy)); + else setPixelColor(x, color_fade(getPixelColor(x), 255-fadeBy)); } } @@ -1040,34 +1019,26 @@ void Segment::blur(uint8_t blur_amount) #ifndef WLED_DISABLE_2D if (is2D()) { // compatibility with 2D - const uint_fast16_t cols = virtualWidth(); - const uint_fast16_t rows = virtualHeight(); - for (uint_fast16_t i = 0; i < rows; i++) blurRow(i, blur_amount); // blur all rows - for (uint_fast16_t k = 0; k < cols; k++) blurCol(k, blur_amount); // blur all columns + const unsigned cols = virtualWidth(); + const unsigned rows = virtualHeight(); + for (unsigned i = 0; i < rows; i++) blurRow(i, blur_amount); // blur all rows + for (unsigned k = 0; k < cols; k++) blurCol(k, blur_amount); // blur all columns return; } #endif uint8_t keep = 255 - blur_amount; uint8_t seep = blur_amount >> 1; - CRGB carryover = CRGB::Black; - uint_fast16_t vlength = virtualLength(); - for(uint_fast16_t i = 0; i < vlength; i++) - { - CRGB cur = CRGB(getPixelColor(i)); - CRGB part = cur; - CRGB before = cur; // remember color before blur - part.nscale8(seep); - cur.nscale8(keep); - cur += carryover; - if(i > 0) { + uint32_t carryover = BLACK; + unsigned vlength = virtualLength(); + for (unsigned i = 0; i < vlength; i++) { + uint32_t cur = getPixelColor(i); + uint32_t part = color_fade(cur, seep); + cur = color_add(color_fade(cur, keep), carryover, true); + if (i > 0) { uint32_t c = getPixelColor(i-1); - uint8_t r = R(c); - uint8_t g = G(c); - uint8_t b = B(c); - setPixelColor((uint16_t)(i-1), qadd8(r, part.red), qadd8(g, part.green), qadd8(b, part.blue)); + setPixelColor(i-1, color_add(c, part, true)); } - if (before != cur) // optimization: only set pixel if color has changed - setPixelColor((uint16_t)i,cur.red, cur.green, cur.blue); + setPixelColor(i, cur); carryover = part; } } @@ -1080,7 +1051,7 @@ void Segment::blur(uint8_t blur_amount) uint32_t Segment::color_wheel(uint8_t pos) { if (palette) return color_from_palette(pos, false, true, 0); pos = 255 - pos; - if(pos < 85) { + if (pos < 85) { return ((uint32_t)(255 - pos * 3) << 16) | ((uint32_t)(0) << 8) | (pos * 3); } else if(pos < 170) { pos -= 85; @@ -1091,21 +1062,6 @@ uint32_t Segment::color_wheel(uint8_t pos) { } } -/* - * Returns a new, random wheel index with a minimum distance of 42 from pos. - */ -uint8_t Segment::get_random_wheel_index(uint8_t pos) { - uint8_t r = 0, x = 0, y = 0, d = 0; - - while(d < 42) { - r = random8(); - x = abs(pos - r); - y = 255 - x; - d = MIN(x, y); - } - return r; -} - /* * Gets a single color from the currently selected palette. * @param i Palette Index (if mapping is true, the full palette will be _virtualSegmentLength long, if false, 255). Will wrap around automatically. @@ -1119,20 +1075,20 @@ uint32_t Segment::color_from_palette(uint16_t i, bool mapping, bool wrap, uint8_ { // default palette or no RGB support on segment if ((palette == 0 && mcol < NUM_COLORS) || !_isRGB) { - uint32_t color = currentColor(mcol, colors[mcol]); + uint32_t color = currentColor(mcol); color = gamma32(color); if (pbri == 255) return color; - return RGBW32(scale8_video(R(color),pbri), scale8_video(G(color),pbri), scale8_video(B(color),pbri), scale8_video(W(color),pbri)); + return color_fade(color, pbri, true); } uint8_t paletteIndex = i; if (mapping && virtualLength() > 1) paletteIndex = (i*255)/(virtualLength() -1); - if (!wrap) paletteIndex = scale8(paletteIndex, 240); //cut off blend at palette "end" - CRGB fastled_col; + if (!wrap && strip.paletteBlend != 3) paletteIndex = scale8(paletteIndex, 240); //cut off blend at palette "end" CRGBPalette16 curPal; - if (transitional && _t) curPal = _t->_palT; - else loadPalette(curPal, palette); - fastled_col = ColorFromPalette(curPal, paletteIndex, pbri, (strip.paletteBlend == 3)? NOBLEND:LINEARBLEND); // NOTE: paletteBlend should be global + curPal = currentPalette(curPal, palette); + //if (isInTransition()) curPal = _t->_palT; + //else loadPalette(curPal, palette); + CRGB fastled_col = ColorFromPalette(curPal, paletteIndex, pbri, (strip.paletteBlend == 3)? NOBLEND:LINEARBLEND); // NOTE: paletteBlend should be global return RGBW32(fastled_col.r, fastled_col.g, fastled_col.b, 0); } @@ -1166,7 +1122,7 @@ void WS2812FX::finalizeInit(void) const uint8_t defNumBusses = ((sizeof defDataPins) / (sizeof defDataPins[0])); const uint8_t defNumCounts = ((sizeof defCounts) / (sizeof defCounts[0])); uint16_t prevLen = 0; - for (uint8_t i = 0; i < defNumBusses && i < WLED_MAX_BUSSES+WLED_MIN_VIRTUAL_BUSSES; i++) { + for (int i = 0; i < defNumBusses && i < WLED_MAX_BUSSES+WLED_MIN_VIRTUAL_BUSSES; i++) { uint8_t defPin[] = {defDataPins[i]}; uint16_t start = prevLen; uint16_t count = defCounts[(i < defNumCounts) ? i : defNumCounts -1]; @@ -1177,7 +1133,7 @@ void WS2812FX::finalizeInit(void) } _length = 0; - for (uint8_t i=0; igetStart() + bus->getLength() > MAX_LEDS) break; @@ -1234,24 +1190,24 @@ void WS2812FX::service() { if (!seg.freeze) { //only run effect function if not frozen _virtualSegmentLength = seg.virtualLength(); - _colors_t[0] = seg.currentColor(0, seg.colors[0]); - _colors_t[1] = seg.currentColor(1, seg.colors[1]); - _colors_t[2] = seg.currentColor(2, seg.colors[2]); - seg.currentPalette(_currentPalette, seg.palette); + _colors_t[0] = seg.currentColor(0); + _colors_t[1] = seg.currentColor(1); + _colors_t[2] = seg.currentColor(2); + seg.currentPalette(_currentPalette, seg.palette); // we need to pass reference - if (!cctFromRgb || correctWB) busses.setSegmentCCT(seg.currentBri(seg.cct, true), correctWB); - for (uint8_t c = 0; c < NUM_COLORS; c++) _colors_t[c] = gamma32(_colors_t[c]); + if (!cctFromRgb || correctWB) busses.setSegmentCCT(seg.currentBri(true), correctWB); + for (int c = 0; c < NUM_COLORS; c++) _colors_t[c] = gamma32(_colors_t[c]); // 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/transitional mode. + // 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. - [[maybe_unused]] uint8_t tmpMode = seg.currentMode(seg.mode); // this will return old mode while in transition + [[maybe_unused]] uint8_t tmpMode = seg.currentMode(); // this will return old mode while in transition delay = (*_mode[seg.mode])(); // run new/current mode #ifndef WLED_DISABLE_MODE_BLEND - if (seg.mode != tmpMode) { + if (modeBlending && seg.mode != tmpMode) { Segment::tmpsegd_t _tmpSegData; Segment::modeBlend(true); // set semaphore seg.swapSegenv(_tmpSegData); // temporarily store new mode state (and swap it with transitional state) @@ -1262,7 +1218,7 @@ void WS2812FX::service() { } #endif if (seg.mode != FX_MODE_HALLOWEEN_EYES) seg.call++; - if (seg.transitional && delay > FRAMETIME) delay = FRAMETIME; // force faster updates during transition + if (seg.isInTransition() && delay > FRAMETIME) delay = FRAMETIME; // force faster updates during transition } seg.next_time = nowUp + delay; @@ -1399,12 +1355,12 @@ void WS2812FX::show(void) { // or async show has a separate buffer (ESP32 RMT and I2S are ok) if (newBri < _brightness) busses.setBrightness(_brightness); - unsigned long now = millis(); - size_t diff = now - _lastShow; + unsigned long showNow = millis(); + size_t diff = showNow - _lastShow; size_t fpsCurr = 200; if (diff > 0) fpsCurr = 1000 / diff; _cumulativeFps = (3 * _cumulativeFps + fpsCurr +2) >> 2; // "+2" for proper rounding (2/4 = 0.5) - _lastShow = now; + _lastShow = showNow; } /** @@ -1597,10 +1553,12 @@ void WS2812FX::setSegment(uint8_t segId, uint16_t i1, uint16_t i2, uint8_t group _qStart = i1; _qStop = i2; _qStartY = startY; _qStopY = stopY; _qGrouping = grouping; _qSpacing = spacing; _qOffset = offset; _queuedChangesSegId = segId; + DEBUG_PRINT(F("Segment queued: ")); DEBUG_PRINTLN(segId); return; // queued changes are applied immediately after effect function returns } _segments[segId].setUp(i1, i2, grouping, spacing, offset, startY, stopY); + if (segId > 0 && segId == getSegmentsNum()-1 && i2 <= i1) _segments.pop_back(); // if last segment was deleted remove it from vector } void WS2812FX::setUpSegmentFromQueuedChanges() { @@ -1729,7 +1687,7 @@ void WS2812FX::fixInvalidSegments() { bool WS2812FX::checkSegmentAlignment() { bool aligned = false; for (segment &seg : _segments) { - for (uint8_t b = 0; bgetStart() && seg.stop == bus->getStart() + bus->getLength()) aligned = true; } @@ -1752,13 +1710,8 @@ uint8_t WS2812FX::setPixelSegment(uint8_t n) { } void WS2812FX::setRange(uint16_t i, uint16_t i2, uint32_t col) { - if (i2 >= i) - { - for (uint16_t x = i; x <= i2; x++) setPixelColor(x, col); - } else - { - for (uint16_t x = i2; x <= i; x++) setPixelColor(x, col); - } + if (i2 < i) std::swap(i,i2); + for (unsigned x = i; x <= i2; x++) setPixelColor(x, col); } void WS2812FX::setTransitionMode(bool t) { @@ -1793,7 +1746,7 @@ void WS2812FX::loadCustomPalettes() { if (readObjectFromFile(fileName, nullptr, &pDoc)) { JsonArray pal = pDoc[F("palette")]; - if (!pal.isNull() && pal.size()>4) { // not an empty palette (at least 2 entries) + 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); @@ -1802,7 +1755,7 @@ void WS2812FX::loadCustomPalettes() { 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] = rgbw[c]; // only use RGB component + for (size_t c=0; c<3; c++) tcp[j+1+c] = gamma8(rgbw[c]); // only use RGB component DEBUG_PRINTF("%d(%d) : %d %d %d\n", i, int(tcp[j]), int(tcp[j+1]), int(tcp[j+2]), int(tcp[j+3])); } } else { @@ -1810,13 +1763,15 @@ void WS2812FX::loadCustomPalettes() { 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] = (uint8_t) pal[i+1].as(); // R - tcp[i+2] = (uint8_t) pal[i+2].as(); // G - tcp[i+3] = (uint8_t) pal[i+3].as(); // B + 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("%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 { @@ -1832,7 +1787,7 @@ bool WS2812FX::deserializeMap(uint8_t n) { char fileName[32]; strcpy_P(fileName, PSTR("/ledmap")); if (n) sprintf(fileName +7, "%d", n); - strcat(fileName, ".json"); + strcat_P(fileName, PSTR(".json")); bool isFile = WLED_FS.exists(fileName); if (!isFile) { @@ -1866,7 +1821,7 @@ bool WS2812FX::deserializeMap(uint8_t n) { if (!map.isNull() && map.size()) { // not an empty map customMappingSize = map.size(); customMappingTable = new uint16_t[customMappingSize]; - for (uint16_t i=0; i= 0) transitionDelay = transitionDelayDefault = tdd * 100; CJSON(strip.paletteFade, light_tr["pal"]); @@ -827,6 +830,7 @@ void serializeConfig() { JsonObject light_tr = light.createNestedObject("tr"); light_tr["mode"] = fadeTransition; + light_tr["fx"] = modeBlending; light_tr["dur"] = transitionDelayDefault / 100; light_tr["pal"] = strip.paletteFade; light_tr[F("rpc")] = randomPaletteChangeTime; @@ -888,6 +892,7 @@ void serializeConfig() { if_live[F("no-gc")] = arlsDisableGammaCorrection; if_live[F("offset")] = arlsOffset; +#ifndef WLED_DISABLE_ALEXA JsonObject if_va = interfaces.createNestedObject("va"); if_va[F("alexa")] = alexaEnabled; @@ -896,6 +901,7 @@ void serializeConfig() { if_va_macros.add(macroAlexaOff); if_va["p"] = alexaNumPresets; +#endif #ifdef WLED_ENABLE_MQTT JsonObject if_mqtt = interfaces.createNestedObject("mqtt"); @@ -1033,7 +1039,7 @@ bool deserializeConfigSec() { JsonObject ap = doc["ap"]; getStringFromJson(apPass, ap["psk"] , 65); - JsonObject interfaces = doc["if"]; + [[maybe_unused]] JsonObject interfaces = doc["if"]; #ifdef WLED_ENABLE_MQTT JsonObject if_mqtt = interfaces["mqtt"]; @@ -1072,7 +1078,7 @@ void serializeConfigSec() { JsonObject ap = doc.createNestedObject("ap"); ap["psk"] = apPass; - JsonObject interfaces = doc.createNestedObject("if"); + [[maybe_unused]] JsonObject interfaces = doc.createNestedObject("if"); #ifdef WLED_ENABLE_MQTT JsonObject if_mqtt = interfaces.createNestedObject("mqtt"); if_mqtt["psk"] = mqttPass; diff --git a/wled00/colors.cpp b/wled00/colors.cpp index 8c4baabb..21c27d65 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -35,23 +35,59 @@ uint32_t color_blend(uint32_t color1, uint32_t color2, uint16_t blend, bool b16) * color add function that preserves ratio * idea: https://github.com/Aircoookie/WLED/pull/2465 by https://github.com/Proto-molecule */ -uint32_t color_add(uint32_t c1, uint32_t c2) +uint32_t color_add(uint32_t c1, uint32_t c2, bool fast) { - uint32_t r = R(c1) + R(c2); - uint32_t g = G(c1) + G(c2); - uint32_t b = B(c1) + B(c2); - uint32_t w = W(c1) + W(c2); - uint16_t max = r; - if (g > max) max = g; - if (b > max) max = b; - if (w > max) max = w; - if (max < 256) return RGBW32(r, g, b, w); - else return RGBW32(r * 255 / max, g * 255 / max, b * 255 / max, w * 255 / max); + if (fast) { + uint8_t r = R(c1); + uint8_t g = G(c1); + uint8_t b = B(c1); + uint8_t w = W(c1); + r = qadd8(r, R(c2)); + g = qadd8(g, G(c2)); + b = qadd8(b, B(c2)); + w = qadd8(w, W(c2)); + return RGBW32(r,g,b,w); + } else { + uint32_t r = R(c1) + R(c2); + uint32_t g = G(c1) + G(c2); + uint32_t b = B(c1) + B(c2); + uint32_t w = W(c1) + W(c2); + uint16_t max = r; + if (g > max) max = g; + if (b > max) max = b; + if (w > max) max = w; + if (max < 256) return RGBW32(r, g, b, w); + else return RGBW32(r * 255 / max, g * 255 / max, b * 255 / max, w * 255 / max); + } +} + +/* + * fades color toward black + * if using "video" method the resulting color will never become black unless it is already black + */ +uint32_t color_fade(uint32_t c1, uint8_t amount, bool video) +{ + uint8_t r = R(c1); + uint8_t g = G(c1); + uint8_t b = B(c1); + uint8_t w = W(c1); + if (video) { + r = scale8_video(r, amount); + g = scale8_video(g, amount); + b = scale8_video(b, amount); + w = scale8_video(w, amount); + } else { + r = scale8(r, amount); + g = scale8(g, amount); + b = scale8(b, amount); + w = scale8(w, amount); + } + return RGBW32(r, g, b, w); } void setRandomColor(byte* rgb) { - lastRandomIndex = strip.getMainSegment().get_random_wheel_index(lastRandomIndex); + lastRandomIndex = get_random_wheel_index(lastRandomIndex); colorHStoRGB(lastRandomIndex*256,255,rgb); } diff --git a/wled00/const.h b/wled00/const.h index 6ee83451..657cb88b 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -166,7 +166,7 @@ #define CALL_MODE_NO_NOTIFY 5 #define CALL_MODE_FX_CHANGED 6 //no longer used #define CALL_MODE_HUE 7 -#define CALL_MODE_PRESET_CYCLE 8 +#define CALL_MODE_PRESET_CYCLE 8 //no longer used #define CALL_MODE_BLYNK 9 //no longer used #define CALL_MODE_ALEXA 10 #define CALL_MODE_WS_SEND 11 //special call mode, not for notifier, updates websocket only @@ -313,10 +313,9 @@ #define SEG_OPTION_MIRROR 3 //Indicates that the effect will be mirrored within the segment #define SEG_OPTION_FREEZE 4 //Segment contents will not be refreshed #define SEG_OPTION_RESET 5 //Segment runtime requires reset -#define SEG_OPTION_TRANSITIONAL 6 -#define SEG_OPTION_REVERSED_Y 7 -#define SEG_OPTION_MIRROR_Y 8 -#define SEG_OPTION_TRANSPOSED 9 +#define SEG_OPTION_REVERSED_Y 6 +#define SEG_OPTION_MIRROR_Y 7 +#define SEG_OPTION_TRANSPOSED 8 //Segment differs return byte #define SEG_DIFFERS_BRI 0x01 // opacity @@ -345,6 +344,7 @@ #define ERR_FS_QUOTA 11 // The FS is full or the maximum file size is reached #define ERR_FS_PLOAD 12 // It was attempted to load a preset that does not exist #define ERR_FS_IRLOAD 13 // It was attempted to load an IR JSON cmd, but the "ir.json" file does not exist +#define ERR_FS_RMLOAD 14 // It was attempted to load an remote JSON cmd, but the "remote.json" file does not exist #define ERR_FS_GENERAL 19 // A general unspecified filesystem error occured #define ERR_OVERTEMP 30 // An attached temperature sensor has measured above threshold temperature (not implemented) #define ERR_OVERCURRENT 31 // An attached current sensor has measured a current above the threshold (not implemented) diff --git a/wled00/data/index.htm b/wled00/data/index.htm index 0cf48d6e..e6ce0489 100644 --- a/wled00/data/index.htm +++ b/wled00/data/index.htm @@ -198,10 +198,11 @@ -
- - -
+ +
+ + +
@@ -392,6 +393,7 @@
For best performance, it is recommended to turn off the streaming source when not in use. +
diff --git a/wled00/data/index.js b/wled00/data/index.js index c356efa8..94731fd4 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -33,10 +33,9 @@ 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"], - [2023,3,9,2,"https://aircoookie.github.io/easter.png"], [2024,2,31,2,"https://aircoookie.github.io/easter.png"], - [0,6,4,1,"https://initiate.alphacoders.com/download/wallpaper/516792/images/jpg/510921363292536"], // 4th of July - [0,0,1,1,"https://initiate.alphacoders.com/download/wallpaper/1198800/images/jpg/2522807481585600"] // new year + [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 ]; function handleVisibilityChange() {if (!d.hidden && new Date () - lastUpdate > 3000) requestJson();} @@ -1209,7 +1208,7 @@ function updateUI() if (hasRGB) { updateTrail(gId('sliderR')); updateTrail(gId('sliderG')); - updateTrail(gId('sliderB')); + updateTrail(gId('sliderB')); } if (hasWhite) updateTrail(gId('sliderW')); @@ -1514,12 +1513,15 @@ function setEffectParameters(idx) } // set the bottom position of selected effect (sticky) as the top of sliders div - setInterval(()=>{ + function setSelectedEffectPosition() { let top = parseInt(getComputedStyle(gId("sliders")).height); top += 5; let sel = d.querySelector('#fxlist .selected'); if (sel) sel.style.bottom = top + "px"; // we will need to remove this when unselected (in setFX()) - },750); + } + + setSelectedEffectPosition(); + setInterval(setSelectedEffectPosition,750); // set html color items on/off var cslLabel = ''; var sep = ''; @@ -1700,7 +1702,7 @@ function toggleLiveview() let wsOn = ws && ws.readyState === WebSocket.OPEN; var lvID = "liveview"; - if (isM && wsOn) { + if (isM && wsOn) { lvID += "2D"; if (isLv) gId('klv2D').innerHTML = ``; gId('mlv2D').style.transform = (isLv) ? "translateY(0px)":"translateY(100%)"; @@ -1887,7 +1889,7 @@ function makeP(i,pl) end: 0 }; var rep = plJson[i].repeat ? plJson[i].repeat : 0; - content = + content = `