Skip to content

Full fastled replacement#4615

Open
DedeHai wants to merge 32 commits intowled:mainfrom
DedeHai:full_fastled_replacement
Open

Full fastled replacement#4615
DedeHai wants to merge 32 commits intowled:mainfrom
DedeHai:full_fastled_replacement

Conversation

@DedeHai
Copy link
Collaborator

@DedeHai DedeHai commented Mar 26, 2025

Completely removed dependency on fastled library for better color control and conversions. This also prevents accidentally pulling in fastled functions that were previously replaced (random() for example).

  • Copied all required functions and structs from fastled
  • Optimized most of them for WLED use, many color manipulations in FX can now be replaced with native 32bit colors, potentially speeding things up with less color conversions
  • Increased accuracy for some functions by scaling the fixedpoint math making use of native 32bit variables.
  • Added a PRNG class for FX that rely on repatable random numbers
  • Updated FX to use the replacement functions where required

Summary by CodeRabbit

  • New Features

    • Four new color palettes: Cloud, Lava, Ocean, Forest
    • Mid-frequency audio analysis added to a particle effect
  • Improvements

    • More accurate and consistent color rendering across many effects
    • Improved, more predictable randomness for visual effects and restores
    • Streamlined palette handling for better visual fidelity
  • Refactor

    • Internal color system consolidated for lighter-weight color operations

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 26, 2025

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

This PR removes the FastLED dependency and introduces a lightweight FastLED-slim implementation, refactors color and palette APIs to CRGB/CRGBW/CHSV types and spectrum-aware conversions, adds a PRNG and beat helpers, and updates FX, particle, IR, utils, and several usermods to use the new color/prng infrastructure.

Changes

Cohort / File(s) Summary
PlatformIO config
platformio.ini
Removed fastled/FastLED @ 3.6.0 from lib_deps and lib_deps_compat.
FastLED-slim dependency
wled00/src/dependencies/fastled_slim/...
Adds fastled_slim.h/.cpp with color/palette/easing primitives and MIT LICENSE.txt.
Core color library
wled00/colors.h, wled00/colors.cpp
Replace FastLED APIs with CRGBW/CHSV32-aware conversions (hsv2rgb_spectrum, rgb2hsv), introduce in-place adjust_color(CRGBW&,...), rename palette API to ColorFromPalette.
FX core & helpers
wled00/FX.h, wled00/FX.cpp, wled00/FX_fcn.cpp, wled00/FXparticleSystem.cpp
Introduce colors.h/prng.h; convert many FX paths to CRGBW, add PRNG instance, change randomness calls, adjust math (easing/quantization), and update several method signatures (blur helpers and 2D/XY accessors).
Palettes & defaults
wled00/palettes.cpp, wled00/FX_fcn.cpp, wled00/json.cpp
Add built-in palettes (Cloud/Lava/Ocean/Forest), change some defaults to PartyColors_gc22, update PROGMEM usage and color_wheel to CRGBW path.
FastLED-slim API header
wled00/src/dependencies/fastled_slim/fastled_slim.h
Adds CHSV/CRGB structs, CRGBPalette16, types and prototypes for palette/HSV/RGB helpers and waveform/easing utilities.
PRNG & randomness
wled00/prng.h, wled00/wled.cpp, various FX/usermods
Add PRNG class; replace some random* uses with prng.random*() or hw_random*(); remove FastLED RNG seeding in setup.
Beat & util helpers
wled00/fcn_declare.h, wled00/util.cpp
Add beat88/beat16/beat8, adapt beatsin*_t signatures to uint16_t BPMs and update implementations.
Color/HSV usage in subsystems
wled00/ir.cpp, wled00/FXparticleSystem.cpp, wled00/colors.*
Refactor IR, particle and FX color paths to use CRGBW/CHSV32 and hsv2rgb_spectrum/adjust_color, remove some old RGB↔HSV roundtrips and gamma calls.
Usermods
usermods/...
Minor API/type tweaks: scale32 param change, replace rgb2hsv_approximatergb2hsv, remove hsv2rgb_rainbow conversions, ColorFromPaletteWLEDColorFromPalette, add helper in rotary encoder, update Animartrix pinned commit.
Headers & macros
wled00/wled.h, wled00/const.h, wled00/fcn_declare.h
Remove FastLED.h and RGBW packing macros, add colors.h, add WLED_O3_ATTR macro and move/add beat declarations.
Misc small changes
wled00/wled_math.cpp, various new files
Whitespace/newline adjustments and many new/added dependency files.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Suggested reviewers

  • netmindz
  • willmmiles
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 60.16% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Full fastled replacement' directly and clearly summarizes the main change: replacing the FastLED library dependency with internal implementations.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

coderabbitai[bot]

This comment was marked as resolved.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (8)
wled00/FX.cpp (2)

72-73: Global PRNG usage.
Declaring PRNG as a global instance is consistent with the new random approach. However, confirm whether concurrency or reproducibility concerns require a more localized scope or synchronization.


4923-4923: Commented-out call to setPixelColorXY.
If this line is no longer needed, consider removing it to avoid confusion.

wled00/colors.h (3)

96-147: Consider initializing the default CHSV constructor.
CHSV warns the default constructor leaves fields uninitialized, which can produce undefined hue/sat/value if not set. If unintentional, consider a safe default.

- inline CHSV() __attribute__((always_inline)) = default;
+ inline CHSV() __attribute__((always_inline)) : h(0), s(0), v(0) { }

218-314: Extensive operator coverage.
Multiple arithmetic operators on CRGB significantly improve usability. However, they can be numerous for embedded environments. Ensure all these operators are covered by unit tests to prevent regressions.

Would you like help drafting unit tests for these operators?


811-899: Inconsistent conversion methods (rainbow vs. spectrum).
CRGBW(CHSV32) and CRGBW(CHSV) use hsv2rgb_rainbow, whereas adjust_hue() calls hsv2rgb_spectrum. Verify this is intended, as "rainbow" and "spectrum" produce visually different hue mappings.

Consider unifying or clarifying these behaviors to avoid unexpected color shifts.

wled00/fcn_declare.h (3)

373-374: Legacy alias check.
#define inoise8 perlin8 and #define inoise16 perlin16 help maintain compatibility with old FastLED function calls. Confirm these aliases are still needed long-term.


411-419: Perlin noise API.
The new perlin noise functions (perlin1D_raw, perlin2D_raw, perlin3D_raw, perlin16, perlin8) significantly expand effect possibilities. They look correct at a glance; consider thorough performance testing if used intensively in tight loops.


522-544: Pseudo-Random Number Generator class.
PRNG helps replicate old FastLED behavior. It is not thread-safe, but typically that’s acceptable in single-thread microcontroller contexts. If concurrency is planned, consider synchronization.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2c69c5e and 6894f35.

📒 Files selected for processing (3)
  • wled00/FX.cpp (22 hunks)
  • wled00/colors.h (1 hunks)
  • wled00/fcn_declare.h (6 hunks)
🧰 Additional context used
🧬 Code Definitions (2)
wled00/colors.h (1)
wled00/colors.cpp (18)
  • color_blend (20-30)
  • color_blend (20-20)
  • fill_solid_RGB (732-736)
  • fill_solid_RGB (732-732)
  • fill_gradient_RGB (739-769)
  • fill_gradient_RGB (739-739)
  • fill_gradient_RGB (771-774)
  • fill_gradient_RGB (771-771)
  • fill_gradient_RGB (776-781)
  • fill_gradient_RGB (776-776)
  • fill_gradient_RGB (783-790)
  • fill_gradient_RGB (783-783)
  • hsv2rgb_spectrum (254-254)
  • hsv2rgb_spectrum (303-308)
  • hsv2rgb_spectrum (303-303)
  • rgb2hsv (418-418)
  • rgb2hsv (440-444)
  • rgb2hsv (440-440)
wled00/FX.cpp (3)
wled00/colors.h (8)
  • color (55-55)
  • color (872-872)
  • color (872-872)
  • r (405-409)
  • r (421-423)
  • r (896-898)
  • color_fade (66-66)
  • color_blend (63-63)
wled00/FX.h (40)
  • i (613-613)
  • i (613-613)
  • i (661-661)
  • i (666-666)
  • i (667-667)
  • i (667-667)
  • i (668-668)
  • i (668-668)
  • i (673-673)
  • i (674-674)
  • i (935-935)
  • r (679-679)
  • b (627-627)
  • c (678-678)
  • c (745-745)
  • c (745-745)
  • c (882-882)
  • c (882-882)
  • col (769-769)
  • col (769-769)
  • t (879-879)
  • t (879-879)
  • t (885-885)
  • t (885-885)
  • n (588-588)
  • n (588-588)
  • n (623-623)
  • n (662-662)
  • n (662-662)
  • n (663-663)
  • n (663-663)
  • n (664-664)
  • n (664-664)
  • n (682-682)
  • n (682-682)
  • n (683-683)
  • n (683-683)
  • n (684-684)
  • n (684-684)
  • n (685-685)
wled00/wled_math.cpp (4)
  • sin8_t (82-86)
  • sin8_t (82-82)
  • cos8_t (88-90)
  • cos8_t (88-88)
🔇 Additional comments (28)
wled00/FX.cpp (18)

16-16: Include colors.h is aligned with the new color management.
No immediate issues with this import, and it supports the PR objective of replacing FastLED.


1760-1783: Re-seeding PRNG for effect consistency.
Storing the old seed in prevSeed and restoring it after rendering ensures predictable random output for this effect. Verify that unsigned has sufficient bits for the seed (consider uint32_t instead) if prng.getSeed() returns 32 bits.


2546-2546: New twinklefox_one_twinkle function.
The function name and parameters are clear for a special twinkle effect. No immediate concerns.


2581-2581: Setting up local CRGBW.
Straightforward variable declaration; no issues noticed with memory or usage.


2596-2596: Assigning black color.
Setting c = 0 is a valid approach to clear color. Looks fine.


2619-2626: Dynamically fading background color.
Logic for scaling based on bglight is consistent. No apparent issues with color_fade usage.


4147-4157: Seed temporarily set to 535.
This ensures repeatable randomness in mode_twinkleup. The final prng.setSeed(prevSeed) properly restores the original seed. No immediate concerns.


4179-4179: Mix of hardware and pseudo-random.
Using hw_random8(64) differs from the new PRNG approach. If reproducibility is needed, consider the pinned PRNG. Otherwise, this is fine for true randomness.


5698-5698: Perlin calculation.
Shifting the noise value by 2 to produce a ~[-32, 32] range is valid. No issues found.


5825-5827: Target coordinates use prng.random8(0, w) and prng.random8(0, h).
Ensure w and h stay within 255 if the distribution must span the entire range. Otherwise, re-check logic for larger dimensions.


5840-5843: Seeding PRNG with strip.now.
Using the current time as a seed repeats the approach seen elsewhere, ensuring variation. No immediate issues.


6014-6014: Incrementing color by 4.
Steady color shifting is an effective approach for subtle hue changes. No concerns.


7510-7510: Blending with nscale8 and ease8InOutCubic.
This is standard functionality to produce smooth transitions. Looks correct.


7819-7819: Hybrid approach with hw_random16(SEGMENT.custom1 >> 3).
Allows a random range for vertical velocity. The usage is consistent with the new 2D particle code.


7863-7863: Random explosion shape.
Leveraging hw_random() & 1 is a neat trick for a 50% chance of a circular explosion. No issues spotted.


7889-7889: [Duplicate of previous comment regarding clipped saturation]
If the intention is a random saturation up to 150, min((uint16_t)150, hw_random16()) nearly always becomes 150 because hw_random16() can be up to 65535. Use a narrower range to avoid clipping.


9724-9724: Gravity offset arithmetic.
(int16_t)cos8_t(...) - 128 properly centers gravity around 0. No immediate issues.


10051-10051: Calculating mid-frequency loudness.
Summation followed by sqrt32_bw is straightforward. Implementation looks fine.

wled00/colors.h (5)

6-11: Confirm correct macro usage for 32-bit color extraction.
These macros for packing and unpacking 32-bit color components appear logically sound. The casts to byte eliminate sign-extension risks.


51-60: Validate Gamma Table Bounds.
gammaT[] is declared but not shown here. Ensure that read/write operations in NeoGammaWLEDMethod do not exceed gammaT's size.

Please confirm initialization of gammaT[] within this repository, verifying no out-of-bounds usage.


149-217: Comprehensive RGB representation looks good.
The CRGB struct covers typical color operations (add, sub, fade, scale) with 8-bit saturation. The saturating functions (qadd8, qsub8) are widely used and well-tested.


595-612: Palette constructor usage.
Using memset and memmove for palette initialization is correct for contiguous color arrays. Confirm large index usage in gradient fills doesn't exceed the buffer.


63-71:

Details

✅ Verification successful

Check blend and fade function usage consistency.
The declarations color_blend, color_add, and color_fade look fine. Verify all call sites have updated parameters (e.g., preserveCR) to avoid mismatches.

Use a script to locate invocations of these functions and confirm parameter alignment:


🏁 Script executed:

#!/bin/bash
rg -A3 -B3 "color_(blend|add|fade)\("

Length of output: 50298


Color Operation Parameter Usage is Consistent
After reviewing the call sites with the verification script, all invocations of the blending and fading functions conform to the updated signatures:

  • color_blend: Every call uses the expected three parameters (with the blend factor explicitly cast as a uint8_t), and usages in helper methods (e.g., in FX.h) are consistent.
  • color_fade: Invocations correctly use either two parameters (defaulting to video = false) or three parameters (explicitly passing true when a video fade is required).
  • color_add: While the header declares a default of false for the preserveCR parameter, the inline helper functions in FX.h override this default to true. Direct calls (e.g., in FX.cpp) intentionally omit the parameter to use the default value. This dual usage appears deliberate and consistent across the codebase.

No mismatches were detected, so please double-check that the intentional differences (particularly for color_add’s default behavior) meet your design goals.

wled00/fcn_declare.h (5)

5-6: Include for color definitions.
#include "colors.h" properly centralizes color-related utilities. This is consistent with the removal of FastLED references. No further issues noted.


395-405: New beat and waveform functions.
These function signatures (beat88, beat16, beat8, beatsinXX_t, etc.) look correct for music-driven or timed effects. Confirm any leftover references to accum88 in comments or code are no longer used.


463-465: Approximated trigonometric macros.
Replacing standard trig calls with sin_approx, cos_approx, tan_approx is valid for microcontrollers. Keep an eye on numerical accuracy in animations or advanced calculations.


483-486: Ease functions for smoother transitions.
ease8InOutCubic, ease16InOutCubic, ease8InOutQuad are helpful for smooth animations. Check for boundary conditions on extreme inputs (0 and max) to ensure no overflow.


487-499: Inline saturating math.
scale8, qadd8, qsub8, qmul8 match typical FastLED logic for 8-bit arithmetic. Confirm zero or negative use cases behave as intended.

@DedeHai
Copy link
Collaborator Author

DedeHai commented Mar 27, 2025

@willmmiles can you please review this? I had some issues with circular includes and I am not sure I did the includes of header in a good way.
To anyone taking a look at this:

  • feel free to examine the functions but generally focus on code structure/includes and if the way I split function declares in headers makes sense
  • I tested and speed checked all relevant core functions and spent a lot of effort making them at least as fast as they were and ensured colors are correctly converted (hsv<->rgb mostly)
  • I did not confirm all CRGB constructors/assignments do work but they are (or should be) straight copies. the ones I modified, I checked
  • any bugs that are in color functions should be minor and can be worked out as they are discovered

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (6)
wled00/util.cpp (2)

381-386: The beat88 implementation looks correct

The function properly implements a 16-bit sawtooth wave generator based on BPM in Q8.8 fixed-point format. The constant 280 is a scaling factor for the computation.

Consider adding a brief comment explaining the magic number 280 for better maintainability:

uint16_t beat88(uint16_t beats_per_minute_88, uint32_t timebase) {
-  return ((millis() - timebase) * beats_per_minute_88 * 280) >> 16;
+  // 280 is a scaling factor: 65536(2^16) / 60000(ms per minute) ≈ 1.09, multiplied by 256 ≈ 280
+  return ((millis() - timebase) * beats_per_minute_88 * 280) >> 16;
}

673-680: Removed inline attribute from gradient1D

The inline attribute has been removed from the gradient1D function. This allows the compiler to make its own decision about inlining, which can lead to better optimization in some cases.

The function maintains consistent behavior and the algorithm is unchanged, but the compiler now has more flexibility to decide whether to inline it. Since this function is used in performance-critical Perlin noise calculations, it might be worth benchmarking with and without the inline attribute to ensure optimal performance.

wled00/FX.cpp (4)

2596-2596: Prefer explicit zeroing.
Assigning c = 0; sets all channels to zero for a CRGBW. For clarity, consider c = CRGBW(0,0,0,0), although this is optional.

-    c = 0; // black
+    c = CRGBW(0, 0, 0, 0); // black

