Remove conditional fade/blend

- transitions always enabled (use delay 0 to disable)
- optimisation in on/off fade
- fix for palette/color blend when blending style is not fade
- various tweaks and optimisations
This commit is contained in:
Blaz Kristan 2024-04-08 16:24:27 +02:00
parent c6805271e9
commit a3a8fa1cef
21 changed files with 135 additions and 286 deletions

View File

@ -1,5 +1,8 @@
## WLED changelog
#### Build 2404050
- Blending styles (with help from @tkadauke)
#### Build 2403280
- Individual color channel control for JSON API (fixes #3860)
- "col":[int|string|object|array, int|string|object|array, int|string|object|array]

View File

@ -96,7 +96,7 @@ void setup() {
jsonTransitionOnce = true;
strip.setTransition(0); //no transition
effectCurrent = FX_MODE_COLOR_WIPE;
resetTimebase(); //make sure wipe starts from beginning
strip.resetTimebase(); //make sure wipe starts from beginning
//set wipe direction
Segment& seg = strip.getSegment(0);

View File

@ -1,111 +0,0 @@
/*
* This file allows you to add own functionality to WLED more easily
* See: https://github.com/Aircoookie/WLED/wiki/Add-own-functionality
* EEPROM bytes 2750+ are reserved for your custom use case. (if you extend #define EEPSIZE in wled_eeprom.h)
* bytes 2400+ are currently ununsed, but might be used for future wled features
*/
//Use userVar0 and userVar1 (API calls &U0=,&U1=, uint16_t)
byte wipeState = 0; //0: inactive 1: wiping 2: solid
unsigned long timeStaticStart = 0;
uint16_t previousUserVar0 = 0;
//comment this out if you want the turn off effect to be just fading out instead of reverse wipe
#define STAIRCASE_WIPE_OFF
//gets called once at boot. Do all initialization that doesn't depend on network here
void userSetup()
{
//setup PIR sensor here, if needed
}
//gets called every time WiFi is (re-)connected. Initialize own network interfaces here
void userConnected()
{
}
//loop. You can use "if (WLED_CONNECTED)" to check for successful connection
void userLoop()
{
//userVar0 (U0 in HTTP API):
//has to be set to 1 if movement is detected on the PIR that is the same side of the staircase as the ESP8266
//has to be set to 2 if movement is detected on the PIR that is the opposite side
//can be set to 0 if no movement is detected. Otherwise LEDs will turn off after a configurable timeout (userVar1 seconds)
if (userVar0 > 0)
{
if ((previousUserVar0 == 1 && userVar0 == 2) || (previousUserVar0 == 2 && userVar0 == 1)) wipeState = 3; //turn off if other PIR triggered
previousUserVar0 = userVar0;
if (wipeState == 0) {
startWipe();
wipeState = 1;
} else if (wipeState == 1) { //wiping
uint32_t cycleTime = 360 + (255 - effectSpeed)*75; //this is how long one wipe takes (minus 25 ms to make sure we switch in time)
if (millis() + strip.timebase > (cycleTime - 25)) { //wipe complete
effectCurrent = FX_MODE_STATIC;
timeStaticStart = millis();
colorUpdated(CALL_MODE_NOTIFICATION);
wipeState = 2;
}
} else if (wipeState == 2) { //static
if (userVar1 > 0) //if U1 is not set, the light will stay on until second PIR or external command is triggered
{
if (millis() - timeStaticStart > userVar1*1000) wipeState = 3;
}
} else if (wipeState == 3) { //switch to wipe off
#ifdef STAIRCASE_WIPE_OFF
effectCurrent = FX_MODE_COLOR_WIPE;
strip.timebase = 360 + (255 - effectSpeed)*75 - millis(); //make sure wipe starts fully lit
colorUpdated(CALL_MODE_NOTIFICATION);
wipeState = 4;
#else
turnOff();
#endif
} else { //wiping off
if (millis() + strip.timebase > (725 + (255 - effectSpeed)*150)) turnOff(); //wipe complete
}
} else {
wipeState = 0; //reset for next time
if (previousUserVar0) {
#ifdef STAIRCASE_WIPE_OFF
userVar0 = previousUserVar0;
wipeState = 3;
#else
turnOff();
#endif
}
previousUserVar0 = 0;
}
}
void startWipe()
{
bri = briLast; //turn on
transitionDelayTemp = 0; //no transition
effectCurrent = FX_MODE_COLOR_WIPE;
resetTimebase(); //make sure wipe starts from beginning
//set wipe direction
Segment& seg = strip.getSegment(0);
bool doReverse = (userVar0 == 2);
seg.setOption(1, doReverse);
colorUpdated(CALL_MODE_NOTIFICATION);
}
void turnOff()
{
#ifdef STAIRCASE_WIPE_OFF
transitionDelayTemp = 0; //turn off immediately after wipe completed
#else
transitionDelayTemp = 4000; //fade out slowly
#endif
bri = 0;
stateUpdated(CALL_MODE_NOTIFICATION);
wipeState = 0;
userVar0 = 0;
previousUserVar0 = 0;
}

View File

@ -1211,8 +1211,9 @@ static const char _data_FX_MODE_COMET[] PROGMEM = "Lighthouse@!,Fade rate;!,!;!"
*/
uint16_t mode_fireworks() {
if (SEGLEN == 1) return mode_static();
const uint16_t width = SEGMENT.is2D() ? SEGMENT.virtualWidth() : SEGMENT.virtualLength();
const uint16_t height = SEGMENT.virtualHeight();
const unsigned width = SEGMENT.is2D() ? SEGMENT.virtualWidth() : SEGMENT.virtualLength();
const unsigned height = SEGMENT.virtualHeight();
const unsigned dimension = width * height;
if (SEGENV.call == 0) {
SEGENV.aux0 = UINT16_MAX;
@ -1220,19 +1221,19 @@ uint16_t mode_fireworks() {
}
SEGMENT.fade_out(128);
bool valid1 = (SEGENV.aux0 < width*height);
bool valid2 = (SEGENV.aux1 < width*height);
uint8_t x = SEGENV.aux0%width, y = SEGENV.aux0/width; // 2D coordinates stored in upper and lower byte
bool valid1 = (SEGENV.aux0 < dimension);
bool valid2 = (SEGENV.aux1 < dimension);
unsigned x = SEGENV.aux0%width, y = SEGENV.aux0/width; // 2D coordinates stored in upper and lower byte
uint32_t sv1 = 0, sv2 = 0;
if (valid1) sv1 = SEGMENT.is2D() ? SEGMENT.getPixelColorXY(x, y) : SEGMENT.getPixelColor(SEGENV.aux0); // get spark color
if (valid2) sv2 = SEGMENT.is2D() ? SEGMENT.getPixelColorXY(x, y) : SEGMENT.getPixelColor(SEGENV.aux1);
if (!SEGENV.step) SEGMENT.blur(16);
if (!SEGENV.step) SEGMENT.blur(dimension > 100 ? 16 : 8);
if (valid1) { if (SEGMENT.is2D()) SEGMENT.setPixelColorXY(x, y, sv1); else SEGMENT.setPixelColor(SEGENV.aux0, sv1); } // restore spark color after blur
if (valid2) { if (SEGMENT.is2D()) SEGMENT.setPixelColorXY(x, y, sv2); else SEGMENT.setPixelColor(SEGENV.aux1, sv2); } // restore old spark color after blur
for (int i=0; i<max(1, width/20); i++) {
for (unsigned i=0; i<max(1U, width/20); i++) {
if (random8(129 - (SEGMENT.intensity >> 1)) == 0) {
uint16_t index = random16(width*height);
unsigned index = random16(dimension);
x = index % width;
y = index / width;
uint32_t col = SEGMENT.color_from_palette(random8(), false, false, 0);
@ -2066,41 +2067,41 @@ uint16_t mode_fire_2012() {
struct virtualStrip {
static void runStrip(uint16_t stripNr, byte* heat, uint32_t it) {
const uint8_t ignition = max(3,SEGLEN/10); // ignition area: 10% of segment length or minimum 3 pixels
const unsigned ignition = max(3,SEGLEN/10); // ignition area: 10% of segment length or minimum 3 pixels
// Step 1. Cool down every cell a little
for (int i = 0; i < SEGLEN; i++) {
uint8_t cool = (it != SEGENV.step) ? random8((((20 + SEGMENT.speed/3) * 16) / SEGLEN)+2) : random8(4);
uint8_t minTemp = (i<ignition) ? (ignition-i)/4 + 16 : 0; // should not become black in ignition area
uint8_t temp = qsub8(heat[i], cool);
for (unsigned i = 0; i < SEGLEN; i++) {
unsigned cool = (it != SEGENV.step) ? random8((((20 + SEGMENT.speed/3) * 16) / SEGLEN)+2) : random8(4);
unsigned minTemp = (i<ignition) ? (ignition-i)/4 + 16 : 0; // should not become black in ignition area
unsigned temp = qsub8(heat[i], cool);
heat[i] = temp<minTemp ? minTemp : temp;
}
if (it != SEGENV.step) {
// Step 2. Heat from each cell drifts 'up' and diffuses a little
for (int k = SEGLEN -1; k > 1; k--) {
for (unsigned k = SEGLEN -1; k > 1; k--) {
heat[k] = (heat[k - 1] + (heat[k - 2]<<1) ) / 3; // heat[k-2] multiplied by 2
}
// Step 3. Randomly ignite new 'sparks' of heat near the bottom
if (random8() <= SEGMENT.intensity) {
uint8_t y = random8(ignition);
uint8_t boost = (17+SEGMENT.custom3) * (ignition - y/2) / ignition; // integer math!
unsigned y = random8(ignition);
unsigned boost = (17+SEGMENT.custom3) * (ignition - y/2) / ignition; // integer math!
heat[y] = qadd8(heat[y], random8(96+2*boost,207+boost));
}
}
// Step 4. Map from heat cells to LED colors
for (int j = 0; j < SEGLEN; j++) {
SEGMENT.setPixelColor(indexToVStrip(j, stripNr), ColorFromPalette(SEGPALETTE, MIN(heat[j],240), 255, NOBLEND));
SEGMENT.setPixelColor(indexToVStrip(j, stripNr), ColorFromPalette(SEGPALETTE, MIN(heat[j],240), 255, LINEARBLEND_NOWRAP));
}
}
};
for (int stripNr=0; stripNr<strips; stripNr++)
for (unsigned stripNr=0; stripNr<strips; stripNr++)
virtualStrip::runStrip(stripNr, &heat[stripNr * SEGLEN], it);
if (SEGMENT.is2D()) SEGMENT.blur(32);
if (SEGMENT.is2D()) SEGMENT.blur((SEGMENT.virtualWidth()*SEGMENT.virtualHeight()) > 100 ? 32 : 0);
if (it != SEGENV.step)
SEGENV.step = it;
@ -4856,9 +4857,9 @@ static const char _data_FX_MODE_FLOWSTRIPE[] PROGMEM = "Flow Stripe@Hue speed,Ef
uint16_t mode_2DBlackHole(void) { // By: Stepko https://editor.soulmatelights.com/gallery/1012 , Modified by: Andrew Tuline
if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up
const uint16_t cols = SEGMENT.virtualWidth();
const uint16_t rows = SEGMENT.virtualHeight();
uint16_t x, y;
const unsigned cols = SEGMENT.virtualWidth();
const unsigned rows = SEGMENT.virtualHeight();
unsigned x, y;
SEGMENT.fadeToBlackBy(16 + (SEGMENT.speed>>3)); // create fading trails
unsigned long t = strip.now/128; // timebase
@ -4877,7 +4878,7 @@ uint16_t mode_2DBlackHole(void) { // By: Stepko https://editor.soulma
// central white dot
SEGMENT.setPixelColorXY(cols/2, rows/2, WHITE);
// blur everything a bit
SEGMENT.blur(16);
SEGMENT.blur(sqrt16(cols*rows) > 100 ? 16 : 0);
return FRAMETIME;
} // mode_2DBlackHole()
@ -6436,8 +6437,8 @@ static const char _data_FX_MODE_2DSWIRL[] PROGMEM = "Swirl@!,Sensitivity,Blur;,B
uint16_t mode_2DWaverly(void) {
if (!strip.isMatrix || !SEGMENT.is2D()) return mode_static(); // not a 2D set-up
const uint16_t cols = SEGMENT.virtualWidth();
const uint16_t rows = SEGMENT.virtualHeight();
const unsigned cols = SEGMENT.virtualWidth();
const unsigned rows = SEGMENT.virtualHeight();
um_data_t *um_data;
if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) {
@ -6449,21 +6450,21 @@ uint16_t mode_2DWaverly(void) {
SEGMENT.fadeToBlackBy(SEGMENT.speed);
long t = strip.now / 2;
for (int i = 0; i < cols; i++) {
uint16_t thisVal = (1 + SEGMENT.intensity/64) * inoise8(i * 45 , t , t)/2;
for (unsigned i = 0; i < cols; i++) {
unsigned thisVal = (1 + SEGMENT.intensity/64) * inoise8(i * 45 , t , t)/2;
// use audio if available
if (um_data) {
thisVal /= 32; // reduce intensity of inoise8()
thisVal *= volumeSmth;
}
uint16_t thisMax = map(thisVal, 0, 512, 0, rows);
unsigned thisMax = map(thisVal, 0, 512, 0, rows);
for (int j = 0; j < thisMax; j++) {
for (unsigned j = 0; j < thisMax; j++) {
SEGMENT.addPixelColorXY(i, j, ColorFromPalette(SEGPALETTE, map(j, 0, thisMax, 250, 0), 255, LINEARBLEND));
SEGMENT.addPixelColorXY((cols - 1) - i, (rows - 1) - j, ColorFromPalette(SEGPALETTE, map(j, 0, thisMax, 250, 0), 255, LINEARBLEND));
}
}
SEGMENT.blur(16);
SEGMENT.blur(sqrt16(cols*rows) > 100 ? 16 : 0);
return FRAMETIME;
} // mode_2DWaverly()

View File

@ -601,7 +601,7 @@ typedef struct Segment {
inline void setPixelColor(float i, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0, bool aa = true) { setPixelColor(i, RGBW32(r,g,b,w), aa); }
inline void setPixelColor(float i, CRGB c, bool aa = true) { setPixelColor(i, RGBW32(c.r,c.g,c.b,0), aa); }
#ifndef WLED_DISABLE_MODE_BLEND
inline void setClippingRect(int startX, int stopX, int startY = 0, int stopY = 1) { _clipStart = startX; _clipStop = stopX; _clipStartY = startY; _clipStopY = stopY; };
static inline void setClippingRect(int startX, int stopX, int startY = 0, int stopY = 1) { _clipStart = startX; _clipStop = stopX; _clipStartY = startY; _clipStopY = stopY; };
#endif
bool isPixelClipped(int i);
uint32_t getPixelColor(int i);
@ -708,9 +708,7 @@ class WS2812FX { // 96 bytes
public:
WS2812FX() :
paletteFade(0),
paletteBlend(0),
cctBlending(0),
now(millis()),
timebase(0),
isMatrix(false),
@ -792,6 +790,7 @@ class WS2812FX { // 96 bytes
addEffect(uint8_t id, mode_ptr mode_fn, const char *mode_name), // add effect to the list; defined in FX.cpp
setupEffectData(void); // add default effects to the list; defined in FX.cpp
inline void resetTimebase() { timebase = 0U - millis(); }
inline void restartRuntime() { for (Segment &seg : _segments) seg.markForReset(); }
inline void setTransitionMode(bool t) { for (Segment &seg : _segments) seg.startTransition(t ? _transitionDur : 0); }
inline void setColor(uint8_t slot, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0) { setColor(slot, RGBW32(r,g,b,w)); }
@ -806,7 +805,6 @@ class WS2812FX { // 96 bytes
inline void resume(void) { _suspend = false; } // will resume strip.service() execution
bool
paletteFade,
checkSegmentAlignment(void),
hasRGBWBus(void),
hasCCTBus(void),
@ -822,7 +820,6 @@ class WS2812FX { // 96 bytes
uint8_t
paletteBlend,
cctBlending,
getActiveSegmentsNum(void),
getFirstSelectedSegId(void),
getLastActiveSegmentId(void),

View File

@ -176,10 +176,10 @@ bool IRAM_ATTR Segment::isPixelXYClipped(int x, int y) {
if (_clipStart != _clipStop && blendingStyle > BLEND_STYLE_FADE) {
const bool invertX = _clipStart > _clipStop;
const bool invertY = _clipStartY > _clipStopY;
const unsigned startX = invertX ? _clipStop : _clipStart;
const unsigned stopX = invertX ? _clipStart : _clipStop;
const unsigned startY = invertY ? _clipStopY : _clipStartY;
const unsigned stopY = invertY ? _clipStartY : _clipStopY;
const int startX = invertX ? _clipStop : _clipStart;
const int stopX = invertX ? _clipStart : _clipStop;
const int startY = invertY ? _clipStopY : _clipStartY;
const int stopY = invertY ? _clipStartY : _clipStopY;
if (blendingStyle == BLEND_STYLE_FAIRY_DUST) {
const unsigned width = stopX - startX; // assumes full segment width (faster than virtualWidth())
const unsigned len = width * (stopY - startY); // assumes full segment height (faster than virtualHeight())
@ -295,7 +295,7 @@ void Segment::setPixelColorXY(float x, float y, uint32_t col, bool aa)
// returns RGBW values of pixel
uint32_t IRAM_ATTR Segment::getPixelColorXY(uint16_t x, uint16_t y) {
if (!isActive()) return 0; // not active
if (x >= virtualWidth() || y >= virtualHeight() || x<0 || y<0) return 0; // if pixel would fall out of virtual segment just exit
if (x >= virtualWidth() || y >= virtualHeight() || x<0 || y<0 || isPixelXYClipped(x,y)) return 0; // if pixel would fall out of virtual segment just exit
if (reverse ) x = virtualWidth() - x - 1;
if (reverse_y) y = virtualHeight() - y - 1;
if (transpose) { uint16_t t = x; x = y; y = t; } // swap X & Y if segment transposed

View File

@ -293,21 +293,17 @@ void Segment::startTransition(uint16_t dur) {
_t->_briT = on ? opacity : 0;
_t->_cctT = cct;
#ifndef WLED_DISABLE_MODE_BLEND
if (modeBlending) {
swapSegenv(_t->_segT);
_t->_modeT = mode;
_t->_segT._dataLenT = 0;
_t->_segT._dataT = nullptr;
if (_dataLen > 0 && data) {
_t->_segT._dataT = (byte *)malloc(_dataLen);
if (_t->_segT._dataT) {
//DEBUG_PRINTF_P(PSTR("-- Allocated duplicate data (%d) for %p: %p\n"), _dataLen, this, _t->_segT._dataT);
memcpy(_t->_segT._dataT, data, _dataLen);
_t->_segT._dataLenT = _dataLen;
}
swapSegenv(_t->_segT);
_t->_modeT = mode;
_t->_segT._dataLenT = 0;
_t->_segT._dataT = nullptr;
if (_dataLen > 0 && data) {
_t->_segT._dataT = (byte *)malloc(_dataLen);
if (_t->_segT._dataT) {
//DEBUG_PRINTF_P(PSTR("-- Allocated duplicate data (%d) for %p: %p\n"), _dataLen, this, _t->_segT._dataT);
memcpy(_t->_segT._dataT, data, _dataLen);
_t->_segT._dataLenT = _dataLen;
}
} else {
for (size_t i=0; i<NUM_COLORS; i++) _t->_segT._colorT[i] = colors[i];
}
#else
for (size_t i=0; i<NUM_COLORS; i++) _t->_colorT[i] = colors[i];
@ -435,7 +431,7 @@ uint8_t IRAM_ATTR Segment::currentBri(bool useCct) {
uint8_t IRAM_ATTR Segment::currentMode() {
#ifndef WLED_DISABLE_MODE_BLEND
uint16_t prog = progress();
if (modeBlending && prog < 0xFFFFU) return _t->_modeT;
if (prog < 0xFFFFU) return _t->_modeT;
#endif
return mode;
}
@ -445,7 +441,7 @@ uint32_t IRAM_ATTR Segment::currentColor(uint8_t slot) {
uint32_t prog = progress();
if (prog == 0xFFFFU) return colors[slot];
#ifndef WLED_DISABLE_MODE_BLEND
if (blendingStyle > BLEND_STYLE_FADE) return _modeBlend ? _t->_segT._colorT[slot] : colors[slot]; // not fade/blend transition, each effect uses its color
if (blendingStyle > BLEND_STYLE_FADE && mode != _t->_modeT) return _modeBlend ? _t->_segT._colorT[slot] : colors[slot]; // not fade/blend transition, each effect uses its color
return color_blend(_t->_segT._colorT[slot], colors[slot], prog, true);
#else
return color_blend(_t->_colorT[slot], colors[slot], prog, true);
@ -456,10 +452,10 @@ CRGBPalette16 IRAM_ATTR &Segment::currentPalette(CRGBPalette16 &targetPalette, u
loadPalette(targetPalette, pal);
uint16_t prog = progress();
#ifndef WLED_DISABLE_MODE_BLEND
if (prog < 0xFFFFU && blendingStyle > BLEND_STYLE_FADE && _modeBlend) targetPalette = _t->_palT; // not fade/blend transition, each effect uses its palette
if (prog < 0xFFFFU && blendingStyle > BLEND_STYLE_FADE && _modeBlend && mode != _t->_modeT) targetPalette = _t->_palT; // not fade/blend transition, each effect uses its palette
else
#endif
if (strip.paletteFade && prog < 0xFFFFU) {
if (prog < 0xFFFFU) {
// blend palettes
// there are about 255 blend passes of 48 "blends" to completely blend two palettes (in _dur time)
// minimum blend time is 100ms maximum is 65535ms
@ -473,19 +469,16 @@ CRGBPalette16 IRAM_ATTR &Segment::currentPalette(CRGBPalette16 &targetPalette, u
// relies on WS2812FX::service() to call it for each frame
void Segment::handleRandomPalette() {
// is it time to generate a new palette?
if ((uint16_t)(millis() / 1000U) - _lastPaletteChange > randomPaletteChangeTime){
_newRandomPalette = useHarmonicRandomPalette ? generateHarmonicRandomPalette(_randomPalette) : generateRandomPalette();
_lastPaletteChange = (uint16_t)(millis() / 1000U);
_lastPaletteBlend = (uint16_t)millis() - 512; // starts blending immediately
if ((uint16_t)(millis()/1000U) - _lastPaletteChange > randomPaletteChangeTime) {
_newRandomPalette = useHarmonicRandomPalette ? generateHarmonicRandomPalette(_randomPalette) : generateRandomPalette();
_lastPaletteChange = (uint16_t)(millis()/1000U);
_lastPaletteBlend = (uint16_t)(millis())-512; // starts blending immediately
}
// if palette transitions is enabled, blend it according to Transition Time (if longer than minimum given by service calls)
if (strip.paletteFade) {
// assumes that 128 updates are sufficient to blend a palette, so shift by 7 (can be more, can be less)
// in reality there need to be 255 blends to fully blend two entirely different palettes
if ((uint16_t)((uint16_t)millis() - _lastPaletteBlend) < strip.getTransition() >> 7) return; // not yet time to fade, delay the update
_lastPaletteBlend = (uint16_t)millis();
}
// assumes that 128 updates are sufficient to blend a palette, so shift by 7 (can be more, can be less)
// in reality there need to be 255 blends to fully blend two entirely different palettes
if ((uint16_t)millis() - _lastPaletteBlend < strip.getTransition() >> 7) return; // not yet time to fade, delay the update
_lastPaletteBlend = (uint16_t)millis();
nblendPaletteTowardPalette(_randomPalette, _newRandomPalette, 48);
}
@ -549,7 +542,7 @@ bool Segment::setColor(uint8_t slot, uint32_t c) { //returns true if changed
if (slot == 0 && c == BLACK) return false; // on/off segment cannot have primary color black
if (slot == 1 && c != BLACK) return false; // on/off segment cannot have secondary color non black
}
if (fadeTransition) startTransition(strip.getTransition()); // start transition prior to change
startTransition(strip.getTransition()); // start transition prior to change
colors[slot] = c;
stateChanged = true; // send UDP/WS broadcast
return true;
@ -562,21 +555,21 @@ void Segment::setCCT(uint16_t k) {
k = (k - 1900) >> 5;
}
if (cct == k) return;
if (fadeTransition) startTransition(strip.getTransition()); // start transition prior to change
startTransition(strip.getTransition()); // start transition prior to change
cct = k;
stateChanged = true; // send UDP/WS broadcast
}
void Segment::setOpacity(uint8_t o) {
if (opacity == o) return;
if (fadeTransition) startTransition(strip.getTransition()); // start transition prior to change
startTransition(strip.getTransition()); // start transition prior to change
opacity = o;
stateChanged = true; // send UDP/WS broadcast
}
void Segment::setOption(uint8_t n, bool val) {
bool prevOn = on;
if (fadeTransition && n == SEG_OPTION_ON && val != prevOn) startTransition(strip.getTransition()); // start transition prior to change
if (n == SEG_OPTION_ON && val != prevOn) startTransition(strip.getTransition()); // start transition prior to change
if (val) options |= 0x01 << n;
else options &= ~(0x01 << n);
if (!(n == SEG_OPTION_SELECTED || n == SEG_OPTION_RESET)) stateChanged = true; // send UDP/WS broadcast
@ -589,7 +582,7 @@ void Segment::setMode(uint8_t fx, bool loadDefaults) {
// if we have a valid mode & is not reserved
if (fx != mode) {
#ifndef WLED_DISABLE_MODE_BLEND
if (modeBlending) startTransition(strip.getTransition()); // set effect transitions
startTransition(strip.getTransition()); // set effect transitions
#endif
mode = fx;
// load default values from effect string
@ -620,7 +613,7 @@ void Segment::setPalette(uint8_t pal) {
if (pal < 245 && pal > GRADIENT_PALETTE_COUNT+13) pal = 0; // built in palettes
if (pal > 245 && (strip.customPalettes.size() == 0 || 255U-pal > strip.customPalettes.size()-1)) pal = 0; // custom palettes
if (pal != palette) {
if (strip.paletteFade) startTransition(strip.getTransition());
startTransition(strip.getTransition());
palette = pal;
stateChanged = true; // send UDP/WS broadcast
}
@ -688,8 +681,8 @@ bool IRAM_ATTR Segment::isPixelClipped(int i) {
#ifndef WLED_DISABLE_MODE_BLEND
if (_clipStart != _clipStop && blendingStyle > BLEND_STYLE_FADE) {
bool invert = _clipStart > _clipStop;
unsigned start = invert ? _clipStop : _clipStart;
unsigned stop = invert ? _clipStart : _clipStop;
int start = invert ? _clipStop : _clipStart;
int stop = invert ? _clipStart : _clipStop;
if (blendingStyle == BLEND_STYLE_FAIRY_DUST) {
unsigned len = stop - start;
if (len < 2) return false;
@ -888,6 +881,8 @@ uint32_t IRAM_ATTR Segment::getPixelColor(int i)
}
#endif
if (isPixelClipped(i)) return 0; // handle clipping on 1D
if (reverse) i = virtualLength() - i - 1;
i *= groupLength();
i += start;
@ -1234,8 +1229,8 @@ void WS2812FX::service() {
// would need to be allocated for each effect and then blended together for each pixel.
[[maybe_unused]] uint8_t tmpMode = seg.currentMode(); // this will return old mode while in transition
#ifndef WLED_DISABLE_MODE_BLEND
seg.setClippingRect(0, 0); // disable clipping
if (modeBlending && seg.mode != tmpMode) {
Segment::setClippingRect(0, 0); // disable clipping (just in case)
if (seg.mode != tmpMode) { // could try seg.isInTransition() to allow color and palette to follow blending styles
// set clipping rectangle
// new mode is run inside clipping area and old mode outside clipping area
unsigned p = seg.progress();
@ -1245,48 +1240,48 @@ void WS2812FX::service() {
unsigned dh = p * h / 0xFFFFU + 1;
switch (blendingStyle) {
case BLEND_STYLE_FAIRY_DUST: // fairy dust (must set entire segment, see isPixelXYClipped())
seg.setClippingRect(0, w, 0, h);
Segment::setClippingRect(0, w, 0, h);
break;
case BLEND_STYLE_SWIPE_RIGHT: // left-to-right
seg.setClippingRect(0, dw, 0, h);
Segment::setClippingRect(0, dw, 0, h);
break;
case BLEND_STYLE_SWIPE_LEFT: // right-to-left
seg.setClippingRect(w - dw, w, 0, h);
Segment::setClippingRect(w - dw, w, 0, h);
break;
case BLEND_STYLE_PINCH_OUT: // corners
seg.setClippingRect((w + dw)/2, (w - dw)/2, (h + dh)/2, (h - dh)/2); // inverted!!
Segment::setClippingRect((w + dw)/2, (w - dw)/2, (h + dh)/2, (h - dh)/2); // inverted!!
break;
case BLEND_STYLE_INSIDE_OUT: // outward
seg.setClippingRect((w - dw)/2, (w + dw)/2, (h - dh)/2, (h + dh)/2);
Segment::setClippingRect((w - dw)/2, (w + dw)/2, (h - dh)/2, (h + dh)/2);
break;
case BLEND_STYLE_SWIPE_DOWN: // top-to-bottom (2D)
seg.setClippingRect(0, w, 0, dh);
Segment::setClippingRect(0, w, 0, dh);
break;
case BLEND_STYLE_SWIPE_UP: // bottom-to-top (2D)
seg.setClippingRect(0, w, h - dh, h);
Segment::setClippingRect(0, w, h - dh, h);
break;
case BLEND_STYLE_OPEN_H: // horizontal-outward (2D) same look as INSIDE_OUT on 1D
seg.setClippingRect((w - dw)/2, (w + dw)/2, 0, h);
Segment::setClippingRect((w - dw)/2, (w + dw)/2, 0, h);
break;
case BLEND_STYLE_OPEN_V: // vertical-outward (2D)
seg.setClippingRect(0, w, (h - dh)/2, (h + dh)/2);
Segment::setClippingRect(0, w, (h - dh)/2, (h + dh)/2);
break;
case BLEND_STYLE_PUSH_TL: // TL-to-BR (2D)
seg.setClippingRect(0, dw, 0, dh);
Segment::setClippingRect(0, dw, 0, dh);
break;
case BLEND_STYLE_PUSH_TR: // TR-to-BL (2D)
seg.setClippingRect(w - dw, w, 0, dh);
Segment::setClippingRect(w - dw, w, 0, dh);
break;
case BLEND_STYLE_PUSH_BR: // BR-to-TL (2D)
seg.setClippingRect(w - dw, w, h - dh, h);
Segment::setClippingRect(w - dw, w, h - dh, h);
break;
case BLEND_STYLE_PUSH_BL: // BL-to-TR (2D)
seg.setClippingRect(0, dw, h - dh, h);
Segment::setClippingRect(0, dw, h - dh, h);
break;
}
}
delay = (*_mode[seg.mode])(); // run new/current mode
if (modeBlending && seg.mode != tmpMode) {
if (seg.mode != tmpMode) { // could try seg.isInTransition() to allow color and palette to follow blending styles
Segment::tmpsegd_t _tmpSegData;
Segment::modeBlend(true); // set semaphore
seg.swapSegenv(_tmpSegData); // temporarily store new mode state (and swap it with transitional state)
@ -1301,13 +1296,14 @@ void WS2812FX::service() {
#endif
seg.call++;
if (seg.isInTransition() && delay > FRAMETIME) delay = FRAMETIME; // force faster updates during transition
BusManager::setSegmentCCT(oldCCT); // restore old CCT for ABL adjustments
BusManager::setSegmentCCT(oldCCT); // restore old CCT for ABL adjustments
}
seg.next_time = nowUp + delay;
}
_segment_index++;
}
Segment::setClippingRect(0, 0); // disable clipping for overlays
_virtualSegmentLength = 0;
_isServicing = false;
_triggered = false;

View File

@ -391,14 +391,14 @@ BusPwm::BusPwm(BusConfig &bc)
uint8_t numPins = NUM_PWM_PINS(bc.type);
_frequency = bc.frequency ? bc.frequency : WLED_PWM_FREQ;
#ifdef ESP8266
#ifdef ESP8266
// duty cycle resolution (_depth) can be extracted from this formula: 1MHz > _frequency * 2^_depth
if (_frequency > 1760) _depth = 8;
else if (_frequency > 880) _depth = 9;
else _depth = 10; // WLED_PWM_FREQ <= 880Hz
analogWriteRange((1<<_depth)-1);
analogWriteFreq(_frequency);
#else
#else
_ledcStart = pinManager.allocateLedc(numPins);
if (_ledcStart == 255) { //no more free LEDC channels
deallocatePins(); return;
@ -408,7 +408,7 @@ BusPwm::BusPwm(BusConfig &bc)
else if (_frequency > 39062) _depth = 10;
else if (_frequency > 19531) _depth = 11;
else _depth = 12; // WLED_PWM_FREQ <= 19531Hz
#endif
#endif
for (unsigned i = 0; i < numPins; i++) {
uint8_t currentPin = bc.pins[i];

View File

@ -173,10 +173,11 @@ class Bus {
type == TYPE_FW1906 || type == TYPE_WS2805 ) return true;
return false;
}
static int16_t getCCT() { return _cct; }
static inline int16_t getCCT() { return _cct; }
static void setCCT(int16_t cct) {
_cct = cct;
}
static inline uint8_t getCCTBlend() { return _cctBlend; }
static void setCCTBlend(uint8_t b) {
if (b > 100) b = 100;
_cctBlend = (b * 127) / 100;

View File

@ -111,8 +111,8 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
CJSON(correctWB, hw_led["cct"]);
CJSON(cctFromRgb, hw_led[F("cr")]);
CJSON(cctICused, hw_led[F("ic")]);
CJSON(strip.cctBlending, hw_led[F("cb")]);
Bus::setCCTBlend(strip.cctBlending);
uint8_t cctBlending = hw_led[F("cb")] | Bus::getCCTBlend();
Bus::setCCTBlend(cctBlending);
strip.setTargetFps(hw_led["fps"]); //NOP if 0, default 42 FPS
CJSON(useGlobalLedBuffer, hw_led[F("ld")]);
@ -408,12 +408,9 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
}
JsonObject light_tr = light["tr"];
CJSON(fadeTransition, light_tr["mode"]);
CJSON(modeBlending, light_tr["fx"]);
int tdd = light_tr["dur"] | -1;
if (tdd >= 0) transitionDelay = transitionDelayDefault = tdd * 100;
strip.setTransition(fadeTransition ? transitionDelayDefault : 0);
CJSON(strip.paletteFade, light_tr["pal"]);
strip.setTransition(transitionDelayDefault);
CJSON(randomPaletteChangeTime, light_tr[F("rpc")]);
CJSON(useHarmonicRandomPalette, light_tr[F("hrp")]);
@ -777,7 +774,7 @@ void serializeConfig() {
hw_led["cct"] = correctWB;
hw_led[F("cr")] = cctFromRgb;
hw_led[F("ic")] = cctICused;
hw_led[F("cb")] = strip.cctBlending;
hw_led[F("cb")] = Bus::getCCTBlend();
hw_led["fps"] = strip.getTargetFps();
hw_led[F("rgbwm")] = Bus::getGlobalAWMode(); // global auto white mode override
hw_led[F("ld")] = useGlobalLedBuffer;
@ -894,10 +891,7 @@ void serializeConfig() {
light_gc["val"] = gammaCorrectVal;
JsonObject light_tr = light.createNestedObject("tr");
light_tr["mode"] = fadeTransition;
light_tr["fx"] = modeBlending;
light_tr["dur"] = transitionDelayDefault / 100;
light_tr["pal"] = strip.paletteFade;
light_tr[F("rpc")] = randomPaletteChangeTime;
light_tr[F("hrp")] = useHarmonicRandomPalette;

View File

@ -834,12 +834,7 @@ Swap: <select id="xw${i}" name="XW${i}">
Use Gamma value: <input name="GV" type="number" class="m" placeholder="2.8" min="1" max="3" step="0.1" required><br><br>
Brightness factor: <input name="BF" type="number" class="m" min="1" max="255" required> %
<h3>Transitions</h3>
Enable transitions: <input type="checkbox" name="TF" onchange="gId('tran').style.display=this.checked?'inline':'none';"><br>
<span id="tran">
Effect blending: <input type="checkbox" name="EB"><br>
Transition Time: <input name="TD" type="number" class="xl" min="0" max="65500"> ms<br>
Palette transitions: <input type="checkbox" name="PF"><br>
</span>
Transition Time: <input name="TD" type="number" class="xl" min="0" max="65500"> ms<br>
<i>Random Cycle</i> Palette Time: <input name="TP" type="number" class="m" min="1" max="255"> s<br>
Use harmonic <i>Random Cycle</i> Palette: <input type="checkbox" name="TH"><br>
<h3>Timed light</h3>

View File

@ -181,7 +181,6 @@ bool serveLiveLeds(AsyncWebServerRequest* request, uint32_t wsClient = 0);
void setValuesFromSegment(uint8_t s);
void setValuesFromMainSeg();
void setValuesFromFirstSelectedSeg();
void resetTimebase();
void toggleOnOff();
void applyBri();
void applyFinalBri();

View File

@ -351,7 +351,7 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
tr = root[F("transition")] | -1;
if (tr >= 0) {
transitionDelay = tr * 100;
if (fadeTransition) strip.setTransition(transitionDelay);
strip.setTransition(transitionDelay);
}
}
@ -364,7 +364,7 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
tr = root[F("tt")] | -1;
if (tr >= 0) {
jsonTransitionOnce = true;
if (fadeTransition) strip.setTransition(tr * 100);
strip.setTransition(tr * 100);
}
tr = root[F("tb")] | -1;
@ -779,7 +779,7 @@ void serializeInfo(JsonObject root)
root[F("freeheap")] = ESP.getFreeHeap();
#if defined(ARDUINO_ARCH_ESP32)
if (psramSafe && psramFound()) root[F("psram")] = ESP.getFreePsram();
if (psramFound()) root[F("psram")] = ESP.getFreePsram();
#endif
root[F("uptime")] = millis()/1000 + rolloverMillis*4294967;

View File

@ -47,12 +47,6 @@ void applyValuesToSelectedSegs()
}
void resetTimebase()
{
strip.timebase = 0 - millis();
}
void toggleOnOff()
{
if (bri == 0)
@ -76,7 +70,7 @@ byte scaledBri(byte in)
}
//applies global brightness
//applies global temporary brightness (briT) to strip
void applyBri() {
if (!realtimeMode || !arlsForceMaxBri)
{
@ -90,6 +84,7 @@ void applyFinalBri() {
briOld = bri;
briT = bri;
applyBri();
strip.trigger(); // force one last update
}
@ -122,7 +117,7 @@ void stateUpdated(byte callMode) {
nightlightStartTime = millis();
}
if (briT == 0) {
if (callMode != CALL_MODE_NOTIFICATION) resetTimebase(); //effect start from beginning
if (callMode != CALL_MODE_NOTIFICATION) strip.resetTimebase(); //effect start from beginning
}
if (bri > 0) briLast = bri;
@ -133,31 +128,24 @@ void stateUpdated(byte callMode) {
// notify usermods of state change
usermods.onStateChange(callMode);
if (fadeTransition) {
if (strip.getTransition() == 0) {
jsonTransitionOnce = false;
transitionActive = false;
applyFinalBri();
strip.trigger();
return;
}
if (transitionActive) {
briOld = briT;
tperLast = 0;
} else
strip.setTransitionMode(true); // force all segments to transition mode
transitionActive = true;
transitionStartTime = millis();
} else {
if (strip.getTransition() == 0) {
jsonTransitionOnce = false;
transitionActive = false;
applyFinalBri();
strip.trigger();
return;
}
if (transitionActive) {
briOld = briT;
tperLast = 0;
} else
strip.setTransitionMode(true); // force all segments to transition mode
transitionActive = true;
transitionStartTime = millis();
}
void updateInterfaces(uint8_t callMode)
{
void updateInterfaces(uint8_t callMode) {
if (!interfaceUpdateCallMode || millis() - lastInterfaceUpdate < INTERFACE_UPDATE_COOLDOWN) return;
sendDataWs();
@ -178,8 +166,7 @@ void updateInterfaces(uint8_t callMode)
}
void handleTransitions()
{
void handleTransitions() {
//handle still pending interface update
updateInterfaces(interfaceUpdateCallMode);
@ -198,7 +185,6 @@ void handleTransitions()
if (tper - tperLast < 0.004f) return;
tperLast = tper;
briT = briOld + ((bri - briOld) * tper);
applyBri();
}
}
@ -211,8 +197,7 @@ void colorUpdated(byte callMode) {
}
void handleNightlight()
{
void handleNightlight() {
unsigned long now = millis();
if (now < 100 && lastNlUpdate > 0) lastNlUpdate = 0; // take care of millis() rollover
if (now - lastNlUpdate < 100) return; // allow only 10 NL updates per second
@ -292,7 +277,6 @@ void handleNightlight()
}
//utility for FastLED to use our custom timer
uint32_t get_millisecond_timer()
{
uint32_t get_millisecond_timer() {
return strip.now;
}

View File

@ -146,7 +146,7 @@ void handlePlaylist() {
}
jsonTransitionOnce = true;
strip.setTransition(fadeTransition ? playlistEntries[playlistIndex].tr * 100 : 0);
strip.setTransition(playlistEntries[playlistIndex].tr * 100);
playlistEntryDur = playlistEntries[playlistIndex].dur;
applyPresetFromPlaylist(playlistEntries[playlistIndex].preset);
}

View File

@ -128,8 +128,8 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
correctWB = request->hasArg(F("CCT"));
cctFromRgb = request->hasArg(F("CR"));
cctICused = request->hasArg(F("IC"));
strip.cctBlending = request->arg(F("CB")).toInt();
Bus::setCCTBlend(strip.cctBlending);
uint8_t cctBlending = request->arg(F("CB")).toInt();
Bus::setCCTBlend(cctBlending);
Bus::setGlobalAWMode(request->arg(F("AW")).toInt());
strip.setTargetFps(request->arg(F("FR")).toInt());
useGlobalLedBuffer = request->hasArg(F("LD"));
@ -313,11 +313,8 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
gammaCorrectCol = false;
}
fadeTransition = request->hasArg(F("TF"));
modeBlending = request->hasArg(F("EB"));
t = request->arg(F("TD")).toInt();
if (t >= 0) transitionDelayDefault = t;
strip.paletteFade = request->hasArg(F("PF"));
t = request->arg(F("TP")).toInt();
randomPaletteChangeTime = MIN(255,MAX(1,t));
useHarmonicRandomPalette = request->hasArg(F("TH"));
@ -1124,7 +1121,7 @@ bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply)
pos = req.indexOf(F("TT="));
if (pos > 0) transitionDelay = getNumVal(&req, pos);
if (fadeTransition) strip.setTransition(transitionDelay);
strip.setTransition(transitionDelay);
//set time (unix timestamp)
pos = req.indexOf(F("ST="));

View File

@ -225,10 +225,8 @@ void parseNotifyPacket(uint8_t *udpIn) {
// set transition time before making any segment changes
if (version > 3) {
if (fadeTransition) {
jsonTransitionOnce = true;
strip.setTransition(((udpIn[17] << 0) & 0xFF) + ((udpIn[18] << 8) & 0xFF00));
}
jsonTransitionOnce = true;
strip.setTransition(((udpIn[17] << 0) & 0xFF) + ((udpIn[18] << 8) & 0xFF00));
}
//apply colors from notification to main segment, only if not syncing full segments

View File

@ -538,10 +538,10 @@ void WLED::beginStrip()
} else {
// fix for #3196
if (bootPreset > 0) {
bool oldTransition = fadeTransition; // workaround if transitions are enabled
fadeTransition = false; // ignore transitions temporarily
uint16_t oldTransition = strip.getTransition(); // workaround if transitions are enabled
strip.setTransition(0); // ignore transitions temporarily
strip.setColor(0, BLACK); // set all segments black
fadeTransition = oldTransition; // restore transitions
strip.setTransition(oldTransition); // restore transitions
col[0] = col[1] = col[2] = col[3] = 0; // needed for colorUpdated()
}
briLast = briS; bri = 0;

View File

@ -545,8 +545,6 @@ WLED_GLOBAL bool wasConnected _INIT(false);
WLED_GLOBAL byte lastRandomIndex _INIT(0); // used to save last random color so the new one is not the same
// transitions
WLED_GLOBAL bool fadeTransition _INIT(true); // enable crossfading brightness/color
WLED_GLOBAL bool modeBlending _INIT(true); // enable effect blending
WLED_GLOBAL uint8_t blendingStyle _INIT(0); // effect blending/transitionig style
WLED_GLOBAL bool transitionActive _INIT(false);
WLED_GLOBAL uint16_t transitionDelay _INIT(750); // global transition duration

View File

@ -220,7 +220,7 @@ void loadSettingsFromEEPROM()
if (lastEEPROMversion > 7)
{
strip.paletteFade = EEPROM.read(374);
//strip.paletteFade = EEPROM.read(374);
strip.paletteBlend = EEPROM.read(382);
for (int i = 0; i < 8; ++i)

View File

@ -357,7 +357,7 @@ void getSettingsJS(byte subPage, char* dest)
sappend('c',SET_F("CCT"),correctWB);
sappend('c',SET_F("IC"),cctICused);
sappend('c',SET_F("CR"),cctFromRgb);
sappend('v',SET_F("CB"),strip.cctBlending);
sappend('v',SET_F("CB"),Bus::getCCTBlend());
sappend('v',SET_F("FR"),strip.getTargetFps());
sappend('v',SET_F("AW"),Bus::getGlobalAWMode());
sappend('c',SET_F("LD"),useGlobalLedBuffer);
@ -445,10 +445,7 @@ void getSettingsJS(byte subPage, char* dest)
sappend('c',SET_F("GB"),gammaCorrectBri);
sappend('c',SET_F("GC"),gammaCorrectCol);
dtostrf(gammaCorrectVal,3,1,nS); sappends('s',SET_F("GV"),nS);
sappend('c',SET_F("TF"),fadeTransition);
sappend('c',SET_F("EB"),modeBlending);
sappend('v',SET_F("TD"),transitionDelayDefault);
sappend('c',SET_F("PF"),strip.paletteFade);
sappend('v',SET_F("TP"),randomPaletteChangeTime);
sappend('c',SET_F("TH"),useHarmonicRandomPalette);
sappend('v',SET_F("BF"),briMultiplier);