From 395f73f0e9a992002a0671a8702e7ba446fbcf22 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 6 Jan 2026 02:48:04 +0000 Subject: [PATCH 1/6] Initial plan From 34c19aac0582b5a9e00c32c3e7cd87d3ed5aeef0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 6 Jan 2026 02:56:59 +0000 Subject: [PATCH 2/6] Complete legacy effect system removal and schema migration - Added scratchpad pointer to SegmentView for stateful effects - Updated Segment::update() to pass scratchpad to view - Simplified EffectInfo (removed legacy flags, added derived helpers) - Removed all legacy registration macros - Simplified effect function signature (removed EffectParams parameter) - Removed EffectParams struct (kept enums and helpers) - Removed SegmentCapabilities struct - Updated API to derive capabilities from schema - Migrated all 23 effects to new signature - Migrated 6 effects with static state to use scratchpad (fire, candle, scanner, rain, fireup, twinkle) Co-authored-by: bring42 <63049750+bring42@users.noreply.github.com> --- src/api/segments.cpp | 54 ++------------- src/core/effect_params.h | 19 ----- src/core/effect_registry.h | 100 +++++++++------------------ src/core/segment.h | 46 +++--------- src/core/segment_view.h | 21 +++++- src/visuallib/effects/breathe.cpp | 6 +- src/visuallib/effects/candle.cpp | 46 ++++++------ src/visuallib/effects/colorwaves.cpp | 7 +- src/visuallib/effects/comet.cpp | 13 ++-- src/visuallib/effects/confetti.cpp | 7 +- src/visuallib/effects/fire.cpp | 35 ++++++---- src/visuallib/effects/fireup.cpp | 29 ++++---- src/visuallib/effects/gradient.cpp | 7 +- src/visuallib/effects/meteor.cpp | 7 +- src/visuallib/effects/noise.cpp | 7 +- src/visuallib/effects/pacifica.cpp | 3 +- src/visuallib/effects/pride.cpp | 5 +- src/visuallib/effects/pulse.cpp | 6 +- src/visuallib/effects/rain.cpp | 45 ++++++------ src/visuallib/effects/rainbow.cpp | 7 +- src/visuallib/effects/scanner.cpp | 53 +++++++------- src/visuallib/effects/sinelon.cpp | 7 +- src/visuallib/effects/solid.cpp | 5 +- src/visuallib/effects/sparkle.cpp | 7 +- src/visuallib/effects/strobe.cpp | 7 +- src/visuallib/effects/theater.cpp | 7 +- src/visuallib/effects/twinkle.cpp | 39 ++++++----- src/visuallib/effects/wave.cpp | 11 ++- 28 files changed, 257 insertions(+), 349 deletions(-) diff --git a/src/api/segments.cpp b/src/api/segments.cpp index 9fa1506..4fc49b7 100644 --- a/src/api/segments.cpp +++ b/src/api/segments.cpp @@ -535,59 +535,17 @@ void handleApiV2EffectsList(AsyncWebServerRequest* request) { effect["name"] = info->displayName; effect["category"] = info->categoryName(); - // Include schema if available + // All effects now have schemas - serialize params from schema JsonArray params = effect["params"].to(); if (info->hasSchema()) { schemaToJson(params, info->schema); - } else { - // Legacy: generate params from flags - if (info->usesSpeed) { - JsonObject p = params.add(); - p["id"] = "speed"; - p["name"] = "Speed"; - p["type"] = "int"; - p["min"] = 0; - p["max"] = 255; - p["default"] = 128; - } - if (info->usesIntensity) { - JsonObject p = params.add(); - p["id"] = "intensity"; - p["name"] = "Intensity"; - p["type"] = "int"; - p["min"] = 0; - p["max"] = 255; - p["default"] = 128; - } - // Generate color params based on colorCount - for (uint8_t c = 0; c < info->colorCount; c++) { - JsonObject p = params.add(); - if (c == 0) { - p["id"] = "color"; - p["name"] = "Color"; - } else { - char idBuf[16], nameBuf[16]; - snprintf(idBuf, sizeof(idBuf), "color%d", c); - snprintf(nameBuf, sizeof(nameBuf), "Color %d", c + 1); - p["id"] = idBuf; - p["name"] = nameBuf; - } - p["type"] = "color"; - p["default"] = "#ff0000"; - } - if (info->usesPalette) { - JsonObject p = params.add(); - p["id"] = "palette"; - p["name"] = "Palette"; - p["type"] = "palette"; - } } - // Effect metadata - effect["usesPalette"] = info->usesPalette; - effect["colorCount"] = info->colorCount; - effect["usesSpeed"] = info->usesSpeed; - effect["usesIntensity"] = info->usesIntensity; + // Effect metadata (derived from schema) + effect["usesPalette"] = info->usesPalette(); + effect["colorCount"] = info->colorCount(); + effect["usesSpeed"] = info->hasParam("speed"); + effect["usesIntensity"] = info->hasParam("intensity"); } String output; diff --git a/src/core/effect_params.h b/src/core/effect_params.h index 2eebf48..39b4bec 100644 --- a/src/core/effect_params.h +++ b/src/core/effect_params.h @@ -5,25 +5,6 @@ namespace lume { -/** - * EffectParams - Legacy parameters struct (being phased out) - * - * Now only contains palette. All other parameters moved to ParamValues. - * Kept temporarily for palette initialization until Segment class refactor complete. - */ -struct EffectParams { - // Palette for multi-color effects - CRGBPalette16 palette; - - // Defaults - EffectParams() - : palette(RainbowColors_p) {} - - // Constructor from palette - EffectParams(CRGBPalette16 pal) - : palette(pal) {} -}; - /** * BlendMode - How overlapping segments combine */ diff --git a/src/core/effect_registry.h b/src/core/effect_registry.h index 4826363..eae223e 100644 --- a/src/core/effect_registry.h +++ b/src/core/effect_registry.h @@ -11,24 +11,23 @@ namespace lume { * Effect function signature * * All effects are pure functions with this signature: - * void effectName(SegmentView& view, const EffectParams& params, - * const ParamValues& paramValues, uint32_t frame, bool firstFrame) + * void effectName(SegmentView& view, const ParamValues& params, + * uint32_t frame, bool firstFrame) * - * - view: The segment to render to (LED array slice) - * - params: Legacy colors, speed, palette (for backward compatibility) - * - paramValues: Schema-aware typed parameter values + * - view: The segment to render to (LED array slice with scratchpad access) + * - params: Schema-aware typed parameter values (includes palette via getPalette()) * - frame: Global frame counter (for timing, use with beatsin8 etc.) * - firstFrame: True when scratchpad was just reset (effect change) * * Effects should: * - Write colors to view[0..view.size()-1] * - Use frame for animation timing (not millis()) - * - Initialize scratchpad state when firstFrame is true + * - Initialize scratchpad state when firstFrame is true via view.getScratchpad() * - Avoid global/static state - use segment scratchpad instead * - Be deterministic given the same inputs */ -using EffectFn = void (*)(SegmentView& view, const EffectParams& params, - const ParamValues& paramValues, uint32_t frame, bool firstFrame); +using EffectFn = void (*)(SegmentView& view, const ParamValues& params, + uint32_t frame, bool firstFrame); /** * Effect categories for UI grouping and filtering @@ -48,24 +47,40 @@ struct EffectInfo { const char* displayName; // Human-readable name: "Fire" EffectCategory category; // For UI grouping - // NEW: Schema pointer (nullptr = legacy effect, uses old EffectParams) + // Schema pointer (all effects must have schema now) const ParamSchema* schema; - // Parameter support flags (enables smart UI) - bool usesPalette; // Responds to palette changes - uint8_t colorCount; // Number of colors used (0-3): 0=none, 1=colors[0], 2=colors[0-1], 3=all - bool usesSpeed; // Responds to speed param - bool usesIntensity; // Responds to intensity param - // Resource hints uint16_t stateSize; // Bytes needed in scratchpad (0 = stateless) uint16_t minLeds; // Minimum LEDs for effect to look good EffectFn fn; // The actual effect function - // Helper: has new-style schema? + // Helper: has schema bool hasSchema() const { return schema != nullptr && schema->count > 0; } + // Helper: check if effect uses palette parameter + bool usesPalette() const { + return hasSchema() && schema->find("palette") != nullptr; + } + + // Helper: count color parameters + uint8_t colorCount() const { + if (!hasSchema()) return 0; + uint8_t count = 0; + for (uint8_t i = 0; i < schema->count; i++) { + if (schema->params[i].type == ParamType::Color) { + count++; + } + } + return count; + } + + // Helper: check if parameter exists + bool hasParam(const char* id) const { + return hasSchema() && schema->find(id) != nullptr; + } + // Helper to get category name const char* categoryName() const { switch (category) { @@ -172,32 +187,15 @@ class EffectRegistrar { } }; -/** - * REGISTER_EFFECT_FULL macro - All parameters explicit - * - * Usage: - * REGISTER_EFFECT_FULL(effectFire, "fire", "Fire", Animated, - * false, 0, true, true, sizeof(FireState), 10); - * // usesPal, colorCount, usesSpd, usesInt, stateSize, minLeds - */ -#define REGISTER_EFFECT_FULL(fn, idStr, dispName, cat, usesPal, colCnt, usesSpd, usesInt, stateSz, minL) \ - static lume::EffectRegistrar _registrar_##fn({ \ - idStr, dispName, lume::EffectCategory::cat, \ - nullptr, \ - usesPal, colCnt, usesSpd, usesInt, \ - stateSz, minL, fn \ - }) - -// NEW: Schema-aware registration macro +// Schema-aware registration macro #define REGISTER_EFFECT_SCHEMA(fn, idStr, dispName, cat, schemaRef, stateSz) \ static lume::EffectRegistrar _registrar_##fn({ \ idStr, dispName, lume::EffectCategory::cat, \ &schemaRef, \ - false, 0, false, false, \ stateSz, 1, fn \ }) -// NEW: Convenience macro to define schema inline +// Convenience macro to define schema inline #define DEFINE_EFFECT_SCHEMA(name, ...) \ static const lume::ParamDesc name##_params[] = { __VA_ARGS__ }; \ static const lume::ParamSchema name = { \ @@ -205,38 +203,6 @@ class EffectRegistrar { sizeof(name##_params) / sizeof(name##_params[0]) \ } -// Simple effect: animated, uses speed only -#define REGISTER_EFFECT_SIMPLE(fn, idStr) \ - REGISTER_EFFECT_FULL(fn, idStr, idStr, Animated, false, 0, true, false, 0, 1) - -// Simple effect with display name -#define REGISTER_EFFECT_SIMPLE_NAMED(fn, idStr, dispName) \ - REGISTER_EFFECT_FULL(fn, idStr, dispName, Animated, false, 0, true, false, 0, 1) - -// Static solid-type effect: no animation, uses colors[0] -#define REGISTER_EFFECT_SOLID(fn, idStr, dispName) \ - REGISTER_EFFECT_FULL(fn, idStr, dispName, Solid, false, 1, false, false, 0, 1) - -// Animated effect with speed + intensity -#define REGISTER_EFFECT_ANIMATED(fn, idStr, dispName) \ - REGISTER_EFFECT_FULL(fn, idStr, dispName, Animated, false, 0, true, true, 0, 1) - -// Moving effect with speed + intensity -#define REGISTER_EFFECT_MOVING(fn, idStr, dispName) \ - REGISTER_EFFECT_FULL(fn, idStr, dispName, Moving, false, 0, true, true, 0, 1) - -// Palette-based effect: uses palette and speed -#define REGISTER_EFFECT_PALETTE(fn, idStr, dispName) \ - REGISTER_EFFECT_FULL(fn, idStr, dispName, Animated, true, 0, true, false, 0, 1) - -// Effect using colors[0] + colors[1] -#define REGISTER_EFFECT_COLORS(fn, idStr, dispName) \ - REGISTER_EFFECT_FULL(fn, idStr, dispName, Animated, false, 2, true, false, 0, 1) - -// Stateful effect: requires scratchpad state -#define REGISTER_EFFECT_STATEFUL(fn, idStr, dispName, stateType) \ - REGISTER_EFFECT_FULL(fn, idStr, dispName, Animated, false, 0, true, true, sizeof(stateType), 1) - // Convenience function to get registry inline EffectRegistry& effects() { return EffectRegistry::instance(); diff --git a/src/core/segment.h b/src/core/segment.h index a987b0c..0055f16 100644 --- a/src/core/segment.h +++ b/src/core/segment.h @@ -11,21 +11,6 @@ namespace lume { // Forward declare for friend access class LumeController; -/** - * Segment capabilities - cached "what's meaningful right now" for UI/AI - * - * Updated automatically when effect changes: - * - UI: Only show sliders for params the effect actually uses - * - AI: Constrain prompt understanding to meaningful params - */ -struct SegmentCapabilities { - bool hasBrightness = true; // Always true for segments - bool hasSpeed; // Effect responds to speed - bool hasIntensity; // Effect responds to intensity - bool hasPalette; // Effect uses palette - uint8_t colorCount; // Number of colors used (0-3) -}; - /** * Segment - A controllable region of the LED strip * @@ -46,9 +31,7 @@ class Segment { Segment() : view() , effect(nullptr) - , params() , paramValues() - , caps() , brightness(255) , blendMode(BlendMode::Replace) , active(false) @@ -62,7 +45,7 @@ class Segment { // Set the LED range for this segment void setRange(CRGB* leds, uint16_t start, uint16_t length, bool reversed = false) { - view = SegmentView(leds, start, length, reversed); + view = SegmentView(leds, start, length, reversed, scratchpad); active = true; } @@ -83,12 +66,6 @@ class Segment { if (info->hasSchema()) { paramValues.applyDefaults(*info->schema); } - - // Update cached capabilities from effect metadata - caps.hasSpeed = info->usesSpeed; - caps.hasIntensity = info->usesIntensity; - caps.hasPalette = info->usesPalette; - caps.colorCount = info->colorCount; } // Set effect by id (looks up in registry) @@ -114,16 +91,19 @@ class Segment { return effect ? effect->displayName : "None"; } - // Get cached capabilities - const SegmentCapabilities& getCapabilities() const { return caps; } + // Helper: check if effect has a specific parameter + bool hasParam(const char* paramId) const { + return effect && effect->hasParam(paramId); + } // --- Parameter accessors --- - // Legacy palette accessors (kept for now) - void setPalette(CRGBPalette16 palette) { params.palette = palette; paramValues.setPalette(palette); } + // Palette accessors + void setPalette(CRGBPalette16 palette) { + paramValues.setPalette(palette); + } void setPalette(PalettePreset preset) { CRGBPalette16 pal = getPalette(preset); - params.palette = pal; paramValues.setPalette(pal); } @@ -177,10 +157,6 @@ class Segment { SegmentView& getView() { return view; } const SegmentView& getView() const { return view; } - // Direct access to params - EffectParams& getParams() { return params; } - const EffectParams& getParams() const { return params; } - // Direct access to param values (schema-aware effects) ParamValues& getParamValues() { return paramValues; } const ParamValues& getParamValues() const { return paramValues; } @@ -219,7 +195,7 @@ class Segment { } // Call the effect function - effect->fn(view, params, paramValues, frame, firstFrame); + effect->fn(view, paramValues, frame, firstFrame); // Apply segment brightness if not 255 if (brightness < 255) { @@ -234,9 +210,7 @@ class Segment { SegmentView view; const EffectInfo* effect; - EffectParams params; ParamValues paramValues; // Schema-aware parameter values - SegmentCapabilities caps; uint8_t brightness; BlendMode blendMode; diff --git a/src/core/segment_view.h b/src/core/segment_view.h index 105e503..c172532 100644 --- a/src/core/segment_view.h +++ b/src/core/segment_view.h @@ -26,16 +26,18 @@ struct SegmentView { uint16_t start; // First LED index in segment uint16_t length; // Number of LEDs in this segment bool reversed; // Run effect in reverse direction? + uint8_t* scratchpad; // Pointer to segment's scratchpad for stateful effects // Default constructor (empty view) - SegmentView() : base(nullptr), start(0), length(0), reversed(false) {} + SegmentView() : base(nullptr), start(0), length(0), reversed(false), scratchpad(nullptr) {} // Construct view from LED array base - SegmentView(CRGB* ledArray, uint16_t startIdx, uint16_t len, bool rev = false) + SegmentView(CRGB* ledArray, uint16_t startIdx, uint16_t len, bool rev = false, uint8_t* scratch = nullptr) : base(ledArray) , start(startIdx) , length(len) - , reversed(rev) {} + , reversed(rev) + , scratchpad(scratch) {} // Indexed access - handles reversal transparently CRGB& operator[](uint16_t i) { @@ -131,6 +133,19 @@ struct SegmentView { uint16_t map8(uint8_t pos) const { return scale16by8(length - 1, pos); } + + // --- Scratchpad access for stateful effects --- + + // Get typed scratchpad pointer (compile-time size check) + template + T* getScratchpad() { + return reinterpret_cast(scratchpad); + } + + template + const T* getScratchpad() const { + return reinterpret_cast(scratchpad); + } }; } // namespace lume diff --git a/src/visuallib/effects/breathe.cpp b/src/visuallib/effects/breathe.cpp index 6654872..822708c 100644 --- a/src/visuallib/effects/breathe.cpp +++ b/src/visuallib/effects/breathe.cpp @@ -21,13 +21,13 @@ DEFINE_EFFECT_SCHEMA(breatheSchema, ParamDesc::Int("speed", "Speed", 128, 1, 255) ); -void effectBreathe(SegmentView& view, const EffectParams& params, const ParamValues& paramValues, uint32_t frame, bool firstFrame) { +void effectBreathe(SegmentView& view, const ParamValues& params, uint32_t frame, bool firstFrame) { (void)frame; (void)firstFrame; // Read parameters - CRGB color = paramValues.getColor(breathe::COLOR); - uint8_t speed = paramValues.getInt(breathe::SPEED); + CRGB color = params.getColor(breathe::COLOR); + uint8_t speed = params.getInt(breathe::SPEED); // Map speed to BPM (breaths per minute) uint8_t bpm = map(speed, 1, 255, 5, 30); diff --git a/src/visuallib/effects/candle.cpp b/src/visuallib/effects/candle.cpp index 2f3f077..285f245 100644 --- a/src/visuallib/effects/candle.cpp +++ b/src/visuallib/effects/candle.cpp @@ -25,24 +25,30 @@ DEFINE_EFFECT_SCHEMA(candleSchema, ParamDesc::Int("intensity", "Flicker Intensity", 128, 1, 255) ); -// Static state for smooth flicker (will move to scratchpad in Phase 1) -static uint8_t candleBase = 200; -static uint8_t candleTarget = 200; -static uint32_t lastFlickerChange = 0; +// Candle state structure for scratchpad +struct CandleState { + uint8_t base; + uint8_t target; + uint32_t lastFlickerChange; +}; -void effectCandle(SegmentView& view, const EffectParams& params, const ParamValues& paramValues, uint32_t frame, bool firstFrame) { +void effectCandle(SegmentView& view, const ParamValues& params, uint32_t frame, bool firstFrame) { (void)frame; + // Access scratchpad state + CandleState* state = view.getScratchpad(); + if (!state) return; + // Read parameters - CRGB baseColor = paramValues.getColor(candle::COLOR); - uint8_t speed = paramValues.getInt(candle::SPEED); - uint8_t intensity = paramValues.getInt(candle::INTENSITY); + CRGB baseColor = params.getColor(candle::COLOR); + uint8_t speed = params.getInt(candle::SPEED); + uint8_t intensity = params.getInt(candle::INTENSITY); // Reset state on first frame if (firstFrame) { - candleBase = 200; - candleTarget = 200; - lastFlickerChange = 0; + state->base = 200; + state->target = 200; + state->lastFlickerChange = 0; } uint32_t now = millis(); @@ -51,8 +57,8 @@ void effectCandle(SegmentView& view, const EffectParams& params, const ParamValu uint32_t flickerDelay = map(speed, 1, 255, 150, 10); // Occasionally pick a new flicker target - if (now - lastFlickerChange > flickerDelay) { - lastFlickerChange = now; + if (now - state->lastFlickerChange > flickerDelay) { + state->lastFlickerChange = now; // Intensity affects dip depth (more sensitive) uint8_t maxDip = map(intensity, 1, 255, 220, 50); @@ -60,24 +66,24 @@ void effectCandle(SegmentView& view, const EffectParams& params, const ParamValu // More random flickering if (random8() < 50) { // Bigger dip - candleTarget = random8(maxDip - 80, maxDip - 30); + state->target = random8(maxDip - 80, maxDip - 30); } else if (random8() < 120) { // Small dip - candleTarget = random8(maxDip - 30, maxDip); + state->target = random8(maxDip - 30, maxDip); } else { // Stay bright - candleTarget = random8(200, 255); + state->target = random8(200, 255); } } // Smooth transition to target - if (candleBase < candleTarget) candleBase += 3; - if (candleBase > candleTarget) candleBase -= 5; // Faster dim than brighten + if (state->base < state->target) state->base += 3; + if (state->base > state->target) state->base -= 5; // Faster dim than brighten // Apply to all LEDs with tiny per-LED variation for (uint16_t i = 0; i < view.size(); i++) { uint8_t variation = random8(0, 15); - uint8_t brightness = qadd8(candleBase, variation) - 7; + uint8_t brightness = qadd8(state->base, variation) - 7; CRGB color = baseColor; color.nscale8(brightness); @@ -85,6 +91,6 @@ void effectCandle(SegmentView& view, const EffectParams& params, const ParamValu } } -REGISTER_EFFECT_SCHEMA(effectCandle, "candle", "Candle", Animated, candleSchema, 0); +REGISTER_EFFECT_SCHEMA(effectCandle, "candle", "Candle", Animated, candleSchema, sizeof(CandleState)); } // namespace lume diff --git a/src/visuallib/effects/colorwaves.cpp b/src/visuallib/effects/colorwaves.cpp index cbdc510..41ffae4 100644 --- a/src/visuallib/effects/colorwaves.cpp +++ b/src/visuallib/effects/colorwaves.cpp @@ -15,12 +15,11 @@ DEFINE_EFFECT_SCHEMA(colorwavesSchema, ParamDesc::Int("speed", "Speed", 128, 1, 255) ); -void effectColorWaves(SegmentView& view, const EffectParams& params, const ParamValues& paramValues, uint32_t frame, bool firstFrame) { +void effectColorWaves(SegmentView& view, const ParamValues& params, uint32_t frame, bool firstFrame) { (void)firstFrame; - (void)params; - const CRGBPalette16& palette = paramValues.getPalette(); - uint8_t speed = paramValues.getInt(colorwaves::SPEED); + const CRGBPalette16& palette = params.getPalette(); + uint8_t speed = params.getInt(colorwaves::SPEED); uint16_t len = view.size(); diff --git a/src/visuallib/effects/comet.cpp b/src/visuallib/effects/comet.cpp index b3be209..6555c31 100644 --- a/src/visuallib/effects/comet.cpp +++ b/src/visuallib/effects/comet.cpp @@ -22,15 +22,14 @@ DEFINE_EFFECT_SCHEMA(cometSchema, ParamDesc::Enum("direction", "Direction", "Up|Down", 0) ); -void effectComet(SegmentView& view, const EffectParams& params, const ParamValues& paramValues, uint32_t frame, bool firstFrame) { +void effectComet(SegmentView& view, const ParamValues& params, uint32_t frame, bool firstFrame) { (void)firstFrame; - (void)params; - CRGB colorHead = paramValues.getColor(comet::COLOR_HEAD); - CRGB colorTail = paramValues.getColor(comet::COLOR_TAIL); - uint8_t speed = paramValues.getInt(comet::SPEED); - uint8_t intensity = paramValues.getInt(comet::INTENSITY); - bool goingDown = paramValues.getEnum(comet::DIRECTION) != 0; + CRGB colorHead = params.getColor(comet::COLOR_HEAD); + CRGB colorTail = params.getColor(comet::COLOR_TAIL); + uint8_t speed = params.getInt(comet::SPEED); + uint8_t intensity = params.getInt(comet::INTENSITY); + bool goingDown = params.getEnum(comet::DIRECTION) != 0; uint16_t len = view.size(); if (len == 0) return; diff --git a/src/visuallib/effects/confetti.cpp b/src/visuallib/effects/confetti.cpp index 13a99e9..b2a0e3a 100644 --- a/src/visuallib/effects/confetti.cpp +++ b/src/visuallib/effects/confetti.cpp @@ -15,13 +15,12 @@ DEFINE_EFFECT_SCHEMA(confettiSchema, ParamDesc::Int("speed", "Spawn Rate", 128, 1, 255) ); -void effectConfetti(SegmentView& view, const EffectParams& params, const ParamValues& paramValues, uint32_t frame, bool firstFrame) { +void effectConfetti(SegmentView& view, const ParamValues& params, uint32_t frame, bool firstFrame) { (void)frame; (void)firstFrame; - (void)params; - const CRGBPalette16& palette = paramValues.getPalette(); - uint8_t speed = paramValues.getInt(confetti::SPEED); + const CRGBPalette16& palette = params.getPalette(); + uint8_t speed = params.getInt(confetti::SPEED); // Fade all pixels slightly view.fade(10); diff --git a/src/visuallib/effects/fire.cpp b/src/visuallib/effects/fire.cpp index 97ca4c6..8212fc5 100644 --- a/src/visuallib/effects/fire.cpp +++ b/src/visuallib/effects/fire.cpp @@ -24,53 +24,58 @@ DEFINE_EFFECT_SCHEMA(fireSchema, ParamDesc::Bool("reversed", "Reversed", false) ); -// Static heat array (TODO: move to segment scratchpad when API supports it) -static uint8_t heat[600]; +// Heat array structure for scratchpad +struct FireState { + uint8_t heat[600]; +}; -// Effect function - now uses ParamValues -void effectFire(SegmentView& view, const EffectParams& params, const ParamValues& paramValues, uint32_t frame, bool firstFrame) { +// Effect function +void effectFire(SegmentView& view, const ParamValues& params, uint32_t frame, bool firstFrame) { (void)frame; - (void)params; const uint16_t numLeds = view.size(); if (numLeds == 0 || numLeds > 600) return; + // Access scratchpad state + FireState* state = view.getScratchpad(); + if (!state) return; + // Read from ParamValues slots (schema-aware) - uint8_t cooling = paramValues.getInt(fire::COOLING); - uint8_t sparking = paramValues.getInt(fire::SPARKING); - bool reversed = paramValues.getBool(fire::REVERSED); + uint8_t cooling = params.getInt(fire::COOLING); + uint8_t sparking = params.getInt(fire::SPARKING); + bool reversed = params.getBool(fire::REVERSED); if (firstFrame) { - memset(heat, 0, sizeof(heat)); + memset(state->heat, 0, sizeof(state->heat)); } // Fire simulation (standard algorithm) // Step 1: Cool down for (uint16_t i = 0; i < numLeds; i++) { - heat[i] = qsub8(heat[i], random8(0, ((cooling * 10) / numLeds) + 2)); + state->heat[i] = qsub8(state->heat[i], random8(0, ((cooling * 10) / numLeds) + 2)); } // Step 2: Heat diffuses upward for (uint16_t i = numLeds - 1; i >= 2; i--) { - heat[i] = (heat[i - 1] + heat[i - 2] + heat[i - 2]) / 3; + state->heat[i] = (state->heat[i - 1] + state->heat[i - 2] + state->heat[i - 2]) / 3; } // Step 3: Random sparks if (random8() < sparking) { uint8_t y = random8(7); if (y < numLeds) { - heat[y] = qadd8(heat[y], random8(160, 255)); + state->heat[y] = qadd8(state->heat[y], random8(160, 255)); } } // Step 4: Map heat to colors for (uint16_t i = 0; i < numLeds; i++) { uint16_t idx = reversed ? (numLeds - 1 - i) : i; - view[idx] = HeatColor(heat[i]); + view[idx] = HeatColor(state->heat[i]); } } -// Register with schema -REGISTER_EFFECT_SCHEMA(effectFire, "fire", "Fire", Animated, fireSchema, 0); +// Register with schema - now with proper state size +REGISTER_EFFECT_SCHEMA(effectFire, "fire", "Fire", Animated, fireSchema, sizeof(FireState)); } // namespace lume diff --git a/src/visuallib/effects/fireup.cpp b/src/visuallib/effects/fireup.cpp index 930db49..87e4692 100644 --- a/src/visuallib/effects/fireup.cpp +++ b/src/visuallib/effects/fireup.cpp @@ -18,51 +18,56 @@ DEFINE_EFFECT_SCHEMA(fireupSchema, ParamDesc::Int("intensity", "Cooling", 55, 1, 255) ); -// Heat array for fire simulation (will move to scratchpad in Phase 1) -static uint8_t fireUpHeat[300]; +// Heat array structure for scratchpad +struct FireUpState { + uint8_t heat[300]; +}; -void effectFireUp(SegmentView& view, const EffectParams& params, const ParamValues& paramValues, uint32_t frame, bool firstFrame) { +void effectFireUp(SegmentView& view, const ParamValues& params, uint32_t frame, bool firstFrame) { (void)frame; - (void)params; - uint8_t sparking = paramValues.getInt(fireup::SPEED); - uint8_t intensity = paramValues.getInt(fireup::INTENSITY); + // Access scratchpad state + FireUpState* state = view.getScratchpad(); + if (!state) return; + + uint8_t sparking = params.getInt(fireup::SPEED); + uint8_t intensity = params.getInt(fireup::INTENSITY); uint16_t len = min(view.size(), (uint16_t)300); if (len == 0) return; // Reset heat on first frame if (firstFrame) { - memset(fireUpHeat, 0, sizeof(fireUpHeat)); + memset(state->heat, 0, sizeof(state->heat)); } uint8_t cooling = intensity > 0 ? intensity / 4 : 55; // Cool down every cell for (uint16_t i = 0; i < len; i++) { - fireUpHeat[i] = qsub8(fireUpHeat[i], random8(0, ((cooling * 10) / len) + 2)); + state->heat[i] = qsub8(state->heat[i], random8(0, ((cooling * 10) / len) + 2)); } // Heat drifts up (toward index 0, opposite of normal fire) for (uint16_t k = 0; k < len - 2; k++) { - fireUpHeat[k] = (fireUpHeat[k + 1] + fireUpHeat[k + 2] + fireUpHeat[k + 2]) / 3; + state->heat[k] = (state->heat[k + 1] + state->heat[k + 2] + state->heat[k + 2]) / 3; } // Randomly ignite new sparks at TOP (high index) if (random8() < sparking) { uint8_t y = len - 1 - random8(7); if (y < len) { - fireUpHeat[y] = qadd8(fireUpHeat[y], random8(160, 255)); + state->heat[y] = qadd8(state->heat[y], random8(160, 255)); } } // Map heat to LED colors for (uint16_t j = 0; j < len; j++) { - uint8_t colorIndex = scale8(fireUpHeat[j], 240); + uint8_t colorIndex = scale8(state->heat[j], 240); view[j] = ColorFromPalette(HeatColors_p, colorIndex); } } -REGISTER_EFFECT_SCHEMA(effectFireUp, "fireup", "Fire Up", Animated, fireupSchema, 0); +REGISTER_EFFECT_SCHEMA(effectFireUp, "fireup", "Fire Up", Animated, fireupSchema, sizeof(FireUpState)); } // namespace lume diff --git a/src/visuallib/effects/gradient.cpp b/src/visuallib/effects/gradient.cpp index a380833..8178b0d 100644 --- a/src/visuallib/effects/gradient.cpp +++ b/src/visuallib/effects/gradient.cpp @@ -16,13 +16,12 @@ DEFINE_EFFECT_SCHEMA(gradientSchema, ParamDesc::Color("colorEnd", "End Color", CRGB::Red) ); -void effectGradient(SegmentView& view, const EffectParams& params, const ParamValues& paramValues, uint32_t frame, bool firstFrame) { +void effectGradient(SegmentView& view, const ParamValues& params, uint32_t frame, bool firstFrame) { (void)frame; (void)firstFrame; - (void)params; - CRGB colorStart = paramValues.getColor(gradient::COLOR_START); - CRGB colorEnd = paramValues.getColor(gradient::COLOR_END); + CRGB colorStart = params.getColor(gradient::COLOR_START); + CRGB colorEnd = params.getColor(gradient::COLOR_END); view.gradient(colorStart, colorEnd); } diff --git a/src/visuallib/effects/meteor.cpp b/src/visuallib/effects/meteor.cpp index 140bc30..ec118eb 100644 --- a/src/visuallib/effects/meteor.cpp +++ b/src/visuallib/effects/meteor.cpp @@ -16,12 +16,11 @@ DEFINE_EFFECT_SCHEMA(meteorSchema, ParamDesc::Int("speed", "Fall Speed", 128, 1, 255) ); -void effectMeteor(SegmentView& view, const EffectParams& params, const ParamValues& paramValues, uint32_t frame, bool firstFrame) { +void effectMeteor(SegmentView& view, const ParamValues& params, uint32_t frame, bool firstFrame) { (void)firstFrame; - (void)params; - CRGB color = paramValues.getColor(meteor::COLOR); - uint8_t speed = paramValues.getInt(meteor::SPEED); + CRGB color = params.getColor(meteor::COLOR); + uint8_t speed = params.getInt(meteor::SPEED); uint16_t len = view.size(); if (len == 0) return; diff --git a/src/visuallib/effects/noise.cpp b/src/visuallib/effects/noise.cpp index 89a2b29..3d66fec 100644 --- a/src/visuallib/effects/noise.cpp +++ b/src/visuallib/effects/noise.cpp @@ -15,12 +15,11 @@ DEFINE_EFFECT_SCHEMA(noiseSchema, ParamDesc::Int("speed", "Speed", 128, 1, 255) ); -void effectNoise(SegmentView& view, const EffectParams& params, const ParamValues& paramValues, uint32_t frame, bool firstFrame) { +void effectNoise(SegmentView& view, const ParamValues& params, uint32_t frame, bool firstFrame) { (void)firstFrame; - (void)params; - const CRGBPalette16& palette = paramValues.getPalette(); - uint8_t speed = paramValues.getInt(noise::SPEED); + const CRGBPalette16& palette = params.getPalette(); + uint8_t speed = params.getInt(noise::SPEED); uint16_t len = view.size(); diff --git a/src/visuallib/effects/pacifica.cpp b/src/visuallib/effects/pacifica.cpp index 7e504cb..dc99d2c 100644 --- a/src/visuallib/effects/pacifica.cpp +++ b/src/visuallib/effects/pacifica.cpp @@ -39,9 +39,8 @@ DEFINE_GRADIENT_PALETTE(pacifica3) { 255, 100,180,220 }; -void effectPacifica(SegmentView& view, const EffectParams& params, const ParamValues& paramValues, uint32_t frame, bool firstFrame) { +void effectPacifica(SegmentView& view, const ParamValues& params, uint32_t frame, bool firstFrame) { (void)firstFrame; - (void)params; uint8_t speed = paramValues.getInt(pacifica::SPEED); diff --git a/src/visuallib/effects/pride.cpp b/src/visuallib/effects/pride.cpp index 23ecc9b..12f2895 100644 --- a/src/visuallib/effects/pride.cpp +++ b/src/visuallib/effects/pride.cpp @@ -27,11 +27,10 @@ DEFINE_GRADIENT_PALETTE(prideGradient) { 255, 255, 0, 0 // Back to red }; -void effectPride(SegmentView& view, const EffectParams& params, const ParamValues& paramValues, uint32_t frame, bool firstFrame) { +void effectPride(SegmentView& view, const ParamValues& params, uint32_t frame, bool firstFrame) { (void)firstFrame; - (void)params; - uint8_t speed = paramValues.getInt(pride::SPEED); + uint8_t speed = params.getInt(pride::SPEED); uint16_t len = view.size(); CRGBPalette16 pridePalette = prideGradient; diff --git a/src/visuallib/effects/pulse.cpp b/src/visuallib/effects/pulse.cpp index 37065a9..1f51992 100644 --- a/src/visuallib/effects/pulse.cpp +++ b/src/visuallib/effects/pulse.cpp @@ -19,12 +19,12 @@ DEFINE_EFFECT_SCHEMA(pulseSchema, ParamDesc::Int("speed", "Speed", 128, 1, 255) ); -void effectPulse(SegmentView& view, const EffectParams& params, const ParamValues& paramValues, uint32_t frame, bool firstFrame) { +void effectPulse(SegmentView& view, const ParamValues& params, uint32_t frame, bool firstFrame) { (void)firstFrame; // Read parameters - CRGB color = paramValues.getColor(pulse::COLOR); - uint8_t speed = paramValues.getInt(pulse::SPEED); + CRGB color = params.getColor(pulse::COLOR); + uint8_t speed = params.getInt(pulse::SPEED); // Use beatsin8 for smooth sine wave uint8_t bpm = speed / 4; // Map 1-255 to reasonable BPM diff --git a/src/visuallib/effects/rain.cpp b/src/visuallib/effects/rain.cpp index 56c5a78..d23249d 100644 --- a/src/visuallib/effects/rain.cpp +++ b/src/visuallib/effects/rain.cpp @@ -20,26 +20,31 @@ DEFINE_EFFECT_SCHEMA(rainSchema, ParamDesc::Int("intensity", "Drop Density", 128, 1, 255) ); -// Drop state (will move to scratchpad in Phase 1) +// Drop state structure for scratchpad constexpr uint8_t MAX_DROPS = 10; -static uint8_t dropBrightness[MAX_DROPS] = {0}; -static uint16_t dropPosition[MAX_DROPS] = {0}; +struct RainState { + uint8_t dropBrightness[MAX_DROPS]; + uint16_t dropPosition[MAX_DROPS]; +}; -void effectRain(SegmentView& view, const EffectParams& params, const ParamValues& paramValues, uint32_t frame, bool firstFrame) { +void effectRain(SegmentView& view, const ParamValues& params, uint32_t frame, bool firstFrame) { (void)frame; - (void)params; - CRGB color = paramValues.getColor(rain::COLOR); - uint8_t speed = paramValues.getInt(rain::SPEED); - uint8_t intensity = paramValues.getInt(rain::INTENSITY); + // Access scratchpad state + RainState* state = view.getScratchpad(); + if (!state) return; + + CRGB color = params.getColor(rain::COLOR); + uint8_t speed = params.getInt(rain::SPEED); + uint8_t intensity = params.getInt(rain::INTENSITY); uint16_t len = view.size(); if (len == 0) return; // Reset state on first frame if (firstFrame) { - memset(dropBrightness, 0, sizeof(dropBrightness)); - memset(dropPosition, 0, sizeof(dropPosition)); + memset(state->dropBrightness, 0, sizeof(state->dropBrightness)); + memset(state->dropPosition, 0, sizeof(state->dropPosition)); } uint8_t density = intensity > 0 ? intensity / 5 : 10; @@ -50,18 +55,18 @@ void effectRain(SegmentView& view, const EffectParams& params, const ParamValues // Update and draw drops for (uint8_t d = 0; d < MAX_DROPS; d++) { - if (dropBrightness[d] > 0) { - dropPosition[d] += dropSpeed; + if (state->dropBrightness[d] > 0) { + state->dropPosition[d] += dropSpeed; - if (dropPosition[d] < len) { + if (state->dropPosition[d] < len) { // Drops fall from top (high index) to bottom (low index) - uint16_t pixelPos = len - 1 - dropPosition[d]; + uint16_t pixelPos = len - 1 - state->dropPosition[d]; CRGB colorScaled = color; - colorScaled.nscale8(dropBrightness[d]); + colorScaled.nscale8(state->dropBrightness[d]); view[pixelPos] = colorScaled; } else { // Drop reached bottom - dropBrightness[d] = 0; + state->dropBrightness[d] = 0; } } } @@ -69,15 +74,15 @@ void effectRain(SegmentView& view, const EffectParams& params, const ParamValues // Spawn new drops if (random8() < density) { for (uint8_t d = 0; d < MAX_DROPS; d++) { - if (dropBrightness[d] == 0) { - dropBrightness[d] = random8(150, 255); - dropPosition[d] = 0; + if (state->dropBrightness[d] == 0) { + state->dropBrightness[d] = random8(150, 255); + state->dropPosition[d] = 0; break; } } } } -REGISTER_EFFECT_SCHEMA(effectRain, "rain", "Rain", Moving, rainSchema, 0); +REGISTER_EFFECT_SCHEMA(effectRain, "rain", "Rain", Moving, rainSchema, sizeof(RainState)); } // namespace lume diff --git a/src/visuallib/effects/rainbow.cpp b/src/visuallib/effects/rainbow.cpp index e16ef29..8165b7d 100644 --- a/src/visuallib/effects/rainbow.cpp +++ b/src/visuallib/effects/rainbow.cpp @@ -21,13 +21,12 @@ DEFINE_EFFECT_SCHEMA(rainbowSchema, ParamDesc::Int("density", "Density", 85, 1, 255) ); -void effectRainbow(SegmentView& view, const EffectParams& params, const ParamValues& paramValues, uint32_t frame, bool firstFrame) { +void effectRainbow(SegmentView& view, const ParamValues& params, uint32_t frame, bool firstFrame) { (void)firstFrame; - (void)params; // Read from ParamValues slots (schema-aware) - uint8_t speed = paramValues.getInt(rainbow::SPEED); - uint8_t density = paramValues.getInt(rainbow::DENSITY); + uint8_t speed = params.getInt(rainbow::SPEED); + uint8_t density = params.getInt(rainbow::DENSITY); // Speed controls how fast the rainbow moves uint8_t hue = (frame * speed) >> 6; diff --git a/src/visuallib/effects/scanner.cpp b/src/visuallib/effects/scanner.cpp index 5653491..b840336 100644 --- a/src/visuallib/effects/scanner.cpp +++ b/src/visuallib/effects/scanner.cpp @@ -22,29 +22,32 @@ DEFINE_EFFECT_SCHEMA(scannerSchema, ParamDesc::Int("intensity", "Tail Length", 80, 1, 255) ); -// Static position and direction (will move to scratchpad in Phase 1) -static int16_t scannerPos = 0; -static int8_t scannerDir = 1; +// Scanner state structure for scratchpad +struct ScannerState { + int16_t pos; + int8_t dir; + uint8_t frameCount; +}; -// Static for frame skip timing -static uint8_t scannerFrameCount = 0; - -void effectScanner(SegmentView& view, const EffectParams& params, const ParamValues& paramValues, uint32_t frame, bool firstFrame) { +void effectScanner(SegmentView& view, const ParamValues& params, uint32_t frame, bool firstFrame) { (void)frame; - (void)params; - CRGB color = paramValues.getColor(scanner::COLOR); - uint8_t speed = paramValues.getInt(scanner::SPEED); - uint8_t intensity = paramValues.getInt(scanner::INTENSITY); + // Access scratchpad state + ScannerState* state = view.getScratchpad(); + if (!state) return; + + CRGB color = params.getColor(scanner::COLOR); + uint8_t speed = params.getInt(scanner::SPEED); + uint8_t intensity = params.getInt(scanner::INTENSITY); uint16_t len = view.size(); if (len == 0) return; // Reset position on first frame (effect change) if (firstFrame) { - scannerPos = 0; - scannerDir = 1; - scannerFrameCount = 0; + state->pos = 0; + state->dir = 1; + state->frameCount = 0; } uint8_t tailLength = intensity > 0 ? intensity / 4 : 20; @@ -57,13 +60,13 @@ void effectScanner(SegmentView& view, const EffectParams& params, const ParamVal view.fade(40); // Main dot - if (scannerPos >= 0 && scannerPos < (int16_t)len) { - view[scannerPos] = color; + if (state->pos >= 0 && state->pos < (int16_t)len) { + view[state->pos] = color; } // Tail behind the dot for (uint8_t i = 1; i <= tailLength; i++) { - int16_t tailPos = scannerPos - (scannerDir * i); + int16_t tailPos = state->pos - (state->dir * i); if (tailPos >= 0 && tailPos < (int16_t)len) { uint8_t fade = 255 - (i * 255 / tailLength); CRGB tailColor = color; @@ -73,19 +76,19 @@ void effectScanner(SegmentView& view, const EffectParams& params, const ParamVal } // Move scanner based on speed - scannerFrameCount++; - if (scannerFrameCount >= frameSkip) { - scannerFrameCount = 0; - scannerPos += scannerDir; + state->frameCount++; + if (state->frameCount >= frameSkip) { + state->frameCount = 0; + state->pos += state->dir; } // Bounce at edges - if (scannerPos >= (int16_t)len || scannerPos < 0) { - scannerDir = -scannerDir; - scannerPos = constrain(scannerPos, 0, len - 1); + if (state->pos >= (int16_t)len || state->pos < 0) { + state->dir = -state->dir; + state->pos = constrain(state->pos, 0, len - 1); } } -REGISTER_EFFECT_SCHEMA(effectScanner, "scanner", "Scanner", Moving, scannerSchema, 0); +REGISTER_EFFECT_SCHEMA(effectScanner, "scanner", "Scanner", Moving, scannerSchema, sizeof(ScannerState)); } // namespace lume diff --git a/src/visuallib/effects/sinelon.cpp b/src/visuallib/effects/sinelon.cpp index f071f20..036c56c 100644 --- a/src/visuallib/effects/sinelon.cpp +++ b/src/visuallib/effects/sinelon.cpp @@ -15,12 +15,11 @@ DEFINE_EFFECT_SCHEMA(sinelonSchema, ParamDesc::Int("speed", "Speed", 128, 1, 255) ); -void effectSinelon(SegmentView& view, const EffectParams& params, const ParamValues& paramValues, uint32_t frame, bool firstFrame) { +void effectSinelon(SegmentView& view, const ParamValues& params, uint32_t frame, bool firstFrame) { (void)firstFrame; - (void)params; - const CRGBPalette16& palette = paramValues.getPalette(); - uint8_t speed = paramValues.getInt(sinelon::SPEED); + const CRGBPalette16& palette = params.getPalette(); + uint8_t speed = params.getInt(sinelon::SPEED); uint16_t len = view.size(); if (len == 0) return; diff --git a/src/visuallib/effects/solid.cpp b/src/visuallib/effects/solid.cpp index f7d2167..37ab1a5 100644 --- a/src/visuallib/effects/solid.cpp +++ b/src/visuallib/effects/solid.cpp @@ -19,13 +19,12 @@ DEFINE_EFFECT_SCHEMA(solidSchema, ParamDesc::Color("color", "Color", CRGB::Red) ); -void effectSolid(SegmentView& view, const EffectParams& params, const ParamValues& paramValues, uint32_t frame, bool firstFrame) { +void effectSolid(SegmentView& view, const ParamValues& params, uint32_t frame, bool firstFrame) { (void)frame; (void)firstFrame; - (void)params; // Read from ParamValues slots (schema-aware) - CRGB color = paramValues.getColor(solid::COLOR); + CRGB color = params.getColor(solid::COLOR); view.fill(color); } diff --git a/src/visuallib/effects/sparkle.cpp b/src/visuallib/effects/sparkle.cpp index 351d7f6..2bbb9f3 100644 --- a/src/visuallib/effects/sparkle.cpp +++ b/src/visuallib/effects/sparkle.cpp @@ -16,13 +16,12 @@ DEFINE_EFFECT_SCHEMA(sparkleSchema, ParamDesc::Int("speed", "Sparkle Density", 128, 1, 255) ); -void effectSparkle(SegmentView& view, const EffectParams& params, const ParamValues& paramValues, uint32_t frame, bool firstFrame) { +void effectSparkle(SegmentView& view, const ParamValues& params, uint32_t frame, bool firstFrame) { (void)frame; (void)firstFrame; - (void)params; - CRGB color = paramValues.getColor(sparkle::COLOR); - uint8_t speed = paramValues.getInt(sparkle::SPEED); + CRGB color = params.getColor(sparkle::COLOR); + uint8_t speed = params.getInt(sparkle::SPEED); // Fill with background color view.fill(color); diff --git a/src/visuallib/effects/strobe.cpp b/src/visuallib/effects/strobe.cpp index f2b6502..028dab5 100644 --- a/src/visuallib/effects/strobe.cpp +++ b/src/visuallib/effects/strobe.cpp @@ -16,12 +16,11 @@ DEFINE_EFFECT_SCHEMA(strobeSchema, ParamDesc::Int("speed", "Flash Rate", 128, 1, 255) ); -void effectStrobe(SegmentView& view, const EffectParams& params, const ParamValues& paramValues, uint32_t frame, bool firstFrame) { +void effectStrobe(SegmentView& view, const ParamValues& params, uint32_t frame, bool firstFrame) { (void)firstFrame; - (void)params; - CRGB color = paramValues.getColor(strobe::COLOR); - uint8_t speed = paramValues.getInt(strobe::SPEED); + CRGB color = params.getColor(strobe::COLOR); + uint8_t speed = params.getInt(strobe::SPEED); // Speed controls flash rate (higher = faster) uint8_t rate = (256 - speed) / 8; diff --git a/src/visuallib/effects/theater.cpp b/src/visuallib/effects/theater.cpp index dc1c3a8..88b6f86 100644 --- a/src/visuallib/effects/theater.cpp +++ b/src/visuallib/effects/theater.cpp @@ -15,12 +15,11 @@ DEFINE_EFFECT_SCHEMA(theaterSchema, ParamDesc::Int("speed", "Speed", 128, 1, 255) ); -void effectTheaterChase(SegmentView& view, const EffectParams& params, const ParamValues& paramValues, uint32_t frame, bool firstFrame) { +void effectTheaterChase(SegmentView& view, const ParamValues& params, uint32_t frame, bool firstFrame) { (void)firstFrame; - (void)params; - const CRGBPalette16& palette = paramValues.getPalette(); - uint8_t speed = paramValues.getInt(theater::SPEED); + const CRGBPalette16& palette = params.getPalette(); + uint8_t speed = params.getInt(theater::SPEED); uint16_t len = view.size(); diff --git a/src/visuallib/effects/twinkle.cpp b/src/visuallib/effects/twinkle.cpp index aba2172..704dbd7 100644 --- a/src/visuallib/effects/twinkle.cpp +++ b/src/visuallib/effects/twinkle.cpp @@ -20,56 +20,61 @@ DEFINE_EFFECT_SCHEMA(twinkleSchema, ParamDesc::Int("speed", "Twinkle Rate", 128, 1, 255) ); -// Twinkle state - per-LED fade state (will move to scratchpad in Phase 1) +// Twinkle state structure for scratchpad // 0 = off, 1-127 = fading in, 128-255 = fading out -static uint8_t twinkleState[300] = {0}; +struct TwinkleState { + uint8_t state[300]; +}; -void effectTwinkle(SegmentView& view, const EffectParams& params, const ParamValues& paramValues, uint32_t frame, bool firstFrame) { +void effectTwinkle(SegmentView& view, const ParamValues& params, uint32_t frame, bool firstFrame) { (void)frame; - (void)params; - CRGB color = paramValues.getColor(twinkle::COLOR); - uint8_t speed = paramValues.getInt(twinkle::SPEED); + // Access scratchpad state + TwinkleState* state = view.getScratchpad(); + if (!state) return; + + CRGB color = params.getColor(twinkle::COLOR); + uint8_t speed = params.getInt(twinkle::SPEED); uint16_t len = min(view.size(), (uint16_t)300); // Reset state on first frame if (firstFrame) { - memset(twinkleState, 0, sizeof(twinkleState)); + memset(state->state, 0, sizeof(state->state)); } uint8_t spawnChance = map(speed, 1, 255, 5, 40); for (uint16_t i = 0; i < len; i++) { - if (twinkleState[i] == 0) { + if (state->state[i] == 0) { // Maybe start a new twinkle if (random8() < spawnChance) { - twinkleState[i] = 1; + state->state[i] = 1; } view[i] = CRGB::Black; - } else if (twinkleState[i] < 128) { + } else if (state->state[i] < 128) { // Fading in - twinkleState[i] += 4; - if (twinkleState[i] >= 128) twinkleState[i] = 128; + state->state[i] += 4; + if (state->state[i] >= 128) state->state[i] = 128; - uint8_t bri = twinkleState[i] * 2; + uint8_t bri = state->state[i] * 2; CRGB colorScaled = color; colorScaled.nscale8(bri); view[i] = colorScaled; } else { // Fading out - twinkleState[i] += 2; + state->state[i] += 2; - uint8_t bri = (255 - twinkleState[i]) * 2; + uint8_t bri = (255 - state->state[i]) * 2; CRGB colorScaled = color; colorScaled.nscale8(bri); view[i] = colorScaled; - if (twinkleState[i] >= 254) twinkleState[i] = 0; + if (state->state[i] >= 254) state->state[i] = 0; } } } -REGISTER_EFFECT_SCHEMA(effectTwinkle, "twinkle", "Twinkle", Animated, twinkleSchema, 0); +REGISTER_EFFECT_SCHEMA(effectTwinkle, "twinkle", "Twinkle", Animated, twinkleSchema, sizeof(TwinkleState)); } // namespace lume diff --git a/src/visuallib/effects/wave.cpp b/src/visuallib/effects/wave.cpp index 0a19f83..e6ddcf4 100644 --- a/src/visuallib/effects/wave.cpp +++ b/src/visuallib/effects/wave.cpp @@ -20,14 +20,13 @@ DEFINE_EFFECT_SCHEMA(waveSchema, ParamDesc::Enum("direction", "Direction", "Up|Down|Center", 0) ); -void effectWave(SegmentView& view, const EffectParams& params, const ParamValues& paramValues, uint32_t frame, bool firstFrame) { +void effectWave(SegmentView& view, const ParamValues& params, uint32_t frame, bool firstFrame) { (void)firstFrame; - (void)params; - CRGB color = paramValues.getColor(wave::COLOR); - uint8_t speed = paramValues.getInt(wave::SPEED); - uint8_t intensity = paramValues.getInt(wave::INTENSITY); - uint8_t direction = paramValues.getEnum(wave::DIRECTION); + CRGB color = params.getColor(wave::COLOR); + uint8_t speed = params.getInt(wave::SPEED); + uint8_t intensity = params.getInt(wave::INTENSITY); + uint8_t direction = params.getEnum(wave::DIRECTION); uint16_t len = view.size(); if (len == 0) return; From 079249de3f76db1511a2002d35ad3695babe8d74 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 6 Jan 2026 02:58:22 +0000 Subject: [PATCH 3/6] Fix remaining references to removed legacy code - Updated server.cpp to derive capabilities from effect schema instead of SegmentCapabilities - Updated effects.h forward declarations to use new effect signature - Added param_schema.h include to effects.h Co-authored-by: bring42 <63049750+bring42@users.noreply.github.com> --- src/network/server.cpp | 19 +++++++++----- src/visuallib/effects.h | 57 ++++++++++++++++++++++------------------- 2 files changed, 44 insertions(+), 32 deletions(-) diff --git a/src/network/server.cpp b/src/network/server.cpp index a86797d..28bd77a 100644 --- a/src/network/server.cpp +++ b/src/network/server.cpp @@ -300,13 +300,20 @@ void setupServer() { } } - // Capabilities - const lume::SegmentCapabilities& caps = seg->getCapabilities(); + // Capabilities - derived from effect schema JsonObject capsObj = segObj["capabilities"].to(); - capsObj["hasSpeed"] = caps.hasSpeed; - capsObj["hasIntensity"] = caps.hasIntensity; - capsObj["hasPalette"] = caps.hasPalette; - capsObj["colorCount"] = caps.colorCount; + const lume::EffectInfo* effect = seg->getEffect(); + if (effect) { + capsObj["hasSpeed"] = effect->hasParam("speed"); + capsObj["hasIntensity"] = effect->hasParam("intensity"); + capsObj["hasPalette"] = effect->usesPalette(); + capsObj["colorCount"] = effect->colorCount(); + } else { + capsObj["hasSpeed"] = false; + capsObj["hasIntensity"] = false; + capsObj["hasPalette"] = false; + capsObj["colorCount"] = 0; + } } // Available effects diff --git a/src/visuallib/effects.h b/src/visuallib/effects.h index 43cf9ec..7ef83e9 100644 --- a/src/visuallib/effects.h +++ b/src/visuallib/effects.h @@ -9,53 +9,58 @@ * * To add a new effect: * 1. Create effects/my_effect.cpp - * 2. Implement the effect function - * 3. Use REGISTER_EFFECT macro - * 4. Add declaration here (optional but good for IDE) + * 2. Define schema with DEFINE_EFFECT_SCHEMA + * 3. Implement the effect function with new signature + * 4. Use REGISTER_EFFECT_SCHEMA macro * 5. That's it - no other changes needed + * + * Effects are accessed via the effect registry, not by direct function calls. + * Forward declarations below are for IDE support only. */ #include "../core/effect_registry.h" +#include "../core/param_schema.h" namespace lume { // === Effect function declarations === // Defined in individual .cpp files, auto-registered via macros +// Note: Effects use the new signature: (SegmentView& view, const ParamValues& params, uint32_t frame, bool firstFrame) // Basic effects -void effectSolid(SegmentView& view, const EffectParams& params, uint32_t frame); -void effectRainbow(SegmentView& view, const EffectParams& params, uint32_t frame); -void effectGradient(SegmentView& view, const EffectParams& params, uint32_t frame); +void effectSolid(SegmentView& view, const ParamValues& params, uint32_t frame, bool firstFrame); +void effectRainbow(SegmentView& view, const ParamValues& params, uint32_t frame, bool firstFrame); +void effectGradient(SegmentView& view, const ParamValues& params, uint32_t frame, bool firstFrame); // Animated effects -void effectFire(SegmentView& view, const EffectParams& params, uint32_t frame); -void effectFireUp(SegmentView& view, const EffectParams& params, uint32_t frame); -void effectConfetti(SegmentView& view, const EffectParams& params, uint32_t frame); -void effectColorWaves(SegmentView& view, const EffectParams& params, uint32_t frame); -void effectNoise(SegmentView& view, const EffectParams& params, uint32_t frame); +void effectFire(SegmentView& view, const ParamValues& params, uint32_t frame, bool firstFrame); +void effectFireUp(SegmentView& view, const ParamValues& params, uint32_t frame, bool firstFrame); +void effectConfetti(SegmentView& view, const ParamValues& params, uint32_t frame, bool firstFrame); +void effectColorWaves(SegmentView& view, const ParamValues& params, uint32_t frame, bool firstFrame); +void effectNoise(SegmentView& view, const ParamValues& params, uint32_t frame, bool firstFrame); // Pulse/breathing effects -void effectPulse(SegmentView& view, const EffectParams& params, uint32_t frame); -void effectBreathe(SegmentView& view, const EffectParams& params, uint32_t frame); -void effectCandle(SegmentView& view, const EffectParams& params, uint32_t frame); +void effectPulse(SegmentView& view, const ParamValues& params, uint32_t frame, bool firstFrame); +void effectBreathe(SegmentView& view, const ParamValues& params, uint32_t frame, bool firstFrame); +void effectCandle(SegmentView& view, const ParamValues& params, uint32_t frame, bool firstFrame); // Sparkle/twinkle effects -void effectSparkle(SegmentView& view, const EffectParams& params, uint32_t frame); -void effectTwinkle(SegmentView& view, const EffectParams& params, uint32_t frame); -void effectStrobe(SegmentView& view, const EffectParams& params, uint32_t frame); +void effectSparkle(SegmentView& view, const ParamValues& params, uint32_t frame, bool firstFrame); +void effectTwinkle(SegmentView& view, const ParamValues& params, uint32_t frame, bool firstFrame); +void effectStrobe(SegmentView& view, const ParamValues& params, uint32_t frame, bool firstFrame); // Moving effects -void effectMeteor(SegmentView& view, const EffectParams& params, uint32_t frame); -void effectComet(SegmentView& view, const EffectParams& params, uint32_t frame); -void effectScanner(SegmentView& view, const EffectParams& params, uint32_t frame); -void effectSinelon(SegmentView& view, const EffectParams& params, uint32_t frame); -void effectTheaterChase(SegmentView& view, const EffectParams& params, uint32_t frame); -void effectWave(SegmentView& view, const EffectParams& params, uint32_t frame); -void effectRain(SegmentView& view, const EffectParams& params, uint32_t frame); +void effectMeteor(SegmentView& view, const ParamValues& params, uint32_t frame, bool firstFrame); +void effectComet(SegmentView& view, const ParamValues& params, uint32_t frame, bool firstFrame); +void effectScanner(SegmentView& view, const ParamValues& params, uint32_t frame, bool firstFrame); +void effectSinelon(SegmentView& view, const ParamValues& params, uint32_t frame, bool firstFrame); +void effectTheaterChase(SegmentView& view, const ParamValues& params, uint32_t frame, bool firstFrame); +void effectWave(SegmentView& view, const ParamValues& params, uint32_t frame, bool firstFrame); +void effectRain(SegmentView& view, const ParamValues& params, uint32_t frame, bool firstFrame); // Special effects -void effectPride(SegmentView& view, const EffectParams& params, uint32_t frame); -void effectPacifica(SegmentView& view, const EffectParams& params, uint32_t frame); +void effectPride(SegmentView& view, const ParamValues& params, uint32_t frame, bool firstFrame); +void effectPacifica(SegmentView& view, const ParamValues& params, uint32_t frame, bool firstFrame); } // namespace lume From 70c85dd85b3bb12983aa605073e2203037b52d99 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 6 Jan 2026 02:59:54 +0000 Subject: [PATCH 4/6] Update documentation for schema-based effect system - Rewrote ADDING_EFFECTS.md to document new ParamSchema system - Removed references to legacy macros and EffectParams - Added complete examples for all parameter types - Documented scratchpad usage for stateful effects - Included migration guide from legacy system Co-authored-by: bring42 <63049750+bring42@users.noreply.github.com> --- docs/ADDING_EFFECTS.md | 364 ++++++++++++++++++++++++++++++----------- 1 file changed, 265 insertions(+), 99 deletions(-) diff --git a/docs/ADDING_EFFECTS.md b/docs/ADDING_EFFECTS.md index fafb0e7..e7c6fa8 100644 --- a/docs/ADDING_EFFECTS.md +++ b/docs/ADDING_EFFECTS.md @@ -9,199 +9,337 @@ Want to add a new effect or port one from WLED? Paste this prompt into GitHub Co Write a new LED effect for the LUME firmware. Use the following template and conventions: - The effect function signature must be: - void effectNAME(SegmentView& view, const EffectParams& params, uint32_t frame, bool firstFrame) -- Do not use static/global variables; use the segment scratchpad for state. -- Use only FastLED-compatible code and LUME's SegmentView API. -- Register the effect with the correct macro (see table below). + void effectNAME(SegmentView& view, const ParamValues& params, uint32_t frame, bool firstFrame) +- Define a schema with DEFINE_EFFECT_SCHEMA to describe parameters +- Use view.getScratchpad() for stateful effects (no static/global variables) +- Use only FastLED-compatible code and LUME's SegmentView API +- Register with REGISTER_EFFECT_SCHEMA macro - Example effect name: "fireup", "rainbowtwinkle", etc. -- If porting from WLED, convert millis() to frame, and replace global state with scratchpad. +- If porting from WLED, convert millis() to frame, and replace global state with scratchpad Example template: ```cpp -void effectMyEffect(SegmentView& view, const EffectParams& params, uint32_t frame, bool firstFrame) { - // Your effect code here +namespace myeffect { + constexpr uint8_t SPEED = 0; + constexpr uint8_t COLOR = 1; } -REGISTER_EFFECT_PALETTE(effectMyEffect, "myeffect", "My Effect"); + +DEFINE_EFFECT_SCHEMA(myeffectSchema, + ParamDesc::Int("speed", "Speed", 128, 1, 255), + ParamDesc::Color("color", "Color", CRGB::Blue) +); + +void effectMyEffect(SegmentView& view, const ParamValues& params, uint32_t frame, bool firstFrame) { + uint8_t speed = params.getInt(myeffect::SPEED); + CRGB color = params.getColor(myeffect::COLOR); + // Your effect code here +} + +REGISTER_EFFECT_SCHEMA(effectMyEffect, "myeffect", "My Effect", Animated, myeffectSchema, 0); ``` --- -See the rest of this file for effect registration macros and parameter details. +See the rest of this file for parameter types and complete examples. + # Adding New Effects Quick guide to creating custom LED effects for LUME. ## File Structure -Create a new file in `src/effects/youreffect.cpp`: +Create a new file in `src/visuallib/effects/youreffect.cpp`: ```cpp -#include "../core/effect_registry.h" +#include "../../core/effect_registry.h" +#include "../../core/param_schema.h" namespace lume { -void effectYourEffect(SegmentView& view, const EffectParams& params, +// Define parameter slot indices +namespace youreffect { + constexpr uint8_t SPEED = 0; + constexpr uint8_t COLOR = 1; +} + +// Define parameter schema +DEFINE_EFFECT_SCHEMA(youreffectSchema, + ParamDesc::Int("speed", "Speed", 128, 1, 255), + ParamDesc::Color("color", "Color", CRGB::Blue) +); + +void effectYourEffect(SegmentView& view, const ParamValues& params, uint32_t frame, bool firstFrame) { + // Access parameters by slot + uint8_t speed = params.getInt(youreffect::SPEED); + CRGB color = params.getColor(youreffect::COLOR); + // Your effect code here for (uint16_t i = 0; i < view.size(); i++) { - view[i] = CRGB::Blue; // Example: set all LEDs to blue + view[i] = color; } } -REGISTER_EFFECT_PALETTE(effectYourEffect, "youreffect", "Your Effect"); +// Register: function, id, name, category, schema, stateSize +REGISTER_EFFECT_SCHEMA(effectYourEffect, "youreffect", "Your Effect", Animated, youreffectSchema, 0); } // namespace lume ``` -## Registration Macros +## Parameter Types -Choose the macro that matches your effect's needs: +All effects now use `ParamSchema` to define parameters. Available types: -| Macro | Primary Color | Secondary Color | Palette | Speed | Intensity | Use Case | -|-------|---------------|-----------------|---------|-------|-----------|----------| -| `REGISTER_EFFECT_SOLID` | ✅ | ❌ | ❌ | ❌ | ❌ | Static color effects (no animation) | -| `REGISTER_EFFECT_SIMPLE_NAMED` | ❌ | ❌ | ❌ | ✅ | ❌ | Basic animated effects (speed only) | -| `REGISTER_EFFECT_PALETTE` | ❌ | ❌ | ✅ | ✅ | ❌ | Palette-based animations | -| `REGISTER_EFFECT_COLORS` | ✅ | ✅ | ❌ | ✅ | ❌ | Two-color effects (primary + secondary) | -| `REGISTER_EFFECT_ANIMATED` | ❌ | ❌ | ❌ | ✅ | ✅ | Animated with speed + intensity | -| `REGISTER_EFFECT_MOVING` | ❌ | ❌ | ❌ | ✅ | ✅ | Moving/traveling effects | +### Int (uint8_t slider, 0-255) +```cpp +ParamDesc::Int("speed", "Speed", 128, 1, 255) +// id name default min max +``` -**Important:** Match your registration to what parameters your effect actually uses! If your effect code uses `params.primaryColor`, you must set `usesPrimaryColor=true` in the registration (use `REGISTER_EFFECT_FULL` or an appropriate convenience macro). +### Float (float slider, 0.0-1.0) +```cpp +ParamDesc::Float("density", "Density", 0.5f, 0.0f, 1.0f) +``` -### Advanced Registration +### Color (RGB picker) +```cpp +ParamDesc::Color("color", "Color", CRGB::Red) +// id name default +``` -For full control, use `REGISTER_EFFECT_FULL`: +### Bool (toggle switch) +```cpp +ParamDesc::Bool("reversed", "Reversed", false) +``` +### Enum (dropdown) ```cpp -REGISTER_EFFECT_FULL( - effectCustom, // Function name - "custom", // ID (lowercase, no spaces) - "Custom Effect", // Display name - Animated, // Category: Solid, Animated, Moving, Special - true, // usesPalette - true, // usesPrimaryColor - true, // usesSecondaryColor - true, // usesSpeed - true, // usesIntensity - 0, // State size (0 if stateless) - 1 // Minimum LEDs -); +ParamDesc::Enum("direction", "Direction", "Up|Down|Left|Right", 0) +// id name options default_index ``` -## Available Parameters +### Palette (palette selector) +```cpp +ParamDesc::PaletteSelect("palette", "Palette") +``` + +## Effect Categories + +Choose the category that best describes your effect: + +- `Solid` - Static, non-animated effects +- `Animated` - Effects with motion/animation +- `Moving` - Effects with positional movement +- `Special` - Complex or unique effects + +## Accessing Parameters ```cpp -void effectYourEffect(SegmentView& view, const EffectParams& params, - uint32_t frame, bool firstFrame) { - - // Colors - CRGB primary = params.primaryColor; // Primary color - CRGB secondary = params.secondaryColor; // Secondary color - - // Animation controls - uint8_t speed = params.speed; // 1-200 (default: 100) - uint8_t intensity = params.intensity; // 0-255 (default: 128) +void effectExample(SegmentView& view, const ParamValues& params, + uint32_t frame, bool firstFrame) { - // Palette (if registered with palette support) - CRGBPalette16 palette = params.palette; + // Access by slot index (defined in namespace above) + uint8_t speed = params.getInt(example::SPEED); + float density = params.getFloat(example::DENSITY); + CRGB color = params.getColor(example::COLOR); + bool reversed = params.getBool(example::REVERSED); + uint8_t direction = params.getEnum(example::DIRECTION); + const CRGBPalette16& palette = params.getPalette(); // Timing - uint32_t currentFrame = frame; // Use for beatsin8(), etc. - bool isFirstFrame = firstFrame; // True on effect change + uint32_t currentFrame = frame; // Use for beatsin8(), etc. + bool isFirstFrame = firstFrame; // True on effect change // LED access uint16_t ledCount = view.size(); - view[0] = CRGB::Red; // Set individual LED - view.fill(CRGB::Blue); // Fill all LEDs - view.gradient(primary, secondary); // Create gradient + view[0] = CRGB::Red; // Set individual LED + view.fill(CRGB::Blue); // Fill all LEDs + view.gradient(color, CRGB::Red); // Create gradient } ``` -## Examples +## Stateful Effects (Using Scratchpad) -### Static Two-Color Gradient +For effects that need to remember state between frames: ```cpp -void effectGradient(SegmentView& view, const EffectParams& params, - uint32_t frame, bool firstFrame) { - view.gradient(params.primaryColor, params.secondaryColor); +// Define state structure +struct FireState { + uint8_t heat[600]; +}; + +void effectFire(SegmentView& view, const ParamValues& params, + uint32_t frame, bool firstFrame) { + // Access scratchpad + FireState* state = view.getScratchpad(); + if (!state) return; + + // Initialize on first frame + if (firstFrame) { + memset(state->heat, 0, sizeof(state->heat)); + } + + // Use state + state->heat[0] = 255; + // ... effect logic } -REGISTER_EFFECT_COLORS(effectGradient, "gradient", "Gradient"); +// Register with state size +REGISTER_EFFECT_SCHEMA(effectFire, "fire", "Fire", Animated, + fireSchema, sizeof(FireState)); ``` -### Animated Rainbow +## Complete Examples +### Simple Color Effect ```cpp -void effectRainbow(SegmentView& view, const EffectParams& params, - uint32_t frame, bool firstFrame) { - uint8_t hue = (frame * params.speed) / 100; - for (uint16_t i = 0; i < view.size(); i++) { - view[i] = CHSV(hue + (i * 255 / view.size()), 255, 255); - } +namespace solid { + constexpr uint8_t COLOR = 0; } -REGISTER_EFFECT_SIMPLE_NAMED(effectRainbow, "rainbow", "Rainbow"); -``` +DEFINE_EFFECT_SCHEMA(solidSchema, + ParamDesc::Color("color", "Color", CRGB::Red) +); + +void effectSolid(SegmentView& view, const ParamValues& params, + uint32_t frame, bool firstFrame) { + (void)frame; + (void)firstFrame; + + CRGB color = params.getColor(solid::COLOR); + view.fill(color); +} -### Palette-Based with Speed +REGISTER_EFFECT_SCHEMA(effectSolid, "solid", "Solid Color", Solid, solidSchema, 0); +``` +### Palette-Based Animation ```cpp -void effectFire(SegmentView& view, const EffectParams& params, - uint32_t frame, bool firstFrame) { - uint8_t cooling = params.intensity > 0 ? params.intensity : 55; +namespace colorwaves { + constexpr uint8_t SPEED = 0; +} + +DEFINE_EFFECT_SCHEMA(colorwavesSchema, + ParamDesc::PaletteSelect("palette", "Palette"), + ParamDesc::Int("speed", "Speed", 128, 1, 255) +); + +void effectColorWaves(SegmentView& view, const ParamValues& params, + uint32_t frame, bool firstFrame) { + (void)firstFrame; + + const CRGBPalette16& palette = params.getPalette(); + uint8_t speed = params.getInt(colorwaves::SPEED); + + uint16_t offset = (frame * speed) >> 6; for (uint16_t i = 0; i < view.size(); i++) { - uint8_t colorIndex = (frame + i * 10) % 255; - view[i] = ColorFromPalette(params.palette, colorIndex); + uint8_t colorIndex = (i * 256 / view.size()) + offset; + view[i] = ColorFromPalette(palette, colorIndex, 255, LINEARBLEND); } } -REGISTER_EFFECT_PALETTE(effectFire, "fire", "Fire"); +REGISTER_EFFECT_SCHEMA(effectColorWaves, "colorwaves", "Color Waves", + Animated, colorwavesSchema, 0); ``` -## Adding to Build +### Stateful Effect with Multiple Parameters +```cpp +namespace candle { + constexpr uint8_t COLOR = 0; + constexpr uint8_t SPEED = 1; + constexpr uint8_t INTENSITY = 2; +} + +DEFINE_EFFECT_SCHEMA(candleSchema, + ParamDesc::Color("color", "Color", CRGB(255, 140, 40)), + ParamDesc::Int("speed", "Flicker Speed", 128, 1, 255), + ParamDesc::Int("intensity", "Flicker Intensity", 128, 1, 255) +); -1. Create your effect file in `src/effects/` -2. Add to `src/effects/effects.h`: - ```cpp - #include "youreffect.cpp" - ``` -3. Compile: `pio run -t upload` +struct CandleState { + uint8_t base; + uint8_t target; + uint32_t lastFlickerChange; +}; + +void effectCandle(SegmentView& view, const ParamValues& params, + uint32_t frame, bool firstFrame) { + (void)frame; + + CandleState* state = view.getScratchpad(); + if (!state) return; + + CRGB baseColor = params.getColor(candle::COLOR); + uint8_t speed = params.getInt(candle::SPEED); + uint8_t intensity = params.getInt(candle::INTENSITY); + + if (firstFrame) { + state->base = 200; + state->target = 200; + state->lastFlickerChange = 0; + } + + // Flicker logic... + uint32_t now = millis(); + uint32_t flickerDelay = map(speed, 1, 255, 150, 10); + + if (now - state->lastFlickerChange > flickerDelay) { + state->lastFlickerChange = now; + state->target = random8(180, 255); + } + + if (state->base < state->target) state->base += 3; + if (state->base > state->target) state->base -= 5; + + for (uint16_t i = 0; i < view.size(); i++) { + CRGB color = baseColor; + color.nscale8(state->base); + view[i] = color; + } +} + +REGISTER_EFFECT_SCHEMA(effectCandle, "candle", "Candle", Animated, + candleSchema, sizeof(CandleState)); +``` ## UI Integration -The web UI automatically adapts based on your registration macro: +The web UI automatically generates controls based on your schema: -- Palette selector appears if `usesPalette = true` -- Speed slider hidden if `usesSpeed = false` -- Intensity slider appears if `usesIntensity = true` -- Secondary color picker shows if `usesSecondaryColor = true` +- **Int/Float parameters** → Sliders +- **Color parameters** → Color pickers +- **Bool parameters** → Toggle switches +- **Enum parameters** → Dropdown menus +- **Palette parameter** → Palette selector -**No frontend changes needed!** The metadata drives the UI. +**No frontend changes needed!** The schema drives the UI. ## Best Practices ✅ **DO:** -- Use `frame` for timing (not `millis()`) +- Use `frame` for timing (not `millis()` for animations) - Keep effects deterministic (same inputs = same output) -- Use `firstFrame` to initialize state +- Use `firstFrame` to initialize scratchpad state +- Use slot constants (e.g., `effect::SPEED`) for readability - Test with different LED counts +- Use `view.getScratchpad()` for stateful effects ❌ **DON'T:** -- Use global variables or `static` state +- Use global variables or `static` state (breaks multi-segment) - Call `delay()` or blocking functions - Assume a specific LED count -- Use `millis()` for animation timing +- Exceed 512 bytes in state structure +- Mix up parameter slot indices ## Debugging Enable logging in your effect: ```cpp -#include "../logging.h" +#include "../../logging.h" -void effectDebug(SegmentView& view, const EffectParams& params, +void effectDebug(SegmentView& view, const ParamValues& params, uint32_t frame, bool firstFrame) { if (firstFrame) { LOG_INFO(LogTag::LED, "Effect started, %d LEDs", view.size()); @@ -211,3 +349,31 @@ void effectDebug(SegmentView& view, const EffectParams& params, ``` Monitor output: `pio device monitor` + +## Migration from Legacy Effects + +If updating an old effect that used `EffectParams`: + +**Before:** +```cpp +void effectOld(SegmentView& view, const EffectParams& params, + const ParamValues& paramValues, uint32_t frame, bool firstFrame) { + CRGB color = paramValues.getColor(0); + view.fill(color); +} +``` + +**After:** +```cpp +void effectNew(SegmentView& view, const ParamValues& params, + uint32_t frame, bool firstFrame) { + CRGB color = params.getColor(0); + view.fill(color); +} +``` + +Changes: +1. Remove `const EffectParams& params` parameter +2. Rename `paramValues` to `params` +3. Palette accessed via `params.getPalette()` instead of `params.palette` +4. Update static state to use `view.getScratchpad()` From 6c9b9d404977619ac5269e08e585f47a42576577 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 6 Jan 2026 03:18:28 +0000 Subject: [PATCH 5/6] Fix compilation errors in effects_handler.cpp and server.cpp - Removed legacy fallback code in effects_handler.cpp that accessed removed member variables - Updated server.cpp to use hasParam() and usesPalette() methods instead of direct member access - All effects now have schemas, so legacy code paths are no longer needed Co-authored-by: bring42 <63049750+bring42@users.noreply.github.com> --- src/api/effects_handler.cpp | 44 +------------------------------------ src/network/server.cpp | 6 ++--- 2 files changed, 4 insertions(+), 46 deletions(-) diff --git a/src/api/effects_handler.cpp b/src/api/effects_handler.cpp index f782799..86ef6a6 100644 --- a/src/api/effects_handler.cpp +++ b/src/api/effects_handler.cpp @@ -77,52 +77,10 @@ void handleGetEffects(AsyncWebServerRequest* request) { effect["name"] = info->displayName; effect["category"] = info->categoryName(); - // Include schema if available + // All effects now have schemas - serialize params from schema JsonArray params = effect["params"]. to(); if (info->hasSchema()) { schemaToJson(params, info->schema); - } else { - // Legacy: generate params from flags - if (info->usesSpeed) { - JsonObject p = params.add(); - p["id"] = "speed"; - p["name"] = "Speed"; - p["type"] = "int"; - p["min"] = 0; - p["max"] = 255; - p["default"] = 128; - } - if (info->usesIntensity) { - JsonObject p = params.add(); - p["id"] = "intensity"; - p["name"] = "Intensity"; - p["type"] = "int"; - p["min"] = 0; - p["max"] = 255; - p["default"] = 128; - } - // Generate color params based on colorCount - for (uint8_t c = 0; c < info->colorCount; c++) { - JsonObject p = params.add(); - if (c == 0) { - p["id"] = "color"; - p["name"] = "Color"; - } else { - char idBuf[16], nameBuf[16]; - snprintf(idBuf, sizeof(idBuf), "color%d", c); - snprintf(nameBuf, sizeof(nameBuf), "Color %d", c + 1); - p["id"] = idBuf; - p["name"] = nameBuf; - } - p["type"] = "color"; - p["default"] = "#ff0000"; - } - if (info->usesPalette) { - JsonObject p = params.add(); - p["id"] = "palette"; - p["name"] = "Palette"; - p["type"] = "palette"; - } } } diff --git a/src/network/server.cpp b/src/network/server.cpp index 28bd77a..c04485a 100644 --- a/src/network/server.cpp +++ b/src/network/server.cpp @@ -327,9 +327,9 @@ void setupServer() { effObj["id"] = info->id; effObj["name"] = info->displayName; effObj["category"] = info->categoryName(); // Use helper method - effObj["usesSpeed"] = info->usesSpeed; - effObj["usesIntensity"] = info->usesIntensity; - effObj["usesPalette"] = info->usesPalette; + effObj["usesSpeed"] = info->hasParam("speed"); + effObj["usesIntensity"] = info->hasParam("intensity"); + effObj["usesPalette"] = info->usesPalette(); } String response; From fc15083d14bb3b0be3c27bfc69c71c432a1c57bc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 6 Jan 2026 03:29:54 +0000 Subject: [PATCH 6/6] Fix pacifica.cpp parameter name Changed paramValues.getInt() to params.getInt() to match the function signature Co-authored-by: bring42 <63049750+bring42@users.noreply.github.com> --- src/visuallib/effects/pacifica.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/visuallib/effects/pacifica.cpp b/src/visuallib/effects/pacifica.cpp index dc99d2c..968a2e3 100644 --- a/src/visuallib/effects/pacifica.cpp +++ b/src/visuallib/effects/pacifica.cpp @@ -42,7 +42,7 @@ DEFINE_GRADIENT_PALETTE(pacifica3) { void effectPacifica(SegmentView& view, const ParamValues& params, uint32_t frame, bool firstFrame) { (void)firstFrame; - uint8_t speed = paramValues.getInt(pacifica::SPEED); + uint8_t speed = params.getInt(pacifica::SPEED); uint16_t len = view.size();