4179-4179: Remove or clarify commented-out line.
You’ve duplicated the palette assignment line, with one commented. If the commented line is no longer needed, please remove it to keep the codebase clean.


4923-4923: Eliminate stale commented-out code.
This line is commented out and may cause confusion. If you prefer a different function call, consider removing or refactoring the line instead of leaving it commented.


6014-6014: Potential color overflow on addition.
If blob->color[i] is an 8-bit channel representation, adding 4 repeatedly can overflow. If wrapping is desired, this is fine. Otherwise, consider clamping.

-    blob->color[i] = blob->color[i] + 4; // slowly change color
+    blob->color[i] = min<uint8_t>(255, blob->color[i] + 4); // slowly change color, clamped
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6894f35 and e353448.

📒 Files selected for processing (3)
  • wled00/FX.cpp (22 hunks)
  • wled00/util.cpp (2 hunks)
  • wled00/wled.cpp (0 hunks)
💤 Files with no reviewable changes (1)
  • wled00/wled.cpp
🧰 Additional context used
🧬 Code Definitions (2)
wled00/util.cpp (2)
wled00/fcn_declare.h (10)
  • beat88 (395-395)
  • beat16 (396-396)
  • sin16_t (469-469)
  • scale16 (494-494)
  • beat8 (397-397)
  • sin8_t (471-471)
  • scale8 (488-488)
  • ease8InOutQuad (485-485)
  • triwave8 (401-401)
  • ease8InOutCubic (483-483)
wled00/wled_math.cpp (8)
  • sin16_t (65-76)
  • sin16_t (65-65)
  • sin8_t (82-86)
  • sin8_t (82-82)
  • ease8InOutQuad (263-269)
  • ease8InOutQuad (263-263)
  • ease8InOutCubic (250-254)
  • ease8InOutCubic (250-250)
wled00/FX.cpp (4)
wled00/colors.h (8)
  • color (55-55)
  • color (872-872)
  • color (872-872)
  • r (405-409)
  • r (421-423)
  • r (896-898)
  • color_fade (66-66)
  • color_blend (63-63)
wled00/FX.h (40)
  • i (613-613)
  • i (613-613)
  • i (661-661)
  • i (666-666)
  • i (667-667)
  • i (667-667)
  • i (668-668)
  • i (668-668)
  • i (673-673)
  • i (674-674)
  • i (935-935)
  • r (679-679)
  • b (627-627)
  • c (678-678)
  • c (745-745)
  • c (745-745)
  • c (882-882)
  • c (882-882)
  • col (769-769)
  • col (769-769)
  • t (879-879)
  • t (879-879)
  • t (885-885)
  • t (885-885)
  • n (588-588)
  • n (588-588)
  • n (623-623)
  • n (662-662)
  • n (662-662)
  • n (663-663)
  • n (663-663)
  • n (664-664)
  • n (664-664)
  • n (682-682)
  • n (682-682)
  • n (683-683)
  • n (683-683)
  • n (684-684)
  • n (684-684)
  • n (685-685)
wled00/wled_math.cpp (4)
  • sin8_t (82-86)
  • sin8_t (82-82)
  • cos8_t (88-90)
  • cos8_t (88-88)
wled00/util.cpp (4)
  • hw_random (643-647)
  • hw_random (643-643)
  • hw_random (649-655)
  • hw_random (649-649)
⏰ Context from checks skipped due to timeout of 90000ms (19)
  • GitHub Check: wled_build / Build Environments (esp32s3_4M_qspi)
  • GitHub Check: wled_build / Build Environments (esp32s3dev_8MB_opi)
  • GitHub Check: wled_build / Build Environments (usermods)
  • GitHub Check: wled_build / Build Environments (esp32s3dev_16MB_opi)
  • GitHub Check: wled_build / Build Environments (esp32c3dev)
  • GitHub Check: wled_build / Build Environments (lolin_s2_mini)
  • GitHub Check: wled_build / Build Environments (esp8266_2m_compat)
  • GitHub Check: wled_build / Build Environments (esp01_1m_full_160)
  • GitHub Check: wled_build / Build Environments (esp01_1m_full_compat)
  • GitHub Check: wled_build / Build Environments (esp32dev_V4)
  • GitHub Check: wled_build / Build Environments (esp32_wrover)
  • GitHub Check: wled_build / Build Environments (esp32_eth)
  • GitHub Check: wled_build / Build Environments (esp8266_2m)
  • GitHub Check: wled_build / Build Environments (esp32dev)
  • GitHub Check: wled_build / Build Environments (nodemcuv2_160)
  • GitHub Check: wled_build / Build Environments (nodemcuv2)
  • GitHub Check: wled_build / Build Environments (esp01_1m_full)
  • GitHub Check: wled_build / Build Environments (nodemcuv2_compat)
  • GitHub Check: wled_build / Build Environments (esp8266_2m_160)
🔇 Additional comments (28)
wled00/util.cpp (10)

379-380: Well-organized FastLED replacement section

Good organization with a clear comment indicating the purpose of the following functions.


388-392: Clean wrapper function for 16-bit beat generation

This implementation correctly handles the BPM conversion from regular integer to Q8.8 format when needed.


394-397: Good use of existing beat16 function for beat8

The implementation reuses the beat16 function and properly shifts the result to get an 8-bit value.


410-417: Clean implementation of beatsin16_t

The function properly implements a 16-bit sine wave that oscillates within a given range, using the beat16 function.


420-427: Good 8-bit implementation of beatsin function

The function correctly implements an 8-bit sine wave that oscillates within a given range, using appropriate scaling for 8-bit values.


429-433: Efficient triangular wave implementation

The triwave8 function efficiently implements a triangular wave by mirroring values above 128 and then doubling the amplitude with a bit shift.


440-442: Good quadratic waveform implementation

The function correctly applies a quadratic easing function to a triangular wave, creating a quadratic waveform that spends more time at the limits.


443-446: Good cubic waveform implementation

The function correctly applies a cubic easing function to a triangular wave, creating a cubic waveform with even more pronounced dwell time at the limits compared to the quadratic waveform.


399-407:

Details

✅ Verification successful

Verify beatsin88_t parameter type change

The function signature has been updated to use uint16_t for BPM instead of accum88 (FastLED type). The implementation correctly uses the new beat88 function for generating the sine wave within the specified range.


🏁 Script executed:

#!/bin/bash
# Check if there are any remaining references to accum88 type in the codebase
rg -i "accum88" --type cpp

Length of output: 26


Parameter Type Change Verified
The update to use uint16_t for the BPM parameter in beatsin88_t has been successfully implemented. The search for accum88 across the codebase returned no results, indicating that no remnants of the old type remain. Please verify manually if needed, but the evidence confirms the intended changes.


435-438:

Details

✅ Verification successful

Verify 16-bit triangular wave implementation

The 16-bit implementation uses a slightly different approach from the 8-bit version, with direct comparison to 0x8000 and different calculation for the second half of the waveform.

Check if the different implementations of triwave8 and triwave16 produce consistent results when scaled:


🏁 Script executed:

#!/bin/bash
# Generate a simple C++ test file to check consistency between triwave8 and triwave16
cat > /tmp/triwave_test.cpp << 'EOF'
#include <cstdint>
#include <iostream>

uint8_t triwave8(uint8_t in) {
  if (in & 0x80) in = 255 - in;
  return in << 1;
}

uint16_t triwave16(uint16_t in) {
  if (in < 0x8000) return in * 2;
  return 0xFFFF - (in - 0x8000) * 2;
}

int main() {
  // Test a few values across the range
  uint8_t test_values[] = {0, 64, 127, 128, 191, 255};
  
  std::cout << "Testing triwave8 and triwave16 consistency:\n";
  std::cout << "input\ttriwave8\ttriwave16 (scaled)\n";
  for (auto val : test_values) {
    uint8_t tw8 = triwave8(val);
    // Scale input to 16-bit equivalent
    uint16_t scaled_input = val * 257;  // 257 = 65535/255 rounded up
    uint16_t tw16 = triwave16(scaled_input);
    // Scale triwave16 result back to 8-bit for comparison
    uint8_t tw16_scaled = tw16 >> 8;
    
    std::cout << (int)val << "\t" << (int)tw8 << "\t" << (int)tw16_scaled;
    if (tw8 != tw16_scaled) std::cout << " (difference)";
    std::cout << "\n";
  }
  return 0;
}
EOF

echo "Test file created. In a real environment, this would be compiled and run to verify implementation consistency."

Length of output: 236


Code Logic Verified: 16-bit and 8-bit Waveform Functions Are Consistent

After reviewing the implementation in wled00/util.cpp and inspecting the generated C++ test file, the manual evaluation confirms that the 16-bit triwave16 function correctly mirrors the behavior of the 8-bit triwave8 when inputs are appropriately scaled. The chosen boundary (0x8000) and the computation in the second half of the waveform are consistent with the intended triangular wave calculations. The test file provided is well structured for further runtime verification if needed, but based on our manual analysis no discrepancies were found.

wled00/FX.cpp (18)

72-72: Global pseudo-random instance initialization.
Declaring a global PRNG prng can simplify random usage across effects. However, this may limit local scoping or seed control. If global usage is intended, no issues here. Otherwise, consider scoping it to relevant classes.


1760-1783: Reproducible PRNG usage per effect iteration.
This code snippet smartly saves and restores the PRNG seed, ensuring that other effects remain unaffected by local random calls. The approach is sound for deterministic color shifts in each cycle. Just confirm that this is the desired behavior and that no concurrency constraints prevent re-seeding in this manner.


2271-2272: [Duplicate from past feedback] Abrupt fade logic.
Using significantly different fade amounts based on brightness (e.g., 68 - strip.getBrightness()) can cause noticeable jumps in fade behavior. If abruptness is unintentional, consider smoother transitions.


2546-2546: Local static function usage.
Defining twinklefox_one_twinkle as static is a good way to limit linkage. If you plan to reuse it elsewhere, consider moving it into a relevant namespace or header.


2581-2581: No issues with CRGBW variable instantiation.
Declaring CRGBW c; here is straightforward. No concerns.


2619-2627: Useful conditional fade logic.
The choice to fade the background color differently for each brightness range gives a dynamic feel. Ensure that these thresholds (64 and 16) match actual usage requirements. Looks fine overall.


2644-2655: [Duplicate from past feedback] Potential overflow in color blending.
Multiplying deltabright by 8 may exceed 255 if deltabright can reach values >31, causing wrap-around. If required, clamp or scale appropriately to avoid unexpected results.


4147-4158: Fixed PRNG seed each pass.
Resetting the PRNG to a constant seed (535) ensures consistent twinkle patterns every loop, which may be intentional. If true randomness is desired, consider using a time-varying or hardware-based seed.


5698-5698: Ensure sign-extension and range are correct.
Casting to int16_t, subtracting 127, then shifting right by 2 produces negative values for smaller inputs. If that’s intentional, no problem. Otherwise, confirm you aren’t inadvertently going below zero.


5825-5827: Check random8(0, w/h) boundary assumptions.
prng.random8(0, w) yields values in [0, w). Confirm that this range won’t exceed array or pixel boundaries for posX/posY = w or h.


5840-5843: Seeding PRNG with strip.now.
Using strip.now to seed the PRNG triggers predictable patterns if now increments uniformly each iteration. This might be desired for repeatability or might reduce randomness. Confirm your intention.


7510-7510: Potential color addition overflow.
Combining scaled PixelA and PixelB may exceed 255 in each channel if it doesn't automatically saturate. Confirm the underlying addition saturates or clamp if needed.


7819-7819: Check range of computations for rocket speed.
(SEGMENT.custom3) + hw_random16(SEGMENT.custom1 >> 3) + 5 might become large if custom3 or SEGMENT.custom1 is high. Ensure this speed remains valid for your effect.


7863-7863: 50% chance approach.
if (hw_random() & 1) is a straightforward way to pick a random bit. This is correct if you truly want a 50/50 branching.


7889-7889: [Duplicate from past feedback] min((uint16_t)150, hw_random16()) often yields 150.
If your aim is to randomize saturation up to 150, use something like hw_random16(151) instead.


9724-9724: Valid negative/positive range for gravity.
cos8_t() returns 0–255, so subtracting 128 yields -128–127, which could be valid if negative gravity is intended. Looks okay if that’s your design.


10051-10051: Check for integer overflow in sqrt32_bw.
Summing multiple FFT bins and passing to sqrt32_bw could risk overflow if bin sums are large. Confirm your typical input ranges stay well within safe bounds of 32-bit integer math.


16-16:

Details

❓ Verification inconclusive

Ensure no circular include issues.
Including "colors.h" here is likely necessary, but please confirm there are no circular include chains. In particular, verify that colors.h does not indirectly include FX.h or wled.h, which could cause subtle build or linkage problems.

Run a quick search within the codebase to confirm that colors.h does not include FX.h or wled.h:


🏁 Script executed:

#!/bin/bash
fd colors.h --exec cat | rg -A 15 -B 15 "include"

Length of output: 49


Circular Include Verification: Manual Check Recommended

It appears that colors.h does not directly include "FX.h" or "wled.h", as the automated search did not return any matches. However, since the initial script produced no output—which might be due to limitations in the automated scan—please manually verify that there are no indirect circular dependencies (for example, via other headers included within colors.h).

  • File: wled00/FX.cpp (line 16) includes "colors.h"
  • Action: Confirm that colors.h and any headers it might include do not indirectly include FX.h or wled.h.

@netmindz netmindz requested review from netmindz and willmmiles March 27, 2025 07:18
@netmindz
Copy link
Member

Updated AnimARTrix - e6b7509b391cab97b59b8eb19580e67d4203ae7d

coderabbitai[bot]

This comment was marked as resolved.

- resolves licensing issue
- moved PRNG into its own space
- prettified some code
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🔭 Outside diff range comments (5)
wled00/FX_fcn.cpp (5)

152-189: 🛠️ Refactor suggestion

Improve memory error checks in allocateData() and deallocateData().

These functions properly handle memory allocation and deallocation but consider clarifying the debug messages for memory failure cases:

  1. Indicate how many bytes were requested vs. how many remain.
  2. Ensure that partial allocations or repeated calls to deallocation can’t leave the system in a partially inconsistent memory state.

316-427: 🛠️ Refactor suggestion

Confirm correct brightness and mode blend behaviors.

Functions updateTransitionProgress(), currentBri(), and currentMode() handle various blending styles. Be sure the new fade/push/swipe styles do not lead to unexpected flickering, especially if intervals between frames are longer. Also, confirm that bridging from “old effect” to “new effect” retains consistent color states.


561-598: 🛠️ Refactor suggestion

Enforce defensive checks in color and CCT updates.

When setColor(), setCCT(), and setOpacity() are invoked in quick succession, the code starts transitions internally. Validate that repeated calls do not accumulate extra allocations or cause the effect logic to get stuck in an indeterminate transition state.


845-938: 🛠️ Refactor suggestion

Consider refactoring Pinwheel logic for maintainability.

The pinwheel effect is quite involved, using Bresenham-based draws. While powerful, it can be challenging to debug. Splitting out the steps (ray generation, block filling, partial line expansions) into smaller helper functions might boost readability and reduce the chance of subtle indexing bugs.


1279-1311: 🛠️ Refactor suggestion

Validate blur logic on boundary pixels.

The blur() function can carry over partial color from adjacent pixels. If the segment boundaries are at the ends of the strip or matrix edges, confirm no out-of-range reads occur. Also, check repeated calls to blur do not cause color drift or require an oppositional unblur step to preserve detail.

♻️ Duplicate comments (3)
wled00/FX.cpp (3)

2645-2655: Potential overflow when multiplying by 8.
(Multiplying deltabright by 8 may exceed 255 and wrap around.)


5841-5844: Potential dimension wrap-around in random8(0, cols).


7890-7890: min((uint16_t)150, hw_random16()) yields 150 often.

🧹 Nitpick comments (25)
wled00/FX_fcn.cpp (9)

16-16: Evaluate conditional include usage for "colors.h".

Since "colors.h" may be large and specialized, consider only including it where necessary to reduce compilation dependencies and improve build efficiency.


95-115: Avoid potential name collisions in copy & move constructors.

The copy and move constructors copy internal pointers (e.g., name, data) and set them to nullptr in the original object. This is correct for single ownership but be sure no other code references these pointers post-move. If a segment is duplicated before a move, ensure that references to the original are not inadvertently invalidated.


209-255: Validate fallback cases in loadPalette().

The switch statement handles wide-ranging pal values, including random and custom palettes. Ensure boundary checks remain consistent with other files that use strip.customPalettes. Also, if _randomPalette is empty or incorrectly generated, confirm that the code gracefully falls back to a known palette.


429-445: Evaluate performance in currentColor().

During transitions, currentColor() conditionally blends old and new color states. In performance-critical contexts (e.g., with many segments or frequent updates), repeated calls may add overhead. Consider caching partial computations if feasible, or verifying that repeated blending won't degrade framerate significantly.


