Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions _codeql_detected_source_root
138 changes: 138 additions & 0 deletions wled00/FX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10745,6 +10745,141 @@ uint8_t WS2812FX::addEffect(uint8_t id, mode_ptr mode_fn, const char *mode_name)
}
}

/*
* Long Transition Effect
* Allows for very slow color/palette transitions over minutes
* Speed slider controls transition duration in minutes (0 = instant for testing)
* Intensity slider (optional): target preset ID to trigger when transition completes
* Stores initial, current, and target states for smooth blending
* Updates at 10Hz for smooth transitions while rendering each frame
*
* PRESET TRIGGERING:
* This effect can optionally trigger a preset when the transition completes.
* This is a non-standard pattern (effects typically don't trigger presets), but it
* enables creating "very slow playlists" by chaining transitions without using the
* scheduler. The preset system is designed to handle calls from various sources,
* so this should work safely, though it's somewhat of a hack as noted in the issue.
*/
void mode_long_transition(void) {
// Structure to store transition state
struct TransitionState {
uint32_t initialColors[NUM_COLORS]; // Colors at start of transition
uint32_t targetColors[NUM_COLORS]; // Target colors for transition
uint32_t currentColors[NUM_COLORS]; // Current blended colors (updated at 10Hz)
uint32_t lastUpdateTime; // Last state update time (10Hz updates)
uint32_t transitionStartTime; // When current transition started
uint8_t lastPalette; // Track palette changes
bool presetTriggered; // Flag to prevent re-triggering preset
};

// Allocate persistent data for this effect
if (!SEGENV.allocateData(sizeof(TransitionState))) FX_FALLBACK_STATIC;
TransitionState* state = reinterpret_cast<TransitionState*>(SEGENV.data);

// Initialize on first call
if (SEGENV.call == 0) {
state->transitionStartTime = strip.now;
state->lastUpdateTime = strip.now;
state->lastPalette = SEGMENT.palette;
state->presetTriggered = false;

// Initialize colors from current palette/segment colors
for (uint8_t i = 0; i < NUM_COLORS; i++) {
state->initialColors[i] = SEGCOLOR(i);
state->targetColors[i] = SEGCOLOR(i);
state->currentColors[i] = SEGCOLOR(i);
}
}

// Calculate transition duration based on speed slider
// Speed = 0: instant (for testing)
// Speed = 1-255: 1 to 255 minutes
uint32_t transitionDuration;
if (SEGMENT.speed == 0) {
transitionDuration = 0; // Instant transition
} else {
// Map speed 1-255 to 1-255 minutes (in milliseconds)
transitionDuration = (uint32_t)SEGMENT.speed * 60000UL; // speed * 60 seconds in ms
}

// Check if palette or colors have changed (new transition target)
bool targetChanged = false;
if (state->lastPalette != SEGMENT.palette) {
targetChanged = true;
state->lastPalette = SEGMENT.palette;
} else {
// Check if any color has changed
for (uint8_t i = 0; i < NUM_COLORS; i++) {
if (state->targetColors[i] != SEGCOLOR(i)) {
targetChanged = true;
break;
}
}
}

// If colors/palette changed, start new transition
if (targetChanged) {
// Current state becomes the new initial state
for (uint8_t i = 0; i < NUM_COLORS; i++) {
state->initialColors[i] = state->currentColors[i];
state->targetColors[i] = SEGCOLOR(i);
}
state->transitionStartTime = strip.now;
state->presetTriggered = false; // Reset preset trigger flag
}

// Calculate current transition progress
uint32_t elapsed = strip.now - state->transitionStartTime;
uint8_t progress;
bool transitionComplete = false;

if (transitionDuration == 0 || elapsed >= transitionDuration) {
progress = 255; // Transition complete
transitionComplete = true;
} else {
progress = (elapsed * 255UL) / transitionDuration;
}

// Update current state at 10Hz (every 100ms)
if (strip.now - state->lastUpdateTime >= 100) {
state->lastUpdateTime = strip.now;

// Update current blended colors
for (uint8_t i = 0; i < NUM_COLORS; i++) {
state->currentColors[i] = color_blend(state->initialColors[i], state->targetColors[i], progress);
}
}

// Trigger target preset when transition completes (if intensity > 0)
// Intensity slider value (0-255) maps to preset IDs 1-250 (valid preset range)
// 0 = no preset trigger, 1-255 = preset IDs 1-250
if (transitionComplete && !state->presetTriggered && SEGMENT.intensity > 0) {
// Use ceiling division to ensure intensity 1-255 always maps to preset 1-250
uint8_t targetPreset = ((SEGMENT.intensity * 250) + 254) / 255;
if (targetPreset > 0 && targetPreset <= 250) {
// Trigger the preset using CALL_MODE_NOTIFICATION to avoid feedback loops
// This is safe as the preset system handles concurrent calls
applyPreset(targetPreset, CALL_MODE_NOTIFICATION);
state->presetTriggered = true;
}
}

// Render each pixel
// For solid colors (palette 0), fill with the blended color
// For palettes, we display the palette but transition between different palette selections
if (SEGMENT.palette == 0) {
// Solid color mode - fill with blended primary color
SEGMENT.fill(state->currentColors[0]);
} else {
// Palette mode - display the palette
// The palette system itself will use the segment colors which we're tracking
for (unsigned i = 0; i < SEGLEN; i++) {
SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0));
}
}
}
static const char _data_FX_MODE_LONG_TRANSITION[] PROGMEM = "Long Transition@Duration (min),Target Preset;;!;01";

void WS2812FX::setupEffectData() {
// Solid must be first! (assuming vector is empty upon call to setup)
_mode.push_back(&mode_static);
Expand Down Expand Up @@ -10995,4 +11130,7 @@ addEffect(FX_MODE_PS1DSONICBOOM, &mode_particle1DsonicBoom, _data_FX_MODE_PS_SON
addEffect(FX_MODE_PS1DSPRINGY, &mode_particleSpringy, _data_FX_MODE_PS_SPRINGY);
#endif // WLED_DISABLE_PARTICLESYSTEM1D

// Long Transition effect
addEffect(FX_MODE_LONG_TRANSITION, &mode_long_transition, _data_FX_MODE_LONG_TRANSITION);

}
3 changes: 2 additions & 1 deletion wled00/FX.h
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,8 @@ extern byte realtimeMode; // used in getMappedPixelIndex()
#define FX_MODE_PS1DSONICBOOM 215
#define FX_MODE_PS1DSPRINGY 216
#define FX_MODE_PARTICLEGALAXY 217
#define MODE_COUNT 218
#define FX_MODE_LONG_TRANSITION 218
#define MODE_COUNT 219


#define BLEND_STYLE_FADE 0x00 // universal
Expand Down