diff --git a/_codeql_detected_source_root b/_codeql_detected_source_root new file mode 120000 index 0000000000..945c9b46d6 --- /dev/null +++ b/_codeql_detected_source_root @@ -0,0 +1 @@ +. \ No newline at end of file diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 54f441da35..fcb576afbc 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -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(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); @@ -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); + } diff --git a/wled00/FX.h b/wled00/FX.h index 9c5291665c..cd5b9e2ba0 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -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