489-503: Monitor timing in handleRandomPalette().

The logic triggers a new random palette when _lastPaletteChange is exceeded by randomPaletteChangeTime. Verify no collisions or race conditions if multiple segments request random palette changes simultaneously. If “harmonic random palette” is used, confirm it’s seeded properly to ensure diverse outcomes.


648-658: Clarify custom palette range in setPalette().

If user-supplied pal indexes a custom palette that is out of range, we unconditionally reset to 0. Consider logging or providing an error message to help users debug. This might reduce confusion if they supply an invalid palette ID programmatically.


807-839: Validate circular expansions in M12_pArc.

Generating circular arcs can be CPU-intensive for large radii. Check if frequent calls degrade performance. For extremely large segments, consider a more traditional Bresenham circle approach or caching partial results to improve performance, especially if arcs must be redrawn frequently.


1006-1038: Review anti-aliased version for float overhead.

setPixelColor(float i, uint32_t col, bool aa) uses floating-point. If performance constraints are strict, consider moving to fixed-point or precomputed scaling. On some microcontrollers, frequent float usage can hamper rendering speed or cause excessive memory usage.


1313-1364: Confirm uniform color distribution in color_wheel() and color_from_palette().

For color_wheel(), the code inverts pos and breaks it into three ranges. Confirm the transitions at 85 and 170 remain correct. For color_from_palette(), watch out for integer rounding in palette index mapping for large segments.

wled00/PRNG.h (3)

3-5: Add user-facing documentation for the PRNG class.

Briefly describing the usage and characteristics of this pseudo-random number generator (e.g., seedable, range-limited, etc.) would help new contributors or maintainers quickly understand how it’s intended to be used in effects.


12-17: Evaluate the internal LCG-like approach for uniform random distribution.

random16() uses multiplication and XOR shifting. While simple and usually sufficient, it’s worth checking if the distribution is acceptable for effects needing high randomness fidelity. Also consider a known approach like Xorshift* or PCG if uniform distribution is crucial.


21-23: Simplify or unify 8-bit random methods.

random8() is effectively random16() >> 8, but parameter overloads do extra multiplications. You could unify the logic to maintain code consistency, e.g., calling random16(lim << 8) >> 8 for uniform distribution, as long as performance remains acceptable.

wled00/palettes.h (2)

16-37: Use consistent naming patterns for palette constants.

CloudColors_p is introduced with color values bridging dark to light blues. The name is consistent with other palettes, but watch for user confusion if these are “Cloud” or “Sky” palettes. Possibly rename to “SkyCloudColors” or keep consistent underscores if it’s required by your style guide.


568-607: Evaluate representation of extra white channel in CRGBPalette16.

All palette definitions are purely RGB. If you anticipate using RGBW strips or a separate white channel, consider how these color definitions might be blended or extended to incorporate W. Alternatively, keep them strictly RGB if separate logic handles white channel injection (CCT or autowhite).

wled00/src/dependencies/fastled/fastled_fcn.h (1)

123-205: Scrutinize CRGB’s multi-constructor approach.

CRGB can be built from channels, 32-bit color codes, or from CHSV. Large expansions happen in hsv2rgb_rainbow(). If performance is a concern, consider caching partial results or adopting inlined transformations to cut function call overhead. Also ensure no memory misalignment when reading/writing raw[3].

wled00/src/dependencies/fastled/fastled_fcn.cpp (1)

101-106: Consider supporting the white channel in isRGBW mode.

Currently, rgbdata[3] is commented out in hsv2rgb_rainbow, leaving the white channel uninitialized if isRGBW is true. This can lead to inconsistencies. Consider explicitly setting rgbdata[3] to zero or computing a white component if needed.

    if(isRGBW) {
      rgbdata[0] = b;
      rgbdata[1] = g;
      rgbdata[2] = r;
-     //rgbdata[3] = 0; // white
+     rgbdata[3] = 0; // ensure a known value
    } ...
wled00/colors.h (2)

17-22: Possible macro collisions or unintended expansions.

The macros RGBW32, R(c), G(c), B(c), and W(c) may collide with similarly named macros or cause multiple evaluations of arguments. Consider prefixed or inline functions instead for safer usage.


159-166: Confirm ignoring the white channel when converting to CHSV32.

The assignment and constructor from CRGBW to CHSV32 discard the white component. Verify that ignoring w is intended. If preserving overall brightness is required, consider factoring in w during conversion to HSV.

wled00/FX.cpp (3)

2620-2627: Consider clarifying the fade factor.
Using 86 yields roughly one-third, but 85 is a closer approximation of 1/3 if that is the intended ratio.

-    bg = color_fade(bg, 86, true);
+    bg = color_fade(bg, 85, true);

4180-4180: Remove or justify commented-out code.
If this old assignment to palettes[1] is no longer needed, consider removing it rather than leaving a commented snippet.

-    //palettes[1] = CRGBPalette16(CHSV(baseI+hw_random8(64), 255, hw_random8(128,255)), ...

4924-4924: Remove or clarify commented line.
The commented-out SEGMENT.setPixelColorXY(...) line appears to be unused.

-      //SEGMENT.setPixelColorXY(dx, dy, grad ? color_fade(color, (255-rate), true) : color);
wled00/fcn_declare.h (4)

5-6: Consider splitting color-related functionality into a dedicated header or file.
Including "colors.h" here introduces a dependency on color-specific logic in a file that mostly declares general-purpose functions. If "colors.h" contains extensive color-handling classes or utilities, placing it in a separate header or compilation unit might improve maintainability and modularity.


395-401: Add documentation or comments clarifying the newly introduced beat functions.
These declarations (e.g., beat88, beat16, beat8, beatsin88_t, beatsin16_t, beatsin8_t) appear to be essential for effects timing or wave generation. It would be beneficial to include brief comments explaining their parameters, expected ranges, any known constraints (e.g., integer overflow), and typical usage scenarios. This helps future maintainers and eases unit test coverage.


459-461: Use inline functions instead of macros for sin_t, cos_t, and tan_t if possible.
Defining #define sin_t sin_approx macros can lead to unexpected expansions and can hamper debugging or IDE assistance. Replacing them with inline functions like inline float sin_t(float x) { return sin_approx(x); } would preserve the intent while avoiding macro side effects.


491-491: Remove or justify the commented standard Math block if no longer needed.
The large block of commented-out math functions above (lines 481-490) might cause confusion if this approximation-based approach is now preferred. Consider removing them or adding a short comment clarifying whether they’re retained as a fallback or reference only.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b49fdab and 47773b4.

📒 Files selected for processing (14)
  • wled00/FX.cpp (22 hunks)
  • wled00/FX.h (4 hunks)
  • wled00/FX_fcn.cpp (2 hunks)
  • wled00/PRNG.h (1 hunks)
  • wled00/colors.cpp (6 hunks)
  • wled00/colors.h (1 hunks)
  • wled00/fcn_declare.h (4 hunks)
  • wled00/ir.cpp (3 hunks)
  • wled00/palettes.h (1 hunks)
  • wled00/src/dependencies/fastled/LICENSE.txt (1 hunks)
  • wled00/src/dependencies/fastled/fastled_fcn.cpp (1 hunks)
  • wled00/src/dependencies/fastled/fastled_fcn.h (1 hunks)
  • wled00/util.cpp (3 hunks)
  • wled00/wled_math.cpp (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • wled00/wled_math.cpp
  • wled00/src/dependencies/fastled/LICENSE.txt
🚧 Files skipped from review as they are similar to previous changes (4)
  • wled00/ir.cpp
  • wled00/FX.h
  • wled00/util.cpp
  • wled00/colors.cpp
🧰 Additional context used
🧬 Code Graph Analysis (6)
wled00/src/dependencies/fastled/fastled_fcn.h (1)
wled00/src/dependencies/fastled/fastled_fcn.cpp (11)
  • hsv2rgb_rainbow (9-9)
  • fill_solid_RGB (136-140)
  • fill_solid_RGB (136-136)
  • fill_gradient_RGB (143-173)
  • fill_gradient_RGB (143-143)
  • fill_gradient_RGB (175-178)
  • fill_gradient_RGB (175-175)
  • fill_gradient_RGB (180-185)
  • fill_gradient_RGB (180-180)
  • fill_gradient_RGB (187-194)
  • fill_gradient_RGB (187-187)
wled00/src/dependencies/fastled/fastled_fcn.cpp (2)
wled00/colors.cpp (3)
  • optimize (96-104)
  • optimize (253-299)
  • optimize (310-329)
wled00/src/dependencies/fastled/fastled_fcn.h (16)
  • hue (167-169)
  • hue (167-167)
  • hue (172-174)
  • hue (172-172)
  • r (378-382)
  • r (394-396)
  • CRGB (148-148)
  • CRGB (148-148)
  • CRGB (151-152)
  • CRGB (151-151)
  • CRGB (155-156)
  • CRGB (155-155)
  • CRGB (159-159)
  • CRGB (159-159)
  • CRGB (162-164)
  • CRGB (162-162)
wled00/colors.h (3)
wled00/src/dependencies/fastled/fastled_fcn.h (26)
  • r (378-382)
  • r (394-396)
  • hue (167-169)
  • hue (167-167)
  • hue (172-174)
  • hue (172-172)
  • always_inline (11-11)
  • always_inline (12-12)
  • always_inline (13-13)
  • always_inline (14-14)
  • always_inline (15-15)
  • always_inline (16-16)
  • always_inline (17-17)
  • always_inline (18-18)
  • always_inline (240-243)
  • always_inline (240-240)
  • always_inline (246-250)
  • always_inline (246-246)
  • always_inline (253-256)
  • CHSV (101-101)
  • CHSV (101-101)
  • CHSV (104-105)
  • CHSV (104-104)
  • CHSV (108-108)
  • CHSV (108-108)
  • hsv2rgb_rainbow (52-52)
wled00/colors.cpp (5)
  • color_blend (20-30)
  • color_blend (20-20)
  • hsv2rgb_spectrum (253-253)
  • hsv2rgb_spectrum (302-307)
  • hsv2rgb_spectrum (302-302)
wled00/src/dependencies/fastled/fastled_fcn.cpp (1)
  • hsv2rgb_rainbow (9-9)
wled00/FX.cpp (3)
wled00/colors.h (6)
  • color (31-31)
  • color (130-130)
  • color (130-130)
  • r (154-156)
  • color_fade (42-42)
  • color_blend (39-39)
wled00/wled_math.cpp (4)
  • sin8_t (82-86)
  • sin8_t (82-82)
  • cos8_t (88-90)
  • cos8_t (88-88)
wled00/PRNG.h (4)
  • min (20-20)
  • min (20-20)
  • min (23-23)
  • min (23-23)
wled00/fcn_declare.h (1)
wled00/FX.h (1)
  • timebase (870-870)
wled00/FX_fcn.cpp (2)
wled00/FX.h (1)
  • _currentPalette (607-607)
wled00/src/dependencies/fastled/fastled_fcn.h (10)
  • CRGBPalette16 (572-574)
  • CRGBPalette16 (577-585)
  • CRGBPalette16 (588-590)
  • CRGBPalette16 (593-595)
  • CRGBPalette16 (610-614)
  • CRGBPalette16 (668-670)
  • CRGBPalette16 (673-675)
  • CRGBPalette16 (678-680)
  • CRGBPalette16 (683-685)
  • CRGBPalette16 (694-696)
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: wled_build / Build Environments (esp32s3dev_16MB_opi)
  • GitHub Check: wled_build / Build Environments (usermods)
  • GitHub Check: wled_build / Build Environments (lolin_s2_mini)
🔇 Additional comments (35)
wled00/FX_fcn.cpp (7)

80-89: Confirm default palette consistency across the codebase.

The static member _currentPalette is initialized to a default-constructed CRGBPalette16(). Make sure all references to _currentPalette (e.g. in loadPalette()) handle the initial state consistently, and verify that global transitions to other palettes remain coherent when the system first boots.


198-208: Expand validation in resetIfRequired().

resetIfRequired() re-initializes segment data if reset is set. This logic is simple and useful, but if the reset flag is triggered frequently or from other code paths, confirm that the effect states, transitions, or upstream references aren't unexpectedly cut short.


257-296: Ensure mode transitions handle concurrent changes smoothly.

startTransition() and stopTransition() rely on dynamic allocations and store transitional state in _t. Verify concurrency safety if transitions are triggered rapidly in succession (e.g., from network commands). Also, ensure memory for _segT._dataT is freed even if transitions are aborted early.


504-559: Assess geometry impacts on large 2D setups.

setGeometry() handles an extensive 2D coordinate system. For bigger matrices or custom LED maps, confirm it efficiently handles out-of-range inputs, preventing memory over-access. Also, ensure clear() calls remain minimal to avoid flickering if geometry changes frequently.


609-646: Check defaults for new modes in setMode().

The code loads default speeds, intensities, and other parameters from extractModeDefaults(). Ensure new or custom modes do not rely on undefined entries. Also, watch out for array out-of-bounds if advanced modes expand their default parameters beyond the baseline expected here.


675-744: Ensure correct handling for mirror and reverse.

The logic in virtualWidth(), virtualHeight(), and virtualLength() accounts for mirror and reverse. For advanced 2D or multiple segments layered, double-check that partial repeats or overlap do not produce off-by-one pixel artifacts (particularly if segment end boundaries vary).


953-961: Scrutinize push-based blending.

When _modeBlend == false but blendingStyle is BLEND_STYLE_PUSH_LEFT|_RIGHT, we adjust indices by a fraction. In large segments or with multi-effects, ensure no integer overflow occurs and that the inlined arithmetic is correct for typical transitions.

wled00/PRNG.h (1)

9-10: Verify that overwriting the seed won't disrupt ongoing randomness.

When setSeed() is called mid-runtime, confirm no conflicts arise if other effects or modules are concurrently using the PRNG. For long-running sessions, ensure seed changes do not inadvertently produce repetitive or patterned outputs.

wled00/palettes.h (3)

6-15: Confirm licensing and referencing for extracted palettes.

The file references FastLED under the MIT license. Ensure these references and the included license file remain correct and that any further modifications to these palettes are tracked. This helps maintain compliance with open-source obligations.


108-114: Validate gradient indexing for RainbowColors_p.

The array uses 16 discrete steps to render a rainbow. If changes are made to extend or reorder these steps, confirm that the color wrap-around remains correct. Also, consider verifying the performance of quick palette transitions if every line references this array.


994-1053: Verify fastledPalettes[] and gGradientPalettes[] indexing logic.

The arrays store references to known palette data structures (e.g., RainbowColors_p, Sunset_Real_gp). If code looks up palettes by index, ensure no out-of-bounds conditions occur or no mismatch with enumerations referencing them. This is especially important if new palettes are appended.

wled00/src/dependencies/fastled/fastled_fcn.h (1)

70-121: Confirm consistent CHSV assumptions across the codebase.

The CHSV struct allows assignment from hue, saturation, value. It’s important that h is stored in 8 bits with an assumed 0–255 range but used as 16 bits in hsv2rgb_rainbow by shifting h << 8. Verify no undesired wrap-around if h is near max values.

wled00/src/dependencies/fastled/fastled_fcn.cpp (4)

3-4: Document the original license details more explicitly.

While referencing that this code is originally from FastLED is good, it may be helpful to replicate the relevant licensing text from the original source, if the license requires it. Ensure compliance with MIT license requirements by including the original copyright statement.

Are you certain you have included all required license text from the FastLED repository in /src/dependencies/fastled/LICENSE.txt?


9-62: Validate handling of saturation=0 within hsv2rgb_rainbow.

When s == 0, the code sets r, g, and b to 255 before scaling by brightness, effectively producing pure white at maximum brightness. This can cause unexpected results if a user expects grayscale levels. Verify that this approach meets your intended design requirements.

Please confirm whether the behavior for s = 0 aligns with your project's color logic or if a gray-level approach is needed, for example:

- r = 255; g = 255; b = 255;
+ r = g = b = v; // produce grayscale

Also applies to: 66-100


136-185: Check for potential out-of-bounds in fill_gradient_RGB.

fill_gradient_RGB writes color data from startpos to endpos inclusively. There's no explicit array size check in this function. Ensure callers always pass valid startpos and endpos within the array bounds to prevent potential out-of-bounds writes.


217-259: LGTM on the easing and waveform functions.

The cubic, quadratic, and triangular wave generators are straightforward and commonly used in LED animation. The bitwise operations appear correct. Good job on keeping them inlined for performance.

wled00/colors.h (3)

13-14: Reiterate the MIT license details in the code.

The comment mentions that some functions derive from FastLED under MIT. Confirm that the notice includes all required disclaimers from the original FastLED source. This helps maintain licensing compliance.


95-107: Union usage might conflict with strict aliasing in some compilers.

While commonly used in color libraries, reading one union member (e.g. color32) after writing another (e.g. b, g, r, w) may trigger undefined behavior under strict aliasing rules. Consider confirming that your build settings or compiler version allow this usage, or use memcpy or std::bit_cast (C++20) for guaranteed well-defined behavior.


146-151: Ensure safe hue adjustments in adjust_hue.

hsv.h += (hueshift << 8) can overflow if hueshift is large. While wrapping might be intentional for cyclical color transitions, ensure it is tested and documented for all valid range values.

wled00/FX.cpp (16)

16-17: Header inclusion looks good!
No issues found in these new includes—the stable references to colors.h and prng.h are in line with the overall removal of the FastLED dependency.


73-74: Global static PRNG usage looks fine.
Given a single-threaded environment, this approach is acceptable and clarifies the random generation.


1761-1784: Consistent PRNG usage with seed restoration
Storing the old seed, using a new seed locally for randomization, and then restoring the old seed helps keep the rest of the system’s random usage coherent. The pattern of randomizing color channels with prng.random8(6) is clear and looks intentional.


2547-2547: Ensure SEGENV.aux0 is never zero.
The expression ms / SEGENV.aux0 could trigger a divide-by-zero if SEGENV.aux0 happens to be 0.

Would you like to confirm that SEGENV.aux0 is always nonzero or add a check for safety?


2582-2582: Definition of local color variable.
The new CRGBW c; line is straightforward, and usage is consistent downstream.


2597-2597: Assigning black is correct.
Setting c = 0; to represent black is valid for CRGBW.


4148-4158: Twinkleup's PRNG usage is consistent.
Storing and restoring the seed supports reproducible twinkle patterns without disrupting global randomness.


5699-5699: Perlin-based shift is correct.
Using a signed shift from perlin8() for bump mapping seems valid.


5826-5828: Check for dimension range in random8(0, w/h).
If w or h exceed 255, passing them to prng.random8(...) will wrap around.

Please confirm that w and h remain ≤ 255 or handle bigger sizes as needed.


6015-6015: Check for color wrap-around.
blob->color[i] += 4; may overflow if the color channel is near its maximum range.

Verify this behavior is intended or consider a clamp.


7511-7511: Blending of PixelA and PixelB is well-structured.
No issues found with the eased nscale addition.


7820-7820: Random rocket velocity.
Logic for vertical speed plus random offset is consistent with the effect’s design.


7864-7864: 50% chance for circular explosion looks intentional.
No issues.


9725-9725: Cosine-based gravity shift is valid.
xgravity = cos8_t(...) - 128; is consistent with existing pattern logic.


10052-10052: Calculating mids using sqrt32_bw is sound.
No immediate concerns.


2272-2273: ⚠️ Potential issue

Prevent underflow in fadeUpAmount and fadeDownAmount.
If strip.getBrightness() exceeds 68, then 68 - strip.getBrightness() underflows in an 8-bit variable.

Use a clamp to avoid negative values:

-  uint8_t fadeUpAmount = strip.getBrightness()>28 ? 8 + (SEGMENT.speed>>2) : 68-strip.getBrightness();
-  uint8_t fadeDownAmount = strip.getBrightness()>28 ? 8 + (SEGMENT.speed>>3) : 68-strip.getBrightness();
+  uint8_t baseUp   = (strip.getBrightness() <= 68) ? (68 - strip.getBrightness()) : 0;
+  uint8_t baseDown = (strip.getBrightness() <= 68) ? (68 - strip.getBrightness()) : 0;
+  uint8_t fadeUpAmount =
+      (strip.getBrightness() > 28 ? (8 + (SEGMENT.speed >> 2)) : baseUp);
+  uint8_t fadeDownAmount =
+      (strip.getBrightness() > 28 ? (8 + (SEGMENT.speed >> 3)) : baseDown);

Likely an incorrect or invalid review comment.

Comment on lines +568 to +782
// RGB color palette with 16 discrete values
class CRGBPalette16 {
public:
CRGB entries[16];
CRGBPalette16() {
memset(entries, 0, sizeof(entries)); // default constructor: set all to black
}

// Create palette from 16 CRGB values
CRGBPalette16(const CRGB& c00, const CRGB& c01, const CRGB& c02, const CRGB& c03,
const CRGB& c04, const CRGB& c05, const CRGB& c06, const CRGB& c07,
const CRGB& c08, const CRGB& c09, const CRGB& c10, const CRGB& c11,
const CRGB& c12, const CRGB& c13, const CRGB& c14, const CRGB& c15) {
entries[0] = c00; entries[1] = c01; entries[2] = c02; entries[3] = c03;
entries[4] = c04; entries[5] = c05; entries[6] = c06; entries[7] = c07;
entries[8] = c08; entries[9] = c09; entries[10] = c10; entries[11] = c11;
entries[12] = c12; entries[13] = c13; entries[14] = c14; entries[15] = c15;
};

// Copy constructor
CRGBPalette16(const CRGBPalette16& rhs) {
memmove((void*)&(entries[0]), &(rhs.entries[0]), sizeof(entries));
}

// Create palette from array of CRGB colors
CRGBPalette16(const CRGB rhs[16]) {
memmove((void*)&(entries[0]), &(rhs[0]), sizeof(entries));
}

// Copy assignment operator
CRGBPalette16& operator=(const CRGBPalette16& rhs) {
memmove((void*)&(entries[0]), &(rhs.entries[0]), sizeof(entries));
return *this;
}

// Create palette from array of CRGB colors
CRGBPalette16& operator=(const CRGB rhs[16]) {
memmove((void*)&(entries[0]), &(rhs[0]), sizeof(entries));
return *this;
}

// Create palette from palette stored in PROGMEM
CRGBPalette16(const TProgmemRGBPalette16& rhs) {
for (int i = 0; i < 16; ++i) {
entries[i] = *(const uint32_t*)(rhs + i);
}
}

// Copy assignment operator for PROGMEM palette
CRGBPalette16& operator=(const TProgmemRGBPalette16& rhs) {
for (int i = 0; i < 16; ++i) {
entries[i] = *(const uint32_t*)(rhs + i);
}
return *this;
}

// Equality operator
bool operator==(const CRGBPalette16& rhs) const {
const uint8_t* p = (const uint8_t*)(&(this->entries[0]));
const uint8_t* q = (const uint8_t*)(&(rhs.entries[0]));
if (p == q) return true;
for (unsigned i = 0; i < (sizeof(entries)); ++i) {
if (*p != *q) return false;
++p;
++q;
}
return true;
}

// Inequality operator
bool operator!=(const CRGBPalette16& rhs) const {
return !(*this == rhs);
}

// Array subscript operator
inline CRGB& operator[](uint8_t x) __attribute__((always_inline)) {
return entries[x];
}

// Array subscript operator (const)
inline const CRGB& operator[](uint8_t x) const __attribute__((always_inline)) {
return entries[x];
}

// Array subscript operator
inline CRGB& operator[](int x) __attribute__((always_inline)) {
return entries[(uint8_t)x];
}

// Array subscript operator (const)
inline const CRGB& operator[](int x) const __attribute__((always_inline)) {
return entries[(uint8_t)x];
}

// Get the underlying pointer to the CRGB entries making up the palette
operator CRGB*() {
return &(entries[0]);
}

// Create palette from a single CRGB color
CRGBPalette16(const CRGB& c1) {
fill_solid_RGB(&(entries[0]), 16, c1);
}

// Create palette from two CRGB colors
CRGBPalette16(const CRGB& c1, const CRGB& c2) {
fill_gradient_RGB(&(entries[0]), 16, c1, c2);
}

// Create palette from three CRGB colors
CRGBPalette16(const CRGB& c1, const CRGB& c2, const CRGB& c3) {
fill_gradient_RGB(&(entries[0]), 16, c1, c2, c3);
}

// Create palette from four CRGB colors
CRGBPalette16(const CRGB& c1, const CRGB& c2, const CRGB& c3, const CRGB& c4) {
fill_gradient_RGB(&(entries[0]), 16, c1, c2, c3, c4);
}

// Creates a palette from a gradient palette in PROGMEM.
//
// Gradient palettes are loaded into CRGBPalettes in such a way
// that, if possible, every color represented in the gradient palette
// is also represented in the CRGBPalette, this may not preserve original
// color spacing, but will try to not omit small color bands.

CRGBPalette16(TProgmemRGBGradientPalette_bytes progpal) {
*this = progpal;
}

CRGBPalette16& operator=(TProgmemRGBGradientPalette_bytes progpal) {
TRGBGradientPaletteEntryUnion* progent = (TRGBGradientPaletteEntryUnion*)(progpal);
TRGBGradientPaletteEntryUnion u;

// Count entries
int count = 0;
do {
u.dword = *(const uint32_t*)(progent + count);
++count;
} while (u.index != 255);

int lastSlotUsed = -1;

u.dword = *(const uint32_t*)(progent);
CRGB rgbstart(u.r, u.g, u.b);

int indexstart = 0;
int istart8 = 0;
int iend8 = 0;
while (indexstart < 255) {
++progent;
u.dword = *(const uint32_t*)(progent);
int indexend = u.index;
CRGB rgbend(u.r, u.g, u.b);
istart8 = indexstart / 16;
iend8 = indexend / 16;
if (count < 16) {
if ((istart8 <= lastSlotUsed) && (lastSlotUsed < 15)) {
istart8 = lastSlotUsed + 1;
if (iend8 < istart8) {
iend8 = istart8;
}
}
lastSlotUsed = iend8;
}
fill_gradient_RGB(&(entries[0]), istart8, rgbstart, iend8, rgbend);
indexstart = indexend;
rgbstart = rgbend;
}
return *this;
}

// Creates a palette from a gradient palette in dynamic (heap) memory.
CRGBPalette16& loadDynamicGradientPalette(TDynamicRGBGradientPalette_bytes gpal) {
TRGBGradientPaletteEntryUnion* ent = (TRGBGradientPaletteEntryUnion*)(gpal);
TRGBGradientPaletteEntryUnion u;

// Count entries
unsigned count = 0;
do {
u = *(ent + count);
++count;
} while (u.index != 255);

int lastSlotUsed = -1;

u = *ent;
CRGB rgbstart(u.r, u.g, u.b);

int indexstart = 0;
int istart8 = 0;
int iend8 = 0;
while (indexstart < 255) {
++ent;
u = *ent;
int indexend = u.index;
CRGB rgbend(u.r, u.g, u.b);
istart8 = indexstart / 16;
iend8 = indexend / 16;
if (count < 16) {
if ((istart8 <= lastSlotUsed) && (lastSlotUsed < 15)) {
istart8 = lastSlotUsed + 1;
if (iend8 < istart8) {
iend8 = istart8;
}
}
lastSlotUsed = iend8;
}
fill_gradient_RGB(&(entries[0]), istart8, rgbstart, iend8, rgbend);
indexstart = indexend;
rgbstart = rgbend;
}
return *this;
}
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Review palette loading and gradient expansions in CRGBPalette16.

The constructor overloads and operator= handle multiple sources (arrays, PROGMEM, dynamic gradient). Make sure that partial or invalid gradient data does not cause out-of-bounds reads. Also, watch out for repeated calls to fill_gradient_RGB() that might degrade performance on low-end MCUs when creating or modifying palettes frequently.

@DedeHai
Copy link
Collaborator Author

DedeHai commented Apr 15, 2025

@Aircoookie any thoughts on how I fixed the licensing issue?

  • moved some of the FastLED stuff into its own folder, adding MIT license there
  • referenced that license in all places where FastLED functions/palettes were added within WLED code

coderabbitai[bot]

This comment was marked as outdated.

coderabbitai[bot]

This comment was marked as duplicate.

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@DedeHai
Copy link
Collaborator Author

DedeHai commented Mar 10, 2026

@netmindz this PR breaks animartrix, it needs an update to not use fastled lib.
edit: simply removing #include <FastLED.h> from animartrix seems to work.

From the usermod or the library?

from the library.

edit:
/ANIMartRIX.h:28:10: fatal error: FastLED.h: No such file or directory

git hash updated to the same version used in WLED-MM - does not include <FastLED.h> any more
@softhack007
Copy link
Member

softhack007 commented Mar 10, 2026

From the usermod or the library?

from the library.

I've just updated the git hash to the same version that's used in WLED-MM. Compiles locally, fingers crossed for the CI build

@softhack007
Copy link
Member

softhack007 commented Mar 10, 2026

@DedeHai how about renaming the folder src/dependancies/fastled to src/dependancies/no-fastled or maybe mini-fastled ??

@DedeHai
Copy link
Collaborator Author

DedeHai commented Mar 12, 2026

@DedeHai how about renaming the folder src/dependancies/fastled to src/dependancies/no-fastled or maybe mini-fastled ??

good idea, so it is unmistakenly labeled as "not full fastled".
How about "fastled-slim" ?

@softhack007
Copy link
Member

good idea, so it is unmistakenly labeled as "not full fastled".
How about "fastled-slim" ?

"fastled-slim" sounds good :-)

@softhack007 softhack007 requested a review from TripleWhy March 12, 2026 15:24
@softhack007
Copy link
Member

softhack007 commented Mar 12, 2026

Before I update this PR: are there any concerns regarding license (i.e. how I solved it in this PR)

@DedeHai I think you did even more than what was needed, by creating a separate component that stays on MIT terms. So all good 🎉

FastLED uses MIT license, so importing their code into our EUPL project is always possible, because our license conditions (attribution, contribution) are stronger than MIT - EUPL satisfies the MIT needs easily, while an MIT project cannot re-use EUPL source code without explicit agreement from the EUPL project.

Basically, MIT is the "grab and run" license - anybody can re-use their source code (even under closed source) if originator and limited liability notices are preserved.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
wled00/FX.cpp (1)

6134-6153: ⚠️ Potential issue | 🟠 Major

Give Crazy Bees its own PRNG state.

Line 6149 reseeds the shared global prng, and Lines 6134-6136 consume from that same generator inside bee_t::aimed(). Because later re-aims keep advancing the shared instance, this effect’s paths depend on whatever other segments/effects used prng before it, and it perturbs them in return. That undermines the repeatable/sync behavior this PR is introducing. Please keep a per-segment seed/PRNG for Crazy Bees, or save and restore the global seed around every call to aimed().

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/FX.cpp` around lines 6134 - 6153, The Crazy Bees effect currently
reseeds and consumes the shared global prng (see prng.setSeed(strip.now),
bee_t::aimed(), and later re-aims), which breaks determinism; fix by giving
Crazy Bees its own PRNG instance/state: introduce a local PRNG member or local
variable used only by this segment (seeded from strip.now and/or a
segment-specific value) and use that PRNG for all calls to bee_t::aimed(), both
during initialization and on subsequent re-aims, or alternatively save the
global prng state before each aimed() call and restore it afterward; update
references to prng in the bee_t::aimed implementation and in the loop that
initializes bee[i].aimed(cols, rows) to use the new per-segment PRNG/state (or
the save/restore calls) so other effects and SEGENV.call behavior remain
unaffected.
♻️ Duplicate comments (3)
wled00/FX.h (1)

755-756: ⚠️ Potential issue | 🟠 Major

Keep the WLED_DISABLE_2D stubs aligned with the 2D API.

The #else branch still exposes blurRow/blurCol, drops several const qualifiers, and changes addPixelColorXY(..., bool) from preserveCR = true to false. That makes 1D-only builds diverge in both compile surface and blending behavior.

🛠️ Proposed fix
-    inline bool isPixelXYClipped(int x, int y)                                    { return isPixelClipped(x); }
-    inline uint32_t getPixelColorXY(int x, int y)                                 { return getPixelColor(x); }
-    inline void blendPixelColorXY(uint16_t x, uint16_t y, uint32_t c, uint8_t blend) { blendPixelColor(x, c, blend); }
-    inline void blendPixelColorXY(uint16_t x, uint16_t y, CRGB c, uint8_t blend)  { blendPixelColor(x, RGBW32(c.r,c.g,c.b,0), blend); }
-    inline void addPixelColorXY(int x, int y, uint32_t color, bool saturate = false) { addPixelColor(x, color, saturate); }
-    inline void addPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0, bool saturate = false) { addPixelColor(x, RGBW32(r,g,b,w), saturate); }
-    inline void addPixelColorXY(int x, int y, CRGB c, bool saturate = false)         { addPixelColor(x, RGBW32(c.r,c.g,c.b,0), saturate); }
-    inline void fadePixelColorXY(uint16_t x, uint16_t y, uint8_t fade)            { fadePixelColor(x, fade); }
-    //inline void box_blur(unsigned i, bool vertical, uint8_t blur_amount) {}
-    inline void blur2D(uint8_t blur_x, uint8_t blur_y, bool smear = false) {}
-    inline void blurRow(int row, uint8_t blur_amount, bool smear = false) {}
-    inline void blurCol(int col, uint8_t blur_amount, bool smear = false) {}
+    inline bool isPixelXYClipped(int x, int y) const                                   { return isPixelClipped(x); }
+    inline uint32_t getPixelColorXY(int x, int y) const                                { return getPixelColor(x); }
+    inline void blendPixelColorXY(uint16_t x, uint16_t y, uint32_t c, uint8_t blend) const { blendPixelColor(x, c, blend); }
+    inline void blendPixelColorXY(uint16_t x, uint16_t y, CRGB c, uint8_t blend) const { blendPixelColor(x, RGBW32(c.r,c.g,c.b,0), blend); }
+    inline void addPixelColorXY(int x, int y, uint32_t color, bool preserveCR = true) const { addPixelColor(x, color, preserveCR); }
+    inline void addPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0, bool preserveCR = true) { addPixelColorXY(x, y, RGBW32(r,g,b,w), preserveCR); }
+    inline void addPixelColorXY(int x, int y, CRGB c, bool preserveCR = true) const   { addPixelColor(x, RGBW32(c.r,c.g,c.b,0), preserveCR); }
+    inline void fadePixelColorXY(uint16_t x, uint16_t y, uint8_t fade) const          { fadePixelColor(x, fade); }
+    //inline void box_blur(unsigned i, bool vertical, uint8_t blur_amount) {}
+    inline void blur2D(uint8_t blur_x, uint8_t blur_y, bool smear = false) const {}
+    inline void blurCols(uint8_t blur_amount, bool smear = false) const {}
+    inline void blurRows(uint8_t blur_amount, bool smear = false) const {}

Also applies to: 784-795

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/FX.h` around lines 755 - 756, The WLED_DISABLE_2D `#else` stubs must
match the 2D API exactly: make the stubbed methods blurCols and blurRows call
blur2D with the same signatures and const qualifiers (i.e., inline void
blurCols(uint8_t, bool smear=false) const { blur2D(0, blur_amount, smear); } and
inline void blurRows(...) const { blur2D(...); }), restore any removed const
qualifiers, and ensure addPixelColorXY(...) in the 1D-only branch preserves the
original default preserveCR = true; update the stub declarations for the
functions referenced (blurCols, blurRows, blur2D, addPixelColorXY) so the
compile surface and blending behavior remain identical under WLED_DISABLE_2D.
wled00/colors.cpp (2)

376-380: ⚠️ Potential issue | 🔴 Critical

Restore the gray fast path before the hue division.

Any non-black gray reaches this block with delta == 0, so Lines 378-380 divide by zero.

🛠️ Proposed fix
     delta = maxval - minval;
-    hsv.s = delta == maxval ? 255 : (255 * delta) / maxval; // faster on fully saturated colors, slightly slower otherwise
-    // note: early return if s==0 is omitted here to increase speed as gray values are rarely used
+    if (delta == 0) {
+      hsv.h = 0;
+      hsv.s = 0;
+      return;
+    }
+    hsv.s = delta == maxval ? 255 : (255 * delta) / maxval; // faster on fully saturated colors, slightly slower otherwise
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/colors.cpp` around lines 376 - 380, The hue calculation divides by
delta without checking for delta==0, causing divide-by-zero for non-black grays;
restore the gray fast path by checking if delta==0 (or s==0) immediately after
computing hsv.s and before the hue assignment, and set hsv.h to 0 (or an
appropriate default) and return/skip hue math; update the block around hsv.s,
hsv.h, delta, maxval, r, g, b to perform this early exit so Lines computing
hsv.h (the 10923/21845/43690 multipliers) never execute when delta==0.

52-53: ⚠️ Potential issue | 🔴 Critical

Include w in the preserve-ratio max calculation.

If only the white channel overflows, maxval stays 0 here and the next line divides by zero. color_add(RGBW32(0,0,0,255), RGBW32(0,0,0,255), true) is enough to trigger it.

🛠️ Proposed fix
-      uint32_t maxval = (r > g) ? ((r > b) ? r : b) : ((g > b) ? g : b); // find max value. note: faster than using max() function or on par
-      const uint32_t scale = (uint32_t(255)<<8) / maxval; // division of two 8bit (shifted) values does not work -> use bit shifts and multiplaction instead
+      uint32_t maxval = r;
+      if (g > maxval) maxval = g;
+      if (b > maxval) maxval = b;
+      if (w > maxval) maxval = w;
+      const uint32_t scale = (uint32_t(255) << 8) / maxval; // division of two 8bit (shifted) values does not work -> use bit shifts and multiplication instead
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/colors.cpp` around lines 52 - 53, The max calculation for scaling
ignores the white channel and can leave maxval zero when only w is nonzero
(e.g., color_add / RGBW32 with w=255), causing a divide-by-zero in the
computation of scale; update the max determination (the expression that computes
maxval) to include w alongside r, g, b so maxval = max(r,g,b,w), and ensure the
same symbols (maxval, r, g, b, w, scale, color_add, RGBW32) are used so the
division uses a nonzero denominator.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@wled00/colors.h`:
- Around line 178-183: The left-shift of a potentially negative signed parameter
causes undefined behavior; in adjust_hue (colors.h) and adjust_color
(colors.cpp) replace the "hueshift << 8" style shift with a safe multiplication
by 256, ensuring the multiplication is done in a signed type before adding to
the HSV hue field (e.g., cast hueshift to int32_t and multiply by 256, then
add/assign to hsv.h) so negative values behave correctly and no left-shift on a
negative signed integer occurs.

In `@wled00/FX.cpp`:
- Around line 1814-1817: The PRNG state is being advanced by SEGENV.step =
RGBW32(prng.random8(), prng.random8(), prng.random8(), 0) and SEGENV.aux0 =
prng.random16() before prevSeed is captured, so the first render still perturbs
the shared prng; move the snapshot so prevSeed = prng.getSeed() occurs before
any prng.random* calls (or capture seed into a variable first and use that to
seed a local PRNG), then leave the existing restore logic intact at the end of
mode_random_chase()/the surrounding function to restore the global prng state.

---

Outside diff comments:
In `@wled00/FX.cpp`:
- Around line 6134-6153: The Crazy Bees effect currently reseeds and consumes
the shared global prng (see prng.setSeed(strip.now), bee_t::aimed(), and later
re-aims), which breaks determinism; fix by giving Crazy Bees its own PRNG
instance/state: introduce a local PRNG member or local variable used only by
this segment (seeded from strip.now and/or a segment-specific value) and use
that PRNG for all calls to bee_t::aimed(), both during initialization and on
subsequent re-aims, or alternatively save the global prng state before each
aimed() call and restore it afterward; update references to prng in the
bee_t::aimed implementation and in the loop that initializes bee[i].aimed(cols,
rows) to use the new per-segment PRNG/state (or the save/restore calls) so other
effects and SEGENV.call behavior remain unaffected.

---

Duplicate comments:
In `@wled00/colors.cpp`:
- Around line 376-380: The hue calculation divides by delta without checking for
delta==0, causing divide-by-zero for non-black grays; restore the gray fast path
by checking if delta==0 (or s==0) immediately after computing hsv.s and before
the hue assignment, and set hsv.h to 0 (or an appropriate default) and
return/skip hue math; update the block around hsv.s, hsv.h, delta, maxval, r, g,
b to perform this early exit so Lines computing hsv.h (the 10923/21845/43690
multipliers) never execute when delta==0.
- Around line 52-53: The max calculation for scaling ignores the white channel
and can leave maxval zero when only w is nonzero (e.g., color_add / RGBW32 with
w=255), causing a divide-by-zero in the computation of scale; update the max
determination (the expression that computes maxval) to include w alongside r, g,
b so maxval = max(r,g,b,w), and ensure the same symbols (maxval, r, g, b, w,
scale, color_add, RGBW32) are used so the division uses a nonzero denominator.

In `@wled00/FX.h`:
- Around line 755-756: The WLED_DISABLE_2D `#else` stubs must match the 2D API
exactly: make the stubbed methods blurCols and blurRows call blur2D with the
same signatures and const qualifiers (i.e., inline void blurCols(uint8_t, bool
smear=false) const { blur2D(0, blur_amount, smear); } and inline void
blurRows(...) const { blur2D(...); }), restore any removed const qualifiers, and
ensure addPixelColorXY(...) in the 1D-only branch preserves the original default
preserveCR = true; update the stub declarations for the functions referenced
(blurCols, blurRows, blur2D, addPixelColorXY) so the compile surface and
blending behavior remain identical under WLED_DISABLE_2D.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 494688e4-15b9-47f4-a72c-f10624132da0

📥 Commits

Reviewing files that changed from the base of the PR and between fcabef7 and 0ec06f4.

📒 Files selected for processing (11)
  • usermods/user_fx/user_fx.cpp
  • usermods/usermod_v2_animartrix/library.json
  • wled00/FX.cpp
  • wled00/FX.h
  • wled00/FX_fcn.cpp
  • wled00/colors.cpp
  • wled00/colors.h
  • wled00/const.h
  • wled00/json.cpp
  • wled00/palettes.cpp
  • wled00/util.cpp

Comment on lines +178 to +183
// adjust hue: input range is 256 for full color cycle, input can be negative
inline void adjust_hue(int hueshift) __attribute__((always_inline)) {
CHSV32 hsv = *this;
hsv.h += hueshift << 8;
hsv2rgb_spectrum(hsv, *this);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, find where adjust_hue is used and with what arguments
rg "adjust_hue" --type cpp --type h -B 2 -A 2

Repository: wled/WLED

Length of output: 1688


🏁 Script executed:

# Check CHSV32 definition and related code around colors.h
grep -n "class CHSV32\|struct CHSV32" wled00/colors.h -A 20

Repository: wled/WLED

Length of output: 2423


🏁 Script executed:

# Look for any explicit handling of negative shifts or hue adjustments in the codebase
rg "hueshift|hue.*shift|shift.*hue" --type cpp --type h -i

Repository: wled/WLED

Length of output: 1816


🏁 Script executed:

# Find the adjust_hue call in ir.cpp and get context around it
grep -n "adjust_hue(amount)" wled00/ir.cpp -B 10 -A 5

Repository: wled/WLED

Length of output: 781


🏁 Script executed:

# Check what adjust_color does with hueshift to see if it handles negative differently
grep -n "adjust_color\|hueShift" wled00/colors.cpp -B 2 -A 5 | head -50

Repository: wled/WLED

Length of output: 773


🏁 Script executed:

# Verify C++ standard behavior - check if any comments or documentation about this exist
rg "undefined|UB|shift.*negative|negative.*shift" --type cpp --type h wled00/ | grep -i "hue\|shift"

Repository: wled/WLED

Length of output: 2345


🏁 Script executed:

# Get more context on where 'amount' comes from in ir.cpp
grep -n "amount\s*=" wled00/ir.cpp | head -20

Repository: wled/WLED

Length of output: 35


🏁 Script executed:

# Search for the function containing the adjust_hue call to see what amount is
grep -n "adjustHue\|void.*hue" wled00/ir.cpp -B 5 -A 15

Repository: wled/WLED

Length of output: 35


🏁 Script executed:

# Get broader context around the adjust_hue call in ir.cpp (lines 130-140)
sed -n '100,160p' wled00/ir.cpp

Repository: wled/WLED

Length of output: 2126


🏁 Script executed:

# Search for where amount variable is defined before the adjust_hue call
sed -n '1,135p' wled00/ir.cpp | tail -50

Repository: wled/WLED

Length of output: 1635


🏁 Script executed:

# Let's check if there are actual negative values passed or if amount is always positive
rg "amount\s*=|int\s+amount" wled00/ir.cpp -B 5 -A 2

Repository: wled/WLED

Length of output: 35


🏁 Script executed:

# Get the full function signature and context
sed -n '110,165p' wled00/ir.cpp

Repository: wled/WLED

Length of output: 1977


🏁 Script executed:

# Check if there are any other places where left-shift is used with signed values and how they handle it
rg "<<\s*8|<<\s*16" wled00/colors.cpp wled00/colors.h -B 1 -A 1 | head -40

Repository: wled/WLED

Length of output: 2570


🏁 Script executed:

# Check the adjust_hue function definition and signature one more time
sed -n '175,190p' wled00/colors.h

Repository: wled/WLED

Length of output: 446


🏁 Script executed:

# Let's verify the C++ standard on left-shift of signed integers
# Check if there are any compiler flags or pragmas that might affect this behavior
grep -r "pragma\|attribute" wled00/colors.h | grep -i "shift\|ub\|undefined"

Repository: wled/WLED

Length of output: 124


🏁 Script executed:

# Check if similar shift operations on signed values appear elsewhere with explicit handling
rg "<<.*8|<<.*16" wled00/ --type cpp -B 2 -A 2 | grep -E "int8_t|int16_t|int32_t" -B 3 -A 1 | head -40

Repository: wled/WLED

Length of output: 2234


🏁 Script executed:

# Check the C++ standard used by WLED
find . -name "CMakeLists.txt" -o -name "*.pio" -o -name "platformio.ini" -o -name ".clang-tidy" -o -name ".clang-format" | head -5

Repository: wled/WLED

Length of output: 83


🏁 Script executed:

# Check for CMakeLists.txt and what C++ version is specified
grep -r "std=c++\|CXX_STANDARD\|cxx_std" . --include="CMakeLists.txt" --include="platformio.ini" 2>/dev/null | head -10

Repository: wled/WLED

Length of output: 35


🏁 Script executed:

# Check if there's any documentation about this in the code or comments
rg "undefined.*behavior|left.shift.*negative|negative.*shift" --type cpp --type h wled00/colors.h wled00/colors.cpp

Repository: wled/WLED

Length of output: 35


🏁 Script executed:

# Check platformio.ini for C++ standard settings
grep -i "std\|cpp\|cxx" ./platformio.ini | head -20

Repository: wled/WLED

Length of output: 35


🏁 Script executed:

# Look at the actual contents of platformio.ini
head -100 ./platformio.ini

Repository: wled/WLED

Length of output: 4654


🏁 Script executed:

# Check compiler flags in platformio.ini more carefully
grep -i "build_flags\|cxxflags\|cflags" platformio.ini | head -30

Repository: wled/WLED

Length of output: 2253


🏁 Script executed:

# Let's also check if there are environment-specific settings
grep -A 30 "\[env:" platformio.ini | grep -E "build_flags|cxxflags|std=" | head -20

Repository: wled/WLED

Length of output: 2264


🏁 Script executed:

# Check what the actual behavior is - let's look for Arduino/esp8266 compiler documentation or any related notes in the code
rg "c\+\+11|c\+\+14|c\+\+17|std=" . --type cpp --type h --type ini | head -10

Repository: wled/WLED

Length of output: 80


🏁 Script executed:

# Let's check the actual C++ standard compliance by looking for any compiler options related to undefined behavior or sanitizers
grep -r "fsanitize\|Wshift\|pedantic" platformio.ini ./.clang-tidy 2>/dev/null

Repository: wled/WLED

Length of output: 35


🏁 Script executed:

# Look at the .clang-tidy file
cat ./.clang-tidy 2>/dev/null || echo "File not found or empty"

Repository: wled/WLED

Length of output: 829


🏁 Script executed:

# Now let's write a test to understand the actual behavior
python3 << 'EOF'
# According to C++ standard, left-shifting a negative signed integer is undefined behavior.
# Let's verify the semantics of the suggested fix.

# Original: hsv.h += hueshift << 8;
# where hsv.h is uint16_t and hueshift is int

# Example with hueshift = -5 (int8_t or int)
hueshift = -5

# What the original code tries to do: multiply by 256
# Left-shift by 8 is equivalent to multiply by 256 for positive values
# For negative: -5 << 8 is undefined behavior

# Suggested fix: int32_t(hueshift) * 256
result = int(hueshift) * 256
print(f"hueshift = {hueshift}")
print(f"hueshift * 256 = {result}")
print(f"Result as uint16_t (wraparound): {result & 0xFFFF}")

# The fix converts to signed 32-bit and multiplies, avoiding the shift on negative
# Then the result gets added to hsv.h (uint16_t) which triggers unsigned arithmetic

print("\nThe semantics appear equivalent for small negative values.")
EOF

Repository: wled/WLED

Length of output: 188


🏁 Script executed:

# Search for any documented standards compliance or known issues related to this
rg "shift.*negative|negative.*shift|undefined.*behavior" --type cpp --type h wled00/ -i

Repository: wled/WLED

Length of output: 2220


Fix left-shift of potentially negative hueshift value.

Line 181 left-shifts hueshift even though the function accepts negative inputs (as confirmed by the comment and actual usage in ir.cpp::changeEffectSpeed(), which calls adjust_hue(amount) with signed amount that can be negative). Left-shifting a negative signed integer is undefined behavior in C++. The same issue exists in colors.cpp::adjust_color() at line 103.

Use multiplication instead to avoid undefined behavior:

Safer arithmetic
-    hsv.h += hueshift << 8;
+    hsv.h = uint16_t(int32_t(hsv.h) + int32_t(hueshift) * 256);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/colors.h` around lines 178 - 183, The left-shift of a potentially
negative signed parameter causes undefined behavior; in adjust_hue (colors.h)
and adjust_color (colors.cpp) replace the "hueshift << 8" style shift with a
safe multiplication by 256, ensuring the multiplication is done in a signed type
before adding to the HSV hue field (e.g., cast hueshift to int32_t and multiply
by 256, then add/assign to hsv.h) so negative values behave correctly and no
left-shift on a negative signed integer occurs.

Comment on lines +1814 to +1817
SEGENV.step = RGBW32(prng.random8(), prng.random8(), prng.random8(), 0);
SEGENV.aux0 = prng.random16();
}
unsigned prevSeed = random16_get_seed(); // save seed so we can restore it at the end of the function
unsigned prevSeed = prng.getSeed(); // save seed so we can restore it at the end of the function
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Snapshot the PRNG state before the init draws.

Lines 1814-1815 consume from the shared prng before Line 1817 captures prevSeed, so the first render of mode_random_chase() still perturbs the global sequence even though the steady-state loop restores it.

🛠️ Minimal fix
-  if (SEGENV.call == 0) {
-    SEGENV.step = RGBW32(prng.random8(), prng.random8(), prng.random8(), 0);
-    SEGENV.aux0 = prng.random16();
-  }
-  unsigned prevSeed = prng.getSeed(); // save seed so we can restore it at the end of the function
+  unsigned prevSeed = prng.getSeed(); // save seed so we can restore it at the end of the function
+  if (SEGENV.call == 0) {
+    SEGENV.step = RGBW32(prng.random8(), prng.random8(), prng.random8(), 0);
+    SEGENV.aux0 = prng.random16();
+  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
SEGENV.step = RGBW32(prng.random8(), prng.random8(), prng.random8(), 0);
SEGENV.aux0 = prng.random16();
}
unsigned prevSeed = random16_get_seed(); // save seed so we can restore it at the end of the function
unsigned prevSeed = prng.getSeed(); // save seed so we can restore it at the end of the function
unsigned prevSeed = prng.getSeed(); // save seed so we can restore it at the end of the function
if (SEGENV.call == 0) {
SEGENV.step = RGBW32(prng.random8(), prng.random8(), prng.random8(), 0);
SEGENV.aux0 = prng.random16();
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/FX.cpp` around lines 1814 - 1817, The PRNG state is being advanced by
SEGENV.step = RGBW32(prng.random8(), prng.random8(), prng.random8(), 0) and
SEGENV.aux0 = prng.random16() before prevSeed is captured, so the first render
still perturbs the shared prng; move the snapshot so prevSeed = prng.getSeed()
occurs before any prng.random* calls (or capture seed into a variable first and
use that to seed a local PRNG), then leave the existing restore logic intact at
the end of mode_random_chase()/the surrounding function to restore the global
prng state.

@softhack007
Copy link
Member

@DedeHai two newer effects still have small compatibility problems:

Compiling .pio\build\usermods\libda9\user_fx\user_fx.cpp.o
usermods/user_fx/user_fx.cpp: In function 'void mode_spinning_wheel()':
usermods/user_fx/user_fx.cpp:151:5: error: 'random16_set_seed' was not declared in this scope
     random16_set_seed(hw_random16());
     ^~~~~~~~~~~~~~~~~
usermods/user_fx/user_fx.cpp:159:5: error: 'random16_add_entropy' was not declared in this scope
     random16_add_entropy(hw_random16());
     ^~~~~~~~~~~~~~~~~~~~
usermods/user_fx/user_fx.cpp: In static member function 'static void mode_spinning_wheel()::virtualStrip::runStrip(uint16_t, uint32_t*, bool, bool, unsigned int)':
usermods/user_fx/user_fx.cpp:218:33: error: 'random16' was not declared in this scope
           state[VELOCITY_IDX] = random16(200, 900) * 655;   // fixed-point velocity scaling (approx. 65536/100)
                                 ^~~~~~~~
usermods/user_fx/user_fx.cpp:226:44: error: 'random16' was not declared in this scope
           state[SLOWDOWN_TIME_IDX] = now + random16(2000, 6000);
                                            ^~~~~~~~

==> should we add some pseudo-random stuff to the lib, or is hw_random() equally fast?

Compiling .pio\build\usermods\src\FX.cpp.o
wled00/FX.cpp: In function 'void mode_ColorClouds()':
wled00/FX.cpp:5010:12: error: 'hsv2rgb' was not declared in this scope
     else { hsv2rgb(CHSV32(hue, 255, vol), pixel); }
            ^~~~~~~

==> what was the CHSV32 equivalent for hsv2rgb ?

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
usermods/user_fx/user_fx.cpp (1)

149-159: ⚠️ Potential issue | 🔴 Critical

Replace the removed FastLED RNG calls before this lands.

The palette-call swap on Line 317 is fine, but mode_spinning_wheel() still references random16_set_seed, random16_add_entropy, and random16, and CI is already failing on those symbols. This file will not build after the FastLED removal until those calls are migrated to the PRNG class instance (matching the pattern in wled00/FX.cpp:86) or to hw_random* functions if determinism is not required.

Also applies to: 218-228

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@usermods/user_fx/user_fx.cpp` around lines 149 - 159, mode_spinning_wheel and
its surrounding init currently call FastLED RNG symbols (random16_set_seed,
random16_add_entropy, random16) which were removed; replace those calls with the
project's PRNG instance methods (use the same PRNG usage pattern as in FX.cpp
where PRNG.seed()/PRNG.rand16()/PRNG.addEntropy() are used) or call
hw_random16()/hw_random* directly if you don't need deterministic PRNG state;
update references to random16_set_seed -> PRNG.seed(hw_random16() or value),
random16_add_entropy -> PRNG.addEntropy(hw_random16()), and random16 ->
PRNG.rand16() (or hw_random16()) and ensure SEGENV/aux1 handling and any cached
scaling logic remains unchanged in mode_spinning_wheel and the other affected
block.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@usermods/user_fx/user_fx.cpp`:
- Around line 149-159: mode_spinning_wheel and its surrounding init currently
call FastLED RNG symbols (random16_set_seed, random16_add_entropy, random16)
which were removed; replace those calls with the project's PRNG instance methods
(use the same PRNG usage pattern as in FX.cpp where
PRNG.seed()/PRNG.rand16()/PRNG.addEntropy() are used) or call
hw_random16()/hw_random* directly if you don't need deterministic PRNG state;
update references to random16_set_seed -> PRNG.seed(hw_random16() or value),
random16_add_entropy -> PRNG.addEntropy(hw_random16()), and random16 ->
PRNG.rand16() (or hw_random16()) and ensure SEGENV/aux1 handling and any cached
scaling logic remains unchanged in mode_spinning_wheel and the other affected
block.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 6c14093d-dc6d-458a-a157-e9e08ff990b4

📥 Commits

Reviewing files that changed from the base of the PR and between 0ec06f4 and ffaefe5.

📒 Files selected for processing (1)
  • usermods/user_fx/user_fx.cpp

@DedeHai
Copy link
Collaborator Author

DedeHai commented Mar 12, 2026

@DedeHai two newer effects still have small compatibility problems:

I only pulled in main, I did not test it yet. The main issue is: I did so many optimizations strewn accross several PRs that all were hanging in there for way too long, now I do not remember which version was the most optimized... so I took a best guess when merging Main back in. It may have broken some things. I still need to test low-brightness colors again in case I optimized and now de-optimized with this PR.
also some of the color functions had different versions / optimizations but if memory serves me right this PR contains most of the optimizations and whatever optimizations are in this PR I speed-tested at least on C3 and ESP32.

==> should we add some pseudo-random stuff to the lib, or is hw_random() equally fast?

hw_random is "instant" it just reads a hardware register and truncates, so should always be fastest. PRNG is only to be used if predictable/repeatable randomness is required.

==> what was the CHSV32 equivalent for hsv2rgb ?

same. its an overloaded function (or it used to be or will be, whichever is the most "future" PR) :)

@DedeHai
Copy link
Collaborator Author

DedeHai commented Mar 12, 2026

Before I update this PR: are there any concerns regarding license (i.e. how I solved it in this PR)

@DedeHai I think you did even more than what was needed, by creating a separate component that stays on MIT terms. So all good 🎉

I did it on purpose, I do not want to claim ownership of those functions, if Fastled wants the optimized versions back (after they clean up after their AI's ;) ) they can use my versions.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
wled00/FX.cpp (1)

6133-6153: ⚠️ Potential issue | 🔴 Critical

Fix coordinate and error field overflow in Crazy Bees effect.

The Bee structure uses uint8_t for positions while aimed() receives uint16_t matrix dimensions and calls random8(uint8_t min, uint8_t lim), causing implicit truncation. On matrices > 256 pixels wide or tall, bounds are truncated (e.g., width 512 becomes 0), preventing bees from reaching far edges. Additionally, deltaX, deltaY, and error fields are int8_t but can hold values exceeding ±127 during Bresenham calculations, causing integer overflow. Widen the position/error fields to uint16_t/int16_t, pass random16() calls with a per-segment PRNG instance seeded from SEGENV rather than the shared global prng.

Suggested fix
 typedef struct Bee {
-  uint8_t posX, posY, aimX, aimY, hue;
-  int8_t deltaX, deltaY, signX, signY, error;
-  void aimed(uint16_t w, uint16_t h) {
-    aimX   = prng.random8(0, w);
-    aimY   = prng.random8(0, h);
-    hue    = prng.random8();
+  uint16_t posX, posY, aimX, aimY;
+  uint8_t  hue;
+  int16_t  deltaX, deltaY, error;
+  int8_t   signX, signY;
+  void aimed(uint16_t w, uint16_t h, PRNG& rng) {
+    aimX   = rng.random16(w);
+    aimY   = rng.random16(h);
+    hue    = rng.random8();
     deltaX = abs(aimX - posX);
     deltaY = abs(aimY - posY);
     signX  = posX < aimX ? 1 : -1;
     signY  = posY < aimY ? 1 : -1;
     error  = deltaX - deltaY;
   };
 } bee_t;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/FX.cpp` around lines 6133 - 6153, The bee_t structure and its aimed()
initializer must be updated to avoid truncation and overflow: change posX/posY
to uint16_t and deltaX/deltaY/error to int16_t, update the aimed(uint16_t w,
uint16_t h) signature to use those types, and replace prng.random8(...) calls
with prng.random16(...) (or random16(min, lim)) so matrix dimensions >256 are
handled correctly; also stop using the shared global prng for per-segment
initialization—instantiate or derive a local PRNG seeded from SEGENV (e.g., seed
= SEGENV.seed or strip.now stored per-segment) and use that when seeding and
generating random positions/aims in the SEGENV.call == 0 initialization block.
♻️ Duplicate comments (2)
wled00/colors.h (1)

178-183: ⚠️ Potential issue | 🟠 Major

Avoid undefined behavior in adjust_hue().

The comment says hueshift can be negative, but Line 181 does hueshift << 8. In C++, left-shifting a negative signed value is undefined behavior, so this helper can produce compiler-dependent hue changes.

🛠️ Proposed fix
   inline void adjust_hue(int hueshift) __attribute__((always_inline)) {
     CHSV32 hsv = *this;
-    hsv.h += hueshift << 8;
+    hsv.h = uint16_t(int32_t(hsv.h) + int32_t(hueshift) * 256);
     hsv2rgb_spectrum(hsv, *this);
   }
#!/bin/bash
# Confirm the signed left shift remains in hue-adjust paths.
sed -n '178,183p' wled00/colors.h
rg -nP 'hueshift\s*<<\s*8|hueShift\s*<<\s*8' wled00/colors.h -C2
fd 'colors\.cpp$' wled00 -x rg -nP 'hueshift\s*<<\s*8|hueShift\s*<<\s*8' {} -C2

Expected: no signed hue shifts remain; use signed multiplication before casting back.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/colors.h` around lines 178 - 183, The adjust_hue helper uses a left
shift on a potentially negative signed value (hueshift << 8) which is undefined;
change adjust_hue in colors.h so you compute the 8-bit-scaled offset using a
defined signed operation (e.g. multiply hueshift by 256 into a signed integer)
and then add that to CHSV32::h before calling hsv2rgb_spectrum; update the line
modifying hsv.h (the current "hsv.h += hueshift << 8") to use a signed
multiplication/cast to avoid shifting a negative value and ensure correct
wrapping behavior.
wled00/FX.cpp (1)

1813-1817: ⚠️ Potential issue | 🟡 Minor

Capture the shared PRNG state before the first-call draws.

Line 1817 runs after Line 1814 and Line 1815 already consumed from prng, so the first render still perturbs the shared sequence even though you restore it at the end.

🛠️ Minimal fix
-  if (SEGENV.call == 0) {
-    SEGENV.step = RGBW32(prng.random8(), prng.random8(), prng.random8(), 0);
-    SEGENV.aux0 = prng.random16();
-  }
-  unsigned prevSeed = prng.getSeed(); // save seed so we can restore it at the end of the function
+  unsigned prevSeed = prng.getSeed(); // save seed so we can restore it at the end of the function
+  if (SEGENV.call == 0) {
+    SEGENV.step = RGBW32(prng.random8(), prng.random8(), prng.random8(), 0);
+    SEGENV.aux0 = prng.random16();
+  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/FX.cpp` around lines 1813 - 1817, Currently the code saves the PRNG
state with prng.getSeed() after calling prng.random8()/random16(), which still
mutates the shared sequence; move the call to prng.getSeed() so it runs before
any calls to prng.random8() or prng.random16() (i.e., capture prevSeed at the
top of the branch that checks SEGENV.call == 0) so you can restore the exact
original state at function end; update references around SEGENV.call,
SEGENV.step, SEGENV.aux0 and the subsequent prng.restore/seed-restore logic to
use that saved prevSeed.
🧹 Nitpick comments (5)
wled00/colors.h (1)

4-4: Make colors.h self-contained for std::vector.

colors.h declares std::vector<CRGBPalette16> on line 64 but never includes <vector>. fastled_slim.h does not provide this header transitively, so the code relies on include order from consuming files. This breaks the header's self-containment.

♻️ Suggested change
+#include <vector>
 `#include` "src/dependencies/fastled_slim/fastled_slim.h"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/colors.h` at line 4, The header colors.h is not self-contained: it
uses std::vector<CRGBPalette16> (and possibly other std types) but does not
include <vector>; add the missing standard header(s) to colors.h (e.g., `#include`
<vector>) so that CRGBPalette16 and std::vector are usable without relying on
transitive includes from fastled_slim.h; update colors.h to directly include the
required <vector> header (and any other needed <utility>/<cstddef> if used) so
the declarations compile when colors.h is included standalone.
wled00/FX.cpp (1)

10461-10486: Reuse the precomputed mids value.

Line 10485 shadows Line 10461, so the FFT reduction and sqrt32_bw() still run once per particle in this hot path. Compute it once, ideally only when SEGMENT.check1 is enabled.

♻️ Small cleanup
-  int mids = sqrt32_bw((int)fftResult[5] + (int)fftResult[6] + (int)fftResult[7] + (int)fftResult[8] + (int)fftResult[9] + (int)fftResult[10]); // average the mids, bin 5 is ~500Hz, bin 10 is ~2kHz (see audio_reactive.h)
+  const int mids = SEGMENT.check1
+    ? sqrt32_bw((int)fftResult[5] + (int)fftResult[6] + (int)fftResult[7] + (int)fftResult[8] + (int)fftResult[9] + (int)fftResult[10])
+    : 0;
@@
-      int mids = sqrt32_bw((int)fftResult[5] + (int)fftResult[6] + (int)fftResult[7] + (int)fftResult[8] + (int)fftResult[9] + (int)fftResult[10]); // average the mids, bin 5 is ~500Hz, bin 10 is ~2kHz (see audio_reactive.h)
       PartSys->particles[i].hue += (mids * perlin8(PartSys->particles[i].x << 2, SEGMENT.step << 2)) >> 9; // color by perlin noise from mid frequencies
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/FX.cpp` around lines 10461 - 10486, The variable mids is recomputed
inside the particle loop (using sqrt32_bw on fftResult bins) causing expensive
repeated work; compute mids once outside the for-loop only when SEGMENT.check1
is true (or if you need it earlier, compute it once when SEGMENT.check1 is
enabled) and then reuse that precomputed mids inside the loop where
PartSys->particles[i].hue is modified; update references to the inner
declaration so they use the outer mids and remove the duplicated sqrt32_bw call
to avoid per-particle FFT reduction.
wled00/src/dependencies/fastled_slim/fastled_slim.h (1)

99-100: Nitpick: Typo "UNITIALIZED" should be "UNINITIALIZED".

-  // `@warning` default values are UNITIALIZED!
+  // `@warning` Default values are UNINITIALIZED!
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/src/dependencies/fastled_slim/fastled_slim.h` around lines 99 - 100,
Fix the typo in the comment above the CHSV default constructor: change
"UNITIALIZED" to "UNINITIALIZED" in the comment that precedes the inline CHSV()
__attribute__((always_inline)) = default; declaration so the warning reads "//
`@warning` default values are UNINITIALIZED!".
wled00/src/dependencies/fastled_slim/fastled_slim.cpp (1)

231-231: Nitpick: Typo "quadradic" should be "quadratic".

-// quadradic ease function (S-curve: x^2)
+// quadratic ease function (S-curve: x^2)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/src/dependencies/fastled_slim/fastled_slim.cpp` at line 231, Fix the
spelling typo in the comment describing the ease function: change "quadradic" to
"quadratic" in the comment that currently reads "quadradic ease function
(S-curve: x^2)" so the comment correctly reads "quadratic ease function
(S-curve: x^2)"; locate this comment near the ease function implementation in
fastled_slim.cpp to update the text.
wled00/util.cpp (1)

1002-1010: Optional: gradient1D is no longer force-inlined unlike gradient2D/gradient3D.

The comment on line 1002 states "forcing inline produces smaller code and makes it 3x faster," but gradient1D was changed from static inline __attribute__((always_inline)) to just static, while gradient2D and gradient3D retain the force-inline attribute. This inconsistency may be intentional, but if the 3× speedup applies to 1D as well, consider restoring the inline attribute for consistency.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/util.cpp` around lines 1002 - 1010, The comment notes that forcing
inline yields much smaller/faster code but gradient1D was changed to plain
static while gradient2D/gradient3D still use force-inlining; restore the
original inline attribute on gradient1D (e.g., add back static inline
__attribute__((always_inline))) so its definition matches gradient2D/gradient3D
and benefits from the same optimization; locate the function named gradient1D
and update its declaration to match the inlining used for gradient2D/gradient3D
(keeping existing logic that uses hashToGradient and PERLIN_SHIFT).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@wled00/colors.h`:
- Line 4: The license attribution path in colors.h is inconsistent with the
renamed dependency folder; update the license reference lines that currently
point to /src/dependencies/fastled/LICENSE.txt to the new path under
src/dependencies/fastled_slim/LICENSE.txt so the in-file attribution matches the
`#include` "src/dependencies/fastled_slim/fastled_slim.h" (locate the include and
the license lines in colors.h and change the path text to
fastled_slim/LICENSE.txt).

In `@wled00/src/dependencies/fastled_slim/fastled_slim.cpp`:
- Line 13: The comment for the bitmask on variable `offset` is wrong: `offset =
h & 0x1FFF` yields a 13-bit range (0..8191), not 0..31; update the inline
comment next to `offset` in `fastled_slim.cpp` to indicate the correct range
(e.g., `// 0..8191 (13 bits)`) and optionally note that `third16 = offset *
21846` / `third = third16 >> 21` expect that 13-bit range so the arithmetic
remains correct.

In `@wled00/src/dependencies/fastled_slim/fastled_slim.h`:
- Around line 609-621: Replace unsafe direct dereferences of PROGMEM in the
CRGBPalette16(const TProgmemRGBPalette16& rhs) constructor and the
CRGBPalette16::operator=(const TProgmemRGBPalette16& rhs) with proper PROGMEM
reads; include <pgmspace.h> and use pgm_read_dword(rhs + i) when loading each
32-bit entry from TProgmemRGBPalette16 in both CRGBPalette16::CRGBPalette16 and
CRGBPalette16::operator= to ensure ESP8266 compatibility and avoid flash access
crashes.

In `@wled00/util.cpp`:
- Around line 400-422: beatsin88_t currently treats its beats_per_minute_88 as
already Q8.8 and thus slows timings when callers pass plain integer BPMs; fix by
encoding plain BPM inputs to Q8.8 inside beatsin88_t (use the same heuristic as
beat16: if beats_per_minute_88 < 256 then left-shift by 8) before calling
beat88; update beatsin88_t (and any internal uses of the beats_per_minute_88
local) to perform this conversion so FX.cpp callers can keep passing plain BPMs,
and reference beat88 and beat16 while making the change.

---

Outside diff comments:
In `@wled00/FX.cpp`:
- Around line 6133-6153: The bee_t structure and its aimed() initializer must be
updated to avoid truncation and overflow: change posX/posY to uint16_t and
deltaX/deltaY/error to int16_t, update the aimed(uint16_t w, uint16_t h)
signature to use those types, and replace prng.random8(...) calls with
prng.random16(...) (or random16(min, lim)) so matrix dimensions >256 are handled
correctly; also stop using the shared global prng for per-segment
initialization—instantiate or derive a local PRNG seeded from SEGENV (e.g., seed
= SEGENV.seed or strip.now stored per-segment) and use that when seeding and
generating random positions/aims in the SEGENV.call == 0 initialization block.

---

Duplicate comments:
In `@wled00/colors.h`:
- Around line 178-183: The adjust_hue helper uses a left shift on a potentially
negative signed value (hueshift << 8) which is undefined; change adjust_hue in
colors.h so you compute the 8-bit-scaled offset using a defined signed operation
(e.g. multiply hueshift by 256 into a signed integer) and then add that to
CHSV32::h before calling hsv2rgb_spectrum; update the line modifying hsv.h (the
current "hsv.h += hueshift << 8") to use a signed multiplication/cast to avoid
shifting a negative value and ensure correct wrapping behavior.

In `@wled00/FX.cpp`:
- Around line 1813-1817: Currently the code saves the PRNG state with
prng.getSeed() after calling prng.random8()/random16(), which still mutates the
shared sequence; move the call to prng.getSeed() so it runs before any calls to
prng.random8() or prng.random16() (i.e., capture prevSeed at the top of the
branch that checks SEGENV.call == 0) so you can restore the exact original state
at function end; update references around SEGENV.call, SEGENV.step, SEGENV.aux0
and the subsequent prng.restore/seed-restore logic to use that saved prevSeed.

---

Nitpick comments:
In `@wled00/colors.h`:
- Line 4: The header colors.h is not self-contained: it uses
std::vector<CRGBPalette16> (and possibly other std types) but does not include
<vector>; add the missing standard header(s) to colors.h (e.g., `#include`
<vector>) so that CRGBPalette16 and std::vector are usable without relying on
transitive includes from fastled_slim.h; update colors.h to directly include the
required <vector> header (and any other needed <utility>/<cstddef> if used) so
the declarations compile when colors.h is included standalone.

In `@wled00/FX.cpp`:
- Around line 10461-10486: The variable mids is recomputed inside the particle
loop (using sqrt32_bw on fftResult bins) causing expensive repeated work;
compute mids once outside the for-loop only when SEGMENT.check1 is true (or if
you need it earlier, compute it once when SEGMENT.check1 is enabled) and then
reuse that precomputed mids inside the loop where PartSys->particles[i].hue is
modified; update references to the inner declaration so they use the outer mids
and remove the duplicated sqrt32_bw call to avoid per-particle FFT reduction.

In `@wled00/src/dependencies/fastled_slim/fastled_slim.cpp`:
- Line 231: Fix the spelling typo in the comment describing the ease function:
change "quadradic" to "quadratic" in the comment that currently reads "quadradic
ease function (S-curve: x^2)" so the comment correctly reads "quadratic ease
function (S-curve: x^2)"; locate this comment near the ease function
implementation in fastled_slim.cpp to update the text.

In `@wled00/src/dependencies/fastled_slim/fastled_slim.h`:
- Around line 99-100: Fix the typo in the comment above the CHSV default
constructor: change "UNITIALIZED" to "UNINITIALIZED" in the comment that
precedes the inline CHSV() __attribute__((always_inline)) = default; declaration
so the warning reads "// `@warning` default values are UNINITIALIZED!".

In `@wled00/util.cpp`:
- Around line 1002-1010: The comment notes that forcing inline yields much
smaller/faster code but gradient1D was changed to plain static while
gradient2D/gradient3D still use force-inlining; restore the original inline
attribute on gradient1D (e.g., add back static inline
__attribute__((always_inline))) so its definition matches gradient2D/gradient3D
and benefits from the same optimization; locate the function named gradient1D
and update its declaration to match the inlining used for gradient2D/gradient3D
(keeping existing logic that uses hashToGradient and PERLIN_SHIFT).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 6e61501d-7f75-4e68-98ea-9f00326fcba7

📥 Commits

Reviewing files that changed from the base of the PR and between ffaefe5 and 345d7c3.

📒 Files selected for processing (8)
  • usermods/user_fx/user_fx.cpp
  • wled00/FX.cpp
  • wled00/colors.h
  • wled00/palettes.cpp
  • wled00/src/dependencies/fastled_slim/LICENSE.txt
  • wled00/src/dependencies/fastled_slim/fastled_slim.cpp
  • wled00/src/dependencies/fastled_slim/fastled_slim.h
  • wled00/util.cpp
✅ Files skipped from review due to trivial changes (2)
  • wled00/src/dependencies/fastled_slim/LICENSE.txt
  • usermods/user_fx/user_fx.cpp
🚧 Files skipped from review as they are similar to previous changes (1)
  • wled00/palettes.cpp

@DedeHai
Copy link
Collaborator Author

DedeHai commented Mar 21, 2026

@softhack007

==> what was the CHSV32 equivalent for hsv2rgb ?

I checked, there was none, I added it now for compatibility. the "new way" is to use implicit cast: color = CRGBW(CHSV32)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (5)
wled00/colors.h (2)

58-58: ⚠️ Potential issue | 🔴 Critical

Parameter order mismatch between declaration and definition.

The declaration here has (int32_t hueShift, int32_t valueChange, int32_t satChange), but the implementation in colors.cpp:99 uses (int32_t hueShift, int32_t satChange, int32_t valueChange). This will cause callers to silently swap saturation and value changes.

Proposed fix
-void adjust_color(CRGBW& rgb, int32_t hueShift, int32_t valueChange, int32_t satChange);
+void adjust_color(CRGBW& rgb, int32_t hueShift, int32_t satChange, int32_t valueChange);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/colors.h` at line 58, The declaration of adjust_color in colors.h
lists parameters as (int32_t hueShift, int32_t valueChange, int32_t satChange)
but the implementation adjust_color in colors.cpp uses (int32_t hueShift,
int32_t satChange, int32_t valueChange); make the two signatures identical by
reordering the parameters to the intended order (preferably (hueShift,
satChange, valueChange) or vice versa) and update any callers to match the
chosen order so saturation and value changes aren't swapped at runtime.

179-183: ⚠️ Potential issue | 🟠 Major

Left-shift of potentially negative hueshift is undefined behavior.

Line 181 performs hueshift << 8 where hueshift is a signed int. Left-shifting a negative signed integer is undefined behavior in C++. Use multiplication instead.

Proposed fix
   inline void adjust_hue(int hueshift) __attribute__((always_inline)) {
     CHSV32 hsv = *this;
-    hsv.h += hueshift << 8;
+    hsv.h = uint16_t(int32_t(hsv.h) + int32_t(hueshift) * 256);
     hsv2rgb_spectrum(hsv, *this);
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/colors.h` around lines 179 - 183, In adjust_hue, avoid undefined
behavior from left-shifting a potentially negative signed int by replacing the
shift expression `hueshift << 8` with a multiplication (e.g., `hueshift * 256`)
or an explicit signed-to-wide-type multiply (e.g.,
`static_cast<int32_t>(hueshift) * 256`) so the math is well-defined before
adding to hsv.h; update the expression in the adjust_hue method accordingly.
wled00/colors.cpp (3)

375-381: ⚠️ Potential issue | 🔴 Critical

Division by zero for gray colors (when delta == 0).

Lines 378-380 divide by delta, but for non-black gray colors (where r == g == b != 0), delta will be 0. The comment on line 377 states the early return is omitted for speed, but this causes undefined behavior.

Proposed fix
     hsv.v = maxval;
     delta = maxval - minval;
+    if (delta == 0) {
+      hsv.h = 0;
+      hsv.s = 0;
+      return;
+    }
     hsv.s = delta == maxval ? 255 : (255 * delta) / maxval; // faster on fully saturated colors, slightly slower otherwise
-    // note: early return if s==0 is omitted here to increase speed as gray values are rarely used
     if (maxval == r) hsv.h = (10923 * (g - b)) / delta;
     else if (maxval == g)  hsv.h = 21845 + (10923 * (b - r)) / delta;
     else hsv.h = 43690 + (10923 * (r - g)) / delta;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/colors.cpp` around lines 375 - 381, The code computes delta = maxval -
minval then unconditionally divides by delta when computing hsv.h, which causes
divide-by-zero for gray colors (r==g==b). Fix by handling the delta==0 case
before any division: when delta == 0 set hsv.s = 0 and set hsv.h to a safe value
(e.g., 0 or leave unchanged) and skip the hue-division branches; otherwise
compute hsv.s and run the existing hue formulas that divide by delta. Update the
block that computes hsv.s and hsv.h (references: delta, maxval, minval, hsv.s,
hsv.h, and r/g/b) so divisions only occur when delta > 0.

52-53: ⚠️ Potential issue | 🔴 Critical

White channel missing from max calculation can cause division by zero.

The maxval calculation ignores the white channel w. If only the white channel overflows (e.g., color_add(RGBW32(0,0,0,255), RGBW32(0,0,0,255), true)), maxval will be 0 causing division by zero on line 53.

Proposed fix
-      uint32_t maxval = (r > g) ? ((r > b) ? r : b) : ((g > b) ? g : b); // find max value. note: faster than using max() function or on par
+      uint32_t maxval = r;
+      if (g > maxval) maxval = g;
+      if (b > maxval) maxval = b;
+      if (w > maxval) maxval = w;
       const uint32_t scale = (uint32_t(255)<<8) / maxval; // division of two 8bit (shifted) values does not work -> use bit shifts and multiplaction instead
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/colors.cpp` around lines 52 - 53, The max value calculation in
colors.cpp uses only r, g, b (variable maxval) which ignores the white channel w
and can lead to division-by-zero in the subsequent scale calculation; update the
max computation used by color_add/RGBW32 logic to include w (i.e., compute
maxval as the maximum of r, g, b, and w) and additionally guard the division by
ensuring maxval is non-zero (e.g., set to 1 if zero) before computing scale so
the scale computation cannot divide by zero.

99-107: ⚠️ Potential issue | 🟠 Major

Left-shift of potentially negative hueShift is undefined behavior.

Line 103 performs hueShift << 8 where hueShift is int32_t. Left-shifting a negative signed integer is undefined behavior. The same issue exists in colors.h:181.

Proposed fix
-  hsv.h += (hueShift << 8); // shift hue (hue is 16 bits)
+  hsv.h = uint16_t(int32_t(hsv.h) + hueShift * 256); // shift hue (hue is 16 bits)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/colors.cpp` around lines 99 - 107, The code left-shifts a signed int
(hueShift << 8) which is undefined for negative values; change the shift to a
safe arithmetic expression (e.g. compute an int32_t delta = hueShift * 256 or
cast to unsigned before shifting: (int32_t)((uint32_t)hueShift << 8)) and use
that delta when updating hsv.h in adjust_color (and apply the same fix to the
other occurrence referenced in colors.h:181, replacing the unsafe left-shift
with the safe multiplication/cast approach).
🧹 Nitpick comments (1)
wled00/src/dependencies/fastled_slim/fastled_slim.h (1)

4-4: Consider using <string.h> instead of <cstring> for embedded compatibility.

Static analysis reports 'cstring' file not found. While <cstring> is standard C++, some embedded toolchains prefer the C-style <string.h> header. Since only memset and memmove are used, switching to <string.h> would improve portability.

Proposed fix
-#include <cstring> // for mem operations
+#include <string.h> // for mem operations
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wled00/src/dependencies/fastled_slim/fastled_slim.h` at line 4, Replace the
C++ header with the C header for better embedded-toolchain compatibility: in
fastled_slim.h remove the `#include` <cstring> and use `#include` <string.h>
instead, since only C-style functions (memset, memmove) are used; update any
comments if needed to reflect the change and ensure references like
memset/memmove in this file continue to compile with the C header.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@wled00/src/dependencies/fastled_slim/fastled_slim.h`:
- Around line 698-738: The operator= implementation for CRGBPalette16 reads
gradient entries via direct dereferences of progmem pointers (uses expressions
like u.dword = *(const uint32_t*)(progent) and *(const uint32_t*)(progent +
count)), which breaks on platforms where palettes live in PROGMEM (ESP8266);
replace those direct loads with safe progmem-aware reads (e.g., use
pgm_read_dword_near or memcpy_P into a local uint32_t before assigning to
u.dword) wherever you see assignments to u.dword from progent (the initial count
loop, the initial u.dword read that seeds rgbstart, and the subsequent loop read
that sets indexend), ensuring you only copy into local variables then use u to
construct CRGB and continue the existing logic in operator=.

---

Duplicate comments:
In `@wled00/colors.cpp`:
- Around line 375-381: The code computes delta = maxval - minval then
unconditionally divides by delta when computing hsv.h, which causes
divide-by-zero for gray colors (r==g==b). Fix by handling the delta==0 case
before any division: when delta == 0 set hsv.s = 0 and set hsv.h to a safe value
(e.g., 0 or leave unchanged) and skip the hue-division branches; otherwise
compute hsv.s and run the existing hue formulas that divide by delta. Update the
block that computes hsv.s and hsv.h (references: delta, maxval, minval, hsv.s,
hsv.h, and r/g/b) so divisions only occur when delta > 0.
- Around line 52-53: The max value calculation in colors.cpp uses only r, g, b
(variable maxval) which ignores the white channel w and can lead to
division-by-zero in the subsequent scale calculation; update the max computation
used by color_add/RGBW32 logic to include w (i.e., compute maxval as the maximum
of r, g, b, and w) and additionally guard the division by ensuring maxval is
non-zero (e.g., set to 1 if zero) before computing scale so the scale
computation cannot divide by zero.
- Around line 99-107: The code left-shifts a signed int (hueShift << 8) which is
undefined for negative values; change the shift to a safe arithmetic expression
(e.g. compute an int32_t delta = hueShift * 256 or cast to unsigned before
shifting: (int32_t)((uint32_t)hueShift << 8)) and use that delta when updating
hsv.h in adjust_color (and apply the same fix to the other occurrence referenced
in colors.h:181, replacing the unsafe left-shift with the safe
multiplication/cast approach).

In `@wled00/colors.h`:
- Line 58: The declaration of adjust_color in colors.h lists parameters as
(int32_t hueShift, int32_t valueChange, int32_t satChange) but the
implementation adjust_color in colors.cpp uses (int32_t hueShift, int32_t
satChange, int32_t valueChange); make the two signatures identical by reordering
the parameters to the intended order (preferably (hueShift, satChange,
valueChange) or vice versa) and update any callers to match the chosen order so
saturation and value changes aren't swapped at runtime.
- Around line 179-183: In adjust_hue, avoid undefined behavior from
left-shifting a potentially negative signed int by replacing the shift
expression `hueshift << 8` with a multiplication (e.g., `hueshift * 256`) or an
explicit signed-to-wide-type multiply (e.g., `static_cast<int32_t>(hueshift) *
256`) so the math is well-defined before adding to hsv.h; update the expression
in the adjust_hue method accordingly.

---

Nitpick comments:
In `@wled00/src/dependencies/fastled_slim/fastled_slim.h`:
- Line 4: Replace the C++ header with the C header for better embedded-toolchain
compatibility: in fastled_slim.h remove the `#include` <cstring> and use `#include`
<string.h> instead, since only C-style functions (memset, memmove) are used;
update any comments if needed to reflect the change and ensure references like
memset/memmove in this file continue to compile with the C header.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 1e7964d3-94b3-4657-b3c6-651dca5638ae

📥 Commits

Reviewing files that changed from the base of the PR and between 0e16d96 and 82e29be.

📒 Files selected for processing (6)
  • wled00/colors.cpp
  • wled00/colors.h
  • wled00/palettes.cpp
  • wled00/src/dependencies/fastled_slim/fastled_slim.cpp
  • wled00/src/dependencies/fastled_slim/fastled_slim.h
  • wled00/util.cpp
🚧 Files skipped from review as they are similar to previous changes (2)
  • wled00/src/dependencies/fastled_slim/fastled_slim.cpp
  • wled00/util.cpp

@DedeHai
Copy link
Collaborator Author

DedeHai commented Mar 21, 2026

@coderabbitai please make suggestions on a practical testing procedure to check if all color conversions and FX still work the same. i.e. 1D/2D setup, RGB/RGBW LEDs and which effects to test.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 21, 2026

Tip

For best results, initiate chat on the files or code changes.

@DedeHai Here is a structured, practical testing procedure organized by hardware configuration and concern area:


WLED FastLED Replacement — Practical Testing Guide

1. Hardware Configurations to Cover

Config Why
RGB strip (WS2812B / SK6812 RGB) Baseline — all color math, no white channel
RGBW strip (SK6812 RGBW) Validates white channel preservation in CRGBW, adjust_color, color_wheel
2D matrix (RGB) Validates 2D blur, distortion, soap, sunradiation, gameoflife
2D matrix (RGBW) Validates CRGBW white channel in 2D paths
AudioReactive-capable device Validates mode_particle1DsonicStream mids computation

2. Core Color Conversion Checks (HSV ↔ RGB)

These are the highest-risk changes. Focus on visual correctness of hue, saturation, and brightness.

Procedure:

  • Set a single-color segment to solid color, step through the full hue wheel via the color picker (0°–360°) and verify smooth, continuous hue transitions with no unexpected jumps or color shifts.
  • Set saturation to 0 → should produce pure white on RGB, pure white (all channels) on RGBW.
  • Set brightness to 0 → should produce pure black.
  • Compare Color Clouds effect (uses CRGBW(CHSV32(...)) new path) visually against a known-good reference build, paying attention to hue accuracy.

Effects to run: Solid, Color Clouds, Pride 2015, Colorwaves


3. Palette Rendering

The default palette was changed from PartyColors_pPartyColors_gc22 and ColorFromPaletteWLED was renamed to ColorFromPalette.

Procedure:

  • Cycle through all 50+ built-in palettes on a 1D strip and verify no garbled or black-only output.
  • Specifically test: Party, Rainbow, Rainbow Stripe, Cloud, Lava, Ocean, Forest (newly defined in palettes.cpp).
  • Check that palette transitions (blend mode) produce smooth gradients, not hard steps.
  • Run Palette effect and Colorwaves — verify color continuity.

4. Effects That Were Directly Modified

Run each of these and compare visually against a reference build if possible, or look for obvious anomalies (stuck pixels, wrong colors, flickering).

Effect What Changed What to Watch
Random Chase prng replaces random16; seed save/restore Consistent repeating chase pattern; no frozen/stuck state
Color Twinkle fract8uint8_t for fade Smooth fade-in/fade-out, no stuck bright pixels
TwinkleFOX Returns CRGBW, background blending updated Background color (seg color 2) visible and blended correctly
Twinkle Up prng seed save/restore, prng.random8() Repeatable twinkle pattern frame-to-frame
Color Clouds CRGBW(CHSV32(...)) new path Smooth cloud-like color drift, correct hues
2D Distortion Waves rgb2hsv replaces rgb2hsv_approximate No sharp hue jumps or color artifacts
2D Soap ease8InOutCubic replaces ease8InOutApprox Smooth easing on bubble boundaries
2D Sunradiation Shift-based quantization replaces divide Sunburst pattern renders correctly, no blocky artifacts
2D Game of Life hw_random8 replaces random8 Normal evolution, no freeze
2D Crazy Bees prng seed Bees move naturally
Particle Fireworks hw_random() for circular selection Fireworks spread in all directions
Particle 1D Sonic Stream New mids computation from FFT bins 5–10 Audio-reactive modulation responds to mid frequencies

5. IR Remote / Color Adjustment (RGBW-specific)

The ir.cpp color adjustment logic was rewritten using CRGBW.adjust_hue() and CHSV32.

Procedure (requires IR remote):

  • Set a solid color on an RGBW strip.
  • Use IR hue-up / hue-down commands and verify smooth hue rotation with white channel (W) preserved.
  • Use IR saturation-up / saturation-down and verify gradual desaturation to white and back.
  • Verify the color is correctly applied to all selected segments, not just the first.

6. Blur Functions (2D Matrix)

blurCols and blurRows changed from fract8 to uint8_t parameter.

Procedure:

  • Run Blur effect on a 2D matrix.
  • Run 2D Soap, 2D Distortion Waves — both use blur internally.
  • Look for: uniform blur, no unblurred rows/columns, no pixel smearing artifacts at edges.

7. Beat / Timing Functions

beat88, beat16, beat8 are new; beatsin*_t parameter types changed from accum88 to uint16_t.

Procedure:

  • Run any BPM-synced effect (e.g., Juggle, Bpm, Dancing Shadows) and set BPM via the slider.
  • Verify the animation tempo scales correctly and smoothly across the BPM range (e.g., 10, 60, 120, 240 BPM).
  • Check that beatsin effects do not produce unexpected speed changes after the parameter type change.

8. PRNG Repeatability Check

PRNG is used in effects requiring deterministic randomness (Random Chase, TwinkleFOX, Twinkle Up, Crazy Bees).

Procedure:

  • Start the effect, note the pattern.
  • Save and reload the config (or restart the device).
  • Verify the pattern restores to the same state (seed save/restore is the mechanism).

9. RGBW White Channel Preservation

Procedure:

  • On an RGBW strip, set segment color 1 to pure white (W=255, R=G=B=0 if the UI allows, or #FFFFFF which should map to the white channel).
  • Run Solid, Color Wipe, TwinkleFOX with white background.
  • Verify the W channel is lit and the RGB channels remain dark (true white, not RGB-mixed white).
  • Run Color Clouds and observe that white-ish clouds have the W channel engaged.

10. Regression Checklist Summary

  • Hue wheel smooth from 0–360 on RGB strip
  • White/black extremes correct on RGBW
  • All built-in palettes render without black/garbled output
  • Party, Rainbow, Forest, Cloud, Lava, Ocean palettes correct
  • TwinkleFOX background color visible
  • Random Chase pattern stable and repeatable
  • 2D Soap / Distortion Waves no color artifacts
  • BPM effects tempo correct at 60 and 120 BPM
  • IR hue/saturation adjust works on RGBW
  • Particle Fireworks radiate in all directions
  • AudioReactive sonic stream responds to mids

🧠 Learnings used
Learnt from: DedeHai
Repo: wled/WLED PR: 4798
File: wled00/FX.cpp:7531-7533
Timestamp: 2025-08-26T11:51:21.817Z
Learning: In WLED PR `#4798`, DedeHai confirmed that certain gamma-related calls in FX.cpp/FX_fcn.cpp/particle systems are intentional for effect-level shaping (e.g., brightness curves, TV sim, Pride 2015 pre-mix), distinct from final output gamma. Do not flag or remove these in future reviews; add comments when feasible to clarify intent.

Learnt from: BobLoeffler68
Repo: wled/WLED PR: 5109
File: wled00/FX.cpp:3174-3343
Timestamp: 2025-11-27T06:33:11.436Z
Learning: WLED Ants effect (wled00/FX.cpp): The author prefers the current velocity initialization using hw_random16(1000, 5000)/5000.0f, resulting in an effective range of ~3.6–10.0 (with VELOCITY_MIN=2.0, VELOCITY_MAX=10.0), and wants the code kept as-is with comments updated to document this behavior. Avoid suggesting changes to span the full 2.0–10.0 range in future reviews.

Learnt from: softhack007
Repo: wled/WLED PR: 5048
File: wled00/bus_manager.cpp:549-556
Timestamp: 2026-02-11T16:20:02.872Z
Learning: ESP-IDF V5 requires a complete rewrite of the LEDC (PWM/analog LED) subsystem in WLED. The framework now manages LEDC channels internally (unlike V4 where WLED managed them directly), and dithering functionality may not work as it did before. Direct LEDC struct access (e.g., `LEDC.channel_group[gr].channel[ch].duty.duty`) used in the current implementation will need to be replaced with proper HAL API calls. This affects `wled00/bus_manager.cpp` BusPwm class and related pin manager LEDC allocation code.

Learnt from: softhack007
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2025-11-14T13:37:11.994Z
Learning: In WLED code reviews, when code is modified or added, look for "magic numbers" (hardcoded numeric literals) and suggest replacing them with appropriate defined constants when those constants are meaningful in the context of the PR. For example, the hardcoded value 32 should be replaced with WLED_MAX_SEGNAME_LEN when it represents a segment name length limit. This improves code maintainability and reduces the risk of inconsistencies.

Learnt from: BobLoeffler68
Repo: wled/WLED PR: 5293
File: usermods/user_fx/user_fx.cpp:149-151
Timestamp: 2026-01-24T18:51:09.740Z
Learning: In WLED's Spinning Wheel effect (usermods/user_fx/user_fx.cpp), the color scale calculation (SEGENV.aux1) must match the position modulo range. When position uses `pos = (pos_fixed >> 16) % SEGLEN`, the color scale must be `aux1 = (255 << 8) / SEGLEN` to distribute the palette evenly across the full position range. Using vstripLen for color scaling while position uses SEGLEN causes palette colors to cycle too quickly and appear incomplete.

Learnt from: softhack007
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2026-03-15T13:21:32.008Z
Learning: In WLED code reviews (and generally), when a PR discussion involves "is this worth doing?" uncertainty about a proposed reliability, safety, or data-integrity mechanism (e.g. CRC checks, backups, power-loss protection), suggest performing a software FMEA (Failure Mode and Effects Analysis). The FMEA should: enumerate failure modes for the feared event, assess each mitigation's effectiveness per failure mode, note common-cause/common-mode failures, and rate credibility for the typical WLED use case. This was successfully used in PR `#5421` to evaluate CRC-based config file protection and was well-received by the WLED maintainers (softhack007 explicitly requested this be remembered for future reviews).

Learnt from: DedeHai
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2026-01-03T14:38:09.949Z
Learning: In WLED code reviews, check that FastLED noise function aliases (`inoise8`, `inoise16`, etc.) are not used. These should be replaced with the proper WLED functions (`perlin8`, `perlin16`, etc.) as the `inoise*` functions are only aliases kept for compatibility reasons.

Learnt from: softhack007
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2025-11-14T13:37:30.955Z
Learning: In WLED code reviews, when code is modified or added, look for "magic numbers" (hardcoded numeric literals) and suggest replacing them with defined constants when meaningful constants exist in the codebase. For example, suggest replacing hardcoded "32" with WLED_MAX_SEGNAME_LEN if the context relates to segment name length limits.

Learnt from: DedeHai
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2026-01-13T21:23:35.514Z
Learning: In WLED, the global `paletteBlend` variable (wled.h:603) and the `WS2812FX::paletteBlend` member (FX.h:940) are duplicates without synchronization code. The global is loaded/saved in cfg.cpp and set via UI in set.cpp, but never copied to the strip member. This is the only such case in the codebase; other settings are either strip-only members (autoSegments, correctWB, cctFromRgb, isMatrix) or global-only (gammaCorrectCol/Bri/Val, blendingStyle).

Learnt from: DedeHai
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2026-01-03T14:38:09.949Z
Learning: In WLED code reviews, check that FastLED trigonometric function aliases are not used. Replace `sin8()` and `cos8()` with `sin8_t()` and `cos8_t()`. Replace `sin16()` and `cos16()` with `sin16_t()` and `cos16_t()`. For float versions, replace `sinf()` and `cosf()` with `sin_approx()` and `cos_approx()`. See wled_math.cpp for the proper WLED implementations.

Learnt from: softhack007
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2025-12-28T14:06:48.772Z
Learning: In WLED post-commit ee9ac947, the rendering pipeline uses per-segment buffers and per-pixel bus updates. Unmapped (0xFFFF) mapping entries are now skipped in WS2812FX::show() (no “clear to black”), which can leave physical gap LEDs with stale/random colors unless they are explicitly cleared. This is a behavior change from pre-0.16 where a full physical buffer was effectively refreshed each frame.

Learnt from: KrX3D
Repo: wled/WLED PR: 4585
File: usermods/seven_segment_display_reloaded_v2/seven_segment_display_reloaded_v2.cpp:3-5
Timestamp: 2026-02-18T00:11:02.403Z
Learning: In PR `#4585`, the seven_segment_display_reloaded_v2 usermod intentionally uses `#error` to require MQTT (`#ifdef WLED_DISABLE_MQTT `#error` "This user mod requires MQTT to be enabled." `#endif``) because the integration flow has not been tested or validated without MQTT, and failing fast at compile time is preferred over allowing potentially broken builds until a full MQTT-optional refactor is completed.

Learnt from: softhack007
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2025-11-14T13:37:30.955Z
Learning: In WLED code reviews, verify that file operations (especially file.open()) respect LittleFS filename limitations. Assume default WLED configuration with LittleFS default filename limit of 255 bytes. Do not assume extreme configuration values like WLED_MAX_SEGNAME_LEN = 512 which would not be standard configurations.

Learnt from: softhack007
Repo: wled/WLED PR: 0
File: :0-0
Timestamp: 2025-11-14T13:37:11.994Z
Learning: In WLED code reviews, file operations (especially file.open()) should be checked to ensure they respect LittleFS filename limitations. The default LittleFS filename limit is 255 bytes (LFS_NAME_MAX). Reviews should assume default WLED configuration defines and not extreme edge-case values (e.g., WLED_MAX_SEGNAME_LEN = 512 would not be standard). File paths should be validated to stay within the 255-byte limit.

@DedeHai
Copy link
Collaborator Author

DedeHai commented Mar 21, 2026

I tested the rabbits suggestions and made some improvements to twinkle fox (background was mostly faded to black due to gamma correction, fixed by applying inverse gamma: there would be other ways to fix it but it would change legacy behaviour even more).

I am sure there is bugs left but in general the FX look good, colors are correct, palettes work.

Ready to merge from my side.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

keep This issue will never become stale/closed automatically

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants