Skip to content

Add new effects: music strobe and flame jet #4819

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: main
Choose a base branch
from

Conversation

aerosh
Copy link

@aerosh aerosh commented Aug 6, 2025

Added audio-reactive strobe and flame jet effects.
The strobe effect adjusts the trigger threshold, the fade time, and the palette.
The flame jet effect adjusts the palette, trigger threshold, fade time, and flame height. The effect looks good on cylindrical matrices.

Summary by CodeRabbit

  • New Features

    • Added two audio-reactive LED effects: Music Strobe and Flame Jet. Both respond dynamically to audio input and are selectable from the effects list.
  • Improvements

    • Increased the total number of available effects to include these additions.

Copy link
Contributor

coderabbitai bot commented Aug 6, 2025

Walkthrough

Added two new audio-reactive effects (mode_music_strobe, mode_flame_jet) implemented in FX.cpp, registered with metadata; added corresponding FX mode constants and updated MODE_COUNT in FX.h; and updated PlatformIO defaults and common settings in platformio.ini.

Changes

Cohort / File(s) Change Summary
New Audio-Reactive Effects Implementation
wled00/FX.cpp
Added mode_music_strobe() and mode_flame_jet() functions, audio input handling, effect logic for 1D/2D rendering, metadata strings, and registration in effect setup.
Effect Mode Definitions
wled00/FX.h
Added #define FX_MODE_MUSIC_STROBE 218 and #define FX_MODE_FLAME_JET 219; updated #define MODE_COUNT from 218 to 220.
Build / CI Configuration
platformio.ini
Added esp32dev_V4 to default_envs; added [common] entries build_cache_dir = ~/.buildcache and extra_configs = platformio_override.ini.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • Updates to particle system #4630 — Modifies FX mode definitions in wled00/FX.h (adds FX_MODE constants and updates MODE_COUNT), directly related to header changes here.
  • Remove old V3 IDF #4836 — Changes PlatformIO environment and common config entries similar to the platformio.ini edits in this PR.

Suggested reviewers

  • blazoncek
  • netmindz

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@blazoncek
Copy link
Collaborator

Thanks. A video would be beneficial.

@aerosh
Copy link
Author

aerosh commented Aug 6, 2025

Thanks. A video would be beneficial.

The video shows an example of the work.
The strobe effect was turned on on the garland, and the flame jet effect was turned on on the lamp. The lamp has a 16x16 matrix rolled into a cylinder.
You can also customize these effects if someone thinks it should work a little differently :)

https://youtube.com/shorts/_6KdyX1JDU8?feature=share

@aerosh
Copy link
Author

aerosh commented Aug 7, 2025

Thanks. A video would be beneficial.

Sorry, the music from the previous video may not be available in some countries.
If you're having trouble watching, I've re-recorded the video with different music for this case.

link

@DedeHai
Copy link
Collaborator

DedeHai commented Aug 7, 2025

I like the flame jet effect, but how is it 2D only? looks 1D to me, did not check the code yet.
The music strobe seems like it could be done with one of the existing effects, not sure. did you check if any of the existing ones use a similar threshold and you could just extend that?

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 (4)
wled00/FX.cpp (4)

10657-10659: Fade mapping almost wipes the frame every tick

map(..., 0, 255, 0, 20); followed by fadeBy = 255 - mappedSpeed constrains fadeBy to 235–255, meaning nearly-full fade-out each frame. The strobe will be invisible except during the exact peak frame.

Consider mapping speed directly to a wider fade range or inverting the relation:

-  long mappedSpeed = map(SEGMENT.speed, 0, 255, 0, 20);
-  uint8_t fadeBy = 255 - mappedSpeed;
+  // Higher speed → faster fade-out (larger fadeBy)
+  uint8_t fadeBy = map(SEGMENT.speed, 0, 255, 20, 235);

10647-10650: Unify spelling: “strob” → “strobe”

Both the comment and metadata string use “Strob”, while the common term (and likely user expectation) is “Strobe”. Rename for consistency and discoverability.

Also applies to: 10670-10671


10704-10727: Clamp loop upper bound to save MCU cycles

When SEGENV.aux0 exceeds SEG_H, the for loop still iterates aux0 times, immediately skipping out-of-range rows. On large matrices this is wasted work.

-  for (uint16_t y_offset = 0; y_offset < SEGENV.aux0; y_offset++) {
+  const uint16_t limit = MIN(SEGENV.aux0, SEG_H);
+  for (uint16_t y_offset = 0; y_offset < limit; y_offset++) {

Repeat the same optimisation in the 1 D branch.


10759-10760: Non-English inline comment

Project comments are English-only; please translate or remove the Russian note to stay consistent.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between b6be4ca and 3255d80.

📒 Files selected for processing (1)
  • wled00/FX.cpp (3 hunks)
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: netmindz
PR: wled/WLED#4728
File: wled00/FX.h:378-378
Timestamp: 2025-06-15T09:59:52.720Z
Learning: In WLED's FX.h, MODE_COUNT represents the highest FX_MODE_ ID + 1, not the total count of FX_MODE_ definitions. This is because some effects may have been removed but their IDs are not reused, creating gaps in the numbering sequence. MODE_COUNT is used for array bounds and range checking.
Learnt from: netmindz
PR: wled/WLED#4728
File: wled00/FX.h:378-378
Timestamp: 2025-06-15T09:59:52.720Z
Learning: In WLED's FX.h, MODE_COUNT represents the highest FX_MODE_ ID + 1, not the total count of FX_MODE_ definitions. This is because some effects may have been removed but their IDs are not reused, creating gaps in the numbering sequence. MODE_COUNT is used for array bounds and range checking.
Learnt from: DedeHai
PR: wled/WLED#4750
File: usermods/audioreactive/audio_reactive.cpp:311-321
Timestamp: 2025-06-27T06:04:44.652Z
Learning: In WLED's AudioReactive usermod, FFT buffer memory (valFFT, windowFFT) is allocated once during initialization and never deallocated, following typical embedded system design where core functionality buffers persist for the application lifetime. Raw buffer pointers for aligned allocations don't need to be tracked in the current implementation since deallocation is never performed.
📚 Learning: 2025-06-15T09:59:52.720Z
Learnt from: netmindz
PR: wled/WLED#4728
File: wled00/FX.h:378-378
Timestamp: 2025-06-15T09:59:52.720Z
Learning: In WLED's FX.h, MODE_COUNT represents the highest FX_MODE_ ID + 1, not the total count of FX_MODE_ definitions. This is because some effects may have been removed but their IDs are not reused, creating gaps in the numbering sequence. MODE_COUNT is used for array bounds and range checking.

Applied to files:

  • wled00/FX.cpp
📚 Learning: 2025-06-27T06:04:44.652Z
Learnt from: DedeHai
PR: wled/WLED#4750
File: usermods/audioreactive/audio_reactive.cpp:311-321
Timestamp: 2025-06-27T06:04:44.652Z
Learning: In WLED's AudioReactive usermod, FFT buffer memory (valFFT, windowFFT) is allocated once during initialization and never deallocated, following typical embedded system design where core functionality buffers persist for the application lifetime. Raw buffer pointers for aligned allocations don't need to be tracked in the current implementation since deallocation is never performed.

Applied to files:

  • wled00/FX.cpp
📚 Learning: 2025-06-07T15:58:42.579Z
Learnt from: willmmiles
PR: wled/WLED#4712
File: wled00/FX_fcn.cpp:992-993
Timestamp: 2025-06-07T15:58:42.579Z
Learning: In WLED segments, the backing pixel buffer is always allocated to the full physical segment size, but effect rendering only uses a subset when mirror or grouping are enabled. For 2D segments, this subset is vWidth() * vHeight(), and for 1D segments it's vLength(). Color processing operations like fade_out should iterate over this rendering subset, not the full allocated buffer.

Applied to files:

  • wled00/FX.cpp
🔇 Additional comments (1)
wled00/FX.cpp (1)

10896-10896: Verify ID / MODE_COUNT alignment

Ensure FX_MODE_MUSIC_STROB and FX_MODE_FLAME_JET are defined in FX.h and that MODE_COUNT was bumped to the highest ID + 1 (219 → 220) as per WLED convention.

Also applies to: 10970-10971

@aerosh
Copy link
Author

aerosh commented Aug 8, 2025

Thanks for the quick feedback, @DedeHai

Regarding Flame Jet:
You are absolutely right! The core concept is definitely 1D, and my initial implementation was unnecessarily limited to 2D matrices. I've refactored the mode_flame_jet function to support both 1D and 2D segments. It now checks SEGMENT.is2D() and uses the appropriate logic for each case. I've also updated the metadata string to reflect this.

Regarding Music Strobe:
That's a great point, and I did a thorough check of the existing effects to avoid redundancy. Here's what I found:

  • The standard Strobe effects are all time-based, not audio-reactive.

  • The audio-reactive effects that use samplePeak (like Ripple Peak, Waterfall, Puddlepeak) trigger different kinds of visual effects (e.g., a ripple from a random point, a single pixel flash) and, importantly, they don't have a user-configurable volume threshold. They trigger on any detected peak.

My Music Strob effect fills a specific gap: it provides a simple, full-segment flash that is triggered only when a beat's volume exceeds a user-defined Intensity threshold. I believe this functionality is distinct and useful enough to warrant its own effect rather than complicating an existing one with more options.

I've pushed the changes for Flame Jet. Let me know what you think!

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 (3)
wled00/FX.cpp (3)

10647-10649: Correct the spelling of “strobe”.

The in-code comment and effect name use “strob”, which looks like a typo. Standardising on “strobe” avoids future search/consistency issues.


10657-10660: fadeBy range is too narrow to give a visible speed difference.

Mapping speed 0-255 → 0-20 means fadeBy varies only between 255 and 235, i.e. ~8 % of the usable range. Consider mapping to the full 0-255 to obtain a noticeably slower fade at low speed values:

- long mappedSpeed = map(SEGMENT.speed, 0, 255, 0, 20);
- uint8_t fadeBy = 255 - mappedSpeed;
+ uint8_t fadeBy = 255 - map(SEGMENT.speed, 0, 255, 0, 255);

10682-10685: Same narrow‐range fade issue as above.

map(..., 0, 64) limits fade_by to 255–191. Expanding to the full range would make “Trail Length” more intuitive for users.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 3255d80 and 66cffa8.

📒 Files selected for processing (1)
  • wled00/FX.cpp (3 hunks)
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: netmindz
PR: wled/WLED#4728
File: wled00/FX.h:378-378
Timestamp: 2025-06-15T09:59:52.720Z
Learning: In WLED's FX.h, MODE_COUNT represents the highest FX_MODE_ ID + 1, not the total count of FX_MODE_ definitions. This is because some effects may have been removed but their IDs are not reused, creating gaps in the numbering sequence. MODE_COUNT is used for array bounds and range checking.
Learnt from: netmindz
PR: wled/WLED#4728
File: wled00/FX.h:378-378
Timestamp: 2025-06-15T09:59:52.720Z
Learning: In WLED's FX.h, MODE_COUNT represents the highest FX_MODE_ ID + 1, not the total count of FX_MODE_ definitions. This is because some effects may have been removed but their IDs are not reused, creating gaps in the numbering sequence. MODE_COUNT is used for array bounds and range checking.
Learnt from: DedeHai
PR: wled/WLED#4750
File: usermods/audioreactive/audio_reactive.cpp:311-321
Timestamp: 2025-06-27T06:04:44.652Z
Learning: In WLED's AudioReactive usermod, FFT buffer memory (valFFT, windowFFT) is allocated once during initialization and never deallocated, following typical embedded system design where core functionality buffers persist for the application lifetime. Raw buffer pointers for aligned allocations don't need to be tracked in the current implementation since deallocation is never performed.
📚 Learning: 2025-06-15T09:59:52.720Z
Learnt from: netmindz
PR: wled/WLED#4728
File: wled00/FX.h:378-378
Timestamp: 2025-06-15T09:59:52.720Z
Learning: In WLED's FX.h, MODE_COUNT represents the highest FX_MODE_ ID + 1, not the total count of FX_MODE_ definitions. This is because some effects may have been removed but their IDs are not reused, creating gaps in the numbering sequence. MODE_COUNT is used for array bounds and range checking.

Applied to files:

  • wled00/FX.cpp
📚 Learning: 2025-06-27T06:04:44.652Z
Learnt from: DedeHai
PR: wled/WLED#4750
File: usermods/audioreactive/audio_reactive.cpp:311-321
Timestamp: 2025-06-27T06:04:44.652Z
Learning: In WLED's AudioReactive usermod, FFT buffer memory (valFFT, windowFFT) is allocated once during initialization and never deallocated, following typical embedded system design where core functionality buffers persist for the application lifetime. Raw buffer pointers for aligned allocations don't need to be tracked in the current implementation since deallocation is never performed.

Applied to files:

  • wled00/FX.cpp
📚 Learning: 2025-06-07T15:58:42.579Z
Learnt from: willmmiles
PR: wled/WLED#4712
File: wled00/FX_fcn.cpp:992-993
Timestamp: 2025-06-07T15:58:42.579Z
Learning: In WLED segments, the backing pixel buffer is always allocated to the full physical segment size, but effect rendering only uses a subset when mirror or grouping are enabled. For 2D segments, this subset is vWidth() * vHeight(), and for 1D segments it's vLength(). Color processing operations like fade_out should iterate over this rendering subset, not the full allocated buffer.

Applied to files:

  • wled00/FX.cpp
🧬 Code Graph Analysis (1)
wled00/FX.cpp (2)
usermods/mpu6050_imu/mpu6050_imu.cpp (1)
  • um_data (124-124)
wled00/fcn_declare.h (3)
  • hw_random8 (549-549)
  • hw_random8 (550-550)
  • hw_random8 (551-551)
🔇 Additional comments (2)
wled00/FX.cpp (2)

10893-10894: Verify header constants and MODE_COUNT.

Adding FX_MODE_MUSIC_STROB requires :

  1. A unique, gap-free numeric ID in FX.h
  2. Updating MODE_COUNT to highest_ID + 1

Please cross-check to prevent array-bounds errors (see team learning about MODE_COUNT).


10731-10734: Same underflow risk for 1-D branch.

Use a signed check (if (current_index >= SEGLEN) break;) or break when start_index + i_offset exceeds length to avoid needless looping.

⛔ Skipped due to learnings
Learnt from: blazoncek
PR: wled/WLED#4658
File: wled00/led.cpp:90-91
Timestamp: 2025-04-24T09:31:06.879Z
Learning: In the WLED codebase, the `getSegment()` function guards against out-of-bounds segment IDs by falling back to the main segment ID, and `getFirstSelectedSegId()` falls back to `getMainSegmentId()` if no segments are selected, ensuring no crashes when used through the `setValuesFromFirstSelectedSeg()` macro.
Learnt from: blazoncek
PR: wled/WLED#4658
File: wled00/led.cpp:90-91
Timestamp: 2025-04-24T09:31:06.879Z
Learning: In the WLED codebase, the `getSegment()` function guards against out-of-bounds segment IDs, and `getFirstSelectedSegId()` falls back to `getMainSegmentId()` if no segments are selected, ensuring no crashes when used through the `setValuesFromFirstSelectedSeg()` macro.
Learnt from: blazoncek
PR: wled/WLED#4658
File: wled00/led.cpp:90-91
Timestamp: 2025-04-24T09:31:06.879Z
Learning: In the WLED codebase, the `getSegment()` function guards against out-of-bounds segment IDs, and `getFirstSelectedSegId()` falls back to `getMainSegmentId()` if no segments are selected, ensuring no crashes when used through the `setValuesFromFirstSelectedSeg()` macro.
Learnt from: blazoncek
PR: wled/WLED#4667
File: usermods/user_fx/user_fx.cpp:27-30
Timestamp: 2025-04-30T05:41:03.633Z
Learning: In WLED, the Segment::allocateData() method already includes optimization to check if data is allocated and sufficiently sized, handling buffer reuse to reduce memory fragmentation. Adding an external check like `if (SEGENV.data == nullptr && !SEGENV.allocateData(dataSize))` is unnecessary and could be problematic, as it bypasses proper size verification.

Comment on lines +10654 to +10655
bool samplePeak = *(bool*)um_data->u_data[3];
int volumeRaw = *(int16_t*)um_data->u_data[1];
Copy link
Contributor

@coderabbitai coderabbitai bot Aug 8, 2025

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Avoid raw pointer casts for audio flags.

*(bool*)um_data->u_data[3] assumes the pointer is valid and correctly aligned for a bool, which isn’t guaranteed across platforms.
If um_data already provides strongly-typed accessors (e.g. um_data->samplePeak or similar), use those instead; otherwise add a helper that hides the cast in one place.

🤖 Prompt for AI Agents
In wled00/FX.cpp around lines 10654 to 10655, avoid using raw pointer casts like
*(bool*)um_data->u_data[3] to access audio flags because it assumes pointer
validity and alignment that may not hold. Instead, check if um_data offers a
strongly-typed accessor for samplePeak and volumeRaw and use those. If not,
create a helper function that safely encapsulates the cast and access in one
place to improve safety and readability.

Copy link
Member

Choose a reason for hiding this comment

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

um_data is not strongly typed as different usermods can send different data. Sadly we can't return a strongly typed result from getAudioData as the AudioReactive usermod might not be included in the build and so dummy data is returned for effects that expect audio

Copy link
Contributor

Choose a reason for hiding this comment

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

@netmindz Thanks for the context. Agreed — since um_data is intentionally untyped (and can be simulated), we can’t introduce a strongly typed accessor.

Small, safe tweak we can still do to avoid aliasing through a bool pointer: read the documented byte and then convert to bool. That keeps the loose typing but eliminates potential alignment/aliasing pitfalls and matches the mapping comment in FX.cpp (samplePeak is uint8_t at u_data[3]).

Example minimal change in the new effects:

// before
bool samplePeak = *(bool*)um_data->u_data[3];

// after (safer and aligned with documented mapping)
bool samplePeak = (*(uint8_t*)um_data->u_data[3]) != 0;

Optionally, we can centralize this in tiny helpers near getAudioData() to keep casts in one place (still generic, no strong typing):

static inline bool ar_samplePeak(const um_data_t* d) {
  return (*(uint8_t*)d->u_data[3]) != 0;
}
static inline int16_t ar_volumeRaw(const um_data_t* d) {
  return *(int16_t*)d->u_data[1];
}
// usage
bool samplePeak = ar_samplePeak(um_data);
int  volumeRaw  = ar_volumeRaw(um_data);

If you’re OK with it, I’ll suggest the minimal bool change across mode_music_strobe and mode_flame_jet to stay consistent with the file’s mapping.


✏️ Learnings added
Learnt from: netmindz
PR: wled/WLED#4819
File: wled00/FX.cpp:10654-10655
Timestamp: 2025-08-08T17:22:37.352Z
Learning: In WLED FX.cpp, um_data returned by getAudioData() is intentionally untyped because different usermods provide different payloads, and simulateSound() may supply dummy data when the AudioReactive usermod isn’t built. Strongly typed accessors are not feasible; prefer explicit casts (e.g., samplePeak as uint8_t at u_data[3]) or small helpers that encapsulate these casts.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: netmindz
PR: wled/WLED#4728
File: wled00/FX.h:378-378
Timestamp: 2025-06-15T09:59:52.720Z
Learning: In WLED's FX.h, MODE_COUNT represents the highest FX_MODE_ ID + 1, not the total count of FX_MODE_ definitions. This is because some effects may have been removed but their IDs are not reused, creating gaps in the numbering sequence. MODE_COUNT is used for array bounds and range checking.

Learnt from: netmindz
PR: wled/WLED#4728
File: wled00/FX.h:378-378
Timestamp: 2025-06-15T09:59:52.720Z
Learning: In WLED's FX.h, MODE_COUNT represents the highest FX_MODE_ ID + 1, not the total count of FX_MODE_ definitions. This is because some effects may have been removed but their IDs are not reused, creating gaps in the numbering sequence. MODE_COUNT is used for array bounds and range checking.

Learnt from: DedeHai
PR: wled/WLED#4750
File: usermods/audioreactive/audio_reactive.cpp:311-321
Timestamp: 2025-06-27T06:04:44.652Z
Learning: In WLED's AudioReactive usermod, FFT buffer memory (valFFT, windowFFT) is allocated once during initialization and never deallocated, following typical embedded system design where core functionality buffers persist for the application lifetime. Raw buffer pointers for aligned allocations don't need to be tracked in the current implementation since deallocation is never performed.

Learnt from: blazoncek
PR: wled/WLED#4667
File: usermods/user_fx/user_fx.cpp:27-30
Timestamp: 2025-04-30T05:41:03.633Z
Learning: In WLED, the Segment::allocateData() method already includes optimization to check if data is allocated and sufficiently sized, handling buffer reuse to reduce memory fragmentation. Adding an external check like `if (SEGENV.data == nullptr && !SEGENV.allocateData(dataSize))` is unnecessary and could be problematic, as it bypasses proper size verification.

Copy link
Member

Choose a reason for hiding this comment

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

those helper methods would certainly help code readability, but it does mean adding ar_ functions into the main codebase that relate to a usermod, so you might as well at that stage add strongly typed return of getAudioData as both is adding AR specific code into the general codebase

Copy link
Contributor

Choose a reason for hiding this comment

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

@netmindz Totally fair. Let’s keep core clean and avoid AR-specific helpers.

I’ll switch the samplePeak read to the safer inline form (aligned with the documented mapping) and leave everything else as-is:

// mode_music_strobe
um_data_t *um_data = getAudioData();
if (!um_data) return FRAMETIME;

bool    samplePeak = (*(uint8_t*)um_data->u_data[3]) != 0;
int     volumeRaw  = *(int16_t*)um_data->u_data[1];
// mode_flame_jet
um_data_t *um_data = getAudioData();
if (!um_data) return FRAMETIME;

bool    samplePeak = (*(uint8_t*)um_data->u_data[3]) != 0;
int     volumeRaw  = *(int16_t*)um_data->u_data[1];

This avoids aliasing through a bool pointer, preserves the untyped contract of um_data, and doesn’t introduce any AR-specific helpers into the codebase.


✏️ Learnings added
Learnt from: netmindz
PR: wled/WLED#4819
File: wled00/FX.cpp:10654-10655
Timestamp: 2025-08-08T17:31:47.296Z
Learning: In WLED core, avoid introducing AR-specific helper wrappers for um_data access. Keep um_data untyped and, when reading samplePeak, prefer `(*(uint8_t*)um_data->u_data[3]) != 0` over `*(bool*)` to avoid alignment/aliasing issues, while staying decoupled from the AudioReactive usermod.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: netmindz
PR: wled/WLED#4728
File: wled00/FX.h:378-378
Timestamp: 2025-06-15T09:59:52.720Z
Learning: In WLED's FX.h, MODE_COUNT represents the highest FX_MODE_ ID + 1, not the total count of FX_MODE_ definitions. This is because some effects may have been removed but their IDs are not reused, creating gaps in the numbering sequence. MODE_COUNT is used for array bounds and range checking.

Learnt from: netmindz
PR: wled/WLED#4728
File: wled00/FX.h:378-378
Timestamp: 2025-06-15T09:59:52.720Z
Learning: In WLED's FX.h, MODE_COUNT represents the highest FX_MODE_ ID + 1, not the total count of FX_MODE_ definitions. This is because some effects may have been removed but their IDs are not reused, creating gaps in the numbering sequence. MODE_COUNT is used for array bounds and range checking.

Learnt from: netmindz
PR: wled/WLED#4819
File: wled00/FX.cpp:10654-10655
Timestamp: 2025-08-08T17:22:37.352Z
Learning: In WLED FX.cpp, um_data returned by getAudioData() is intentionally untyped because different usermods provide different payloads, and simulateSound() may supply dummy data when the AudioReactive usermod isn’t built. Strongly typed accessors are not feasible; prefer explicit casts (e.g., samplePeak as uint8_t at u_data[3]) or small helpers that encapsulate these casts.

Learnt from: DedeHai
PR: wled/WLED#4750
File: usermods/audioreactive/audio_reactive.cpp:311-321
Timestamp: 2025-06-27T06:04:44.652Z
Learning: In WLED's AudioReactive usermod, FFT buffer memory (valFFT, windowFFT) is allocated once during initialization and never deallocated, following typical embedded system design where core functionality buffers persist for the application lifetime. Raw buffer pointers for aligned allocations don't need to be tracked in the current implementation since deallocation is never performed.

Learnt from: blazoncek
PR: wled/WLED#4667
File: usermods/user_fx/user_fx.cpp:27-30
Timestamp: 2025-04-30T05:41:03.633Z
Learning: In WLED, the Segment::allocateData() method already includes optimization to check if data is allocated and sufficiently sized, handling buffer reuse to reduce memory fragmentation. Adding an external check like `if (SEGENV.data == nullptr && !SEGENV.allocateData(dataSize))` is unnecessary and could be problematic, as it bypasses proper size verification.

Learnt from: KrX3D
PR: wled/WLED#4585
File: usermods/seven_segment_display_reloaded_v2/seven_segment_display_reloaded_v2.h:121-136
Timestamp: 2025-04-27T09:37:28.415Z
Learning: Using PROGMEM for the seven-segment font array (umSSDRNumbers) in the WLED SSDR usermod causes compilation problems, so it should be left as a regular array.

Learnt from: willmmiles
PR: wled/WLED#4623
File: usermods/word-clock-matrix/word-clock-matrix.cpp:332-332
Timestamp: 2025-03-29T01:22:54.617Z
Learning: In the WLED project, hardcoded usermod IDs (like 500 in the WordClockMatrix's getId() method) are intentionally used as part of a strategy to avoid modifying core code. There are plans to remove numeric usermod IDs entirely in the future.

Comment on lines +10702 to +10705
for (uint16_t y_offset = 0; y_offset < SEGENV.aux0; y_offset++) {
uint16_t current_y = start_y - y_offset;
if (current_y >= SEG_H) continue; // Boundary check

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

Potential unsigned underflow when current_y wraps.

When y_offset > start_y, current_y = start_y - y_offset wraps to 65 535, the subsequent >= SEG_H check skips the pixel but the loop keeps iterating. Break early to avoid wasted iterations:

if (y_offset > start_y) break;  // prevents under-flow
🤖 Prompt for AI Agents
In wled00/FX.cpp around lines 10702 to 10705, the calculation of current_y as
start_y minus y_offset can cause unsigned underflow when y_offset exceeds
start_y, resulting in a large value that passes the boundary check but wastes
iterations. To fix this, add a condition to break the loop early if y_offset is
greater than start_y, preventing the underflow and unnecessary loop iterations.

@netmindz netmindz added the effect label Aug 8, 2025
@netmindz netmindz changed the title Add new effects: music strob and flame jet Add new effects: music strobe and flame jet Aug 8, 2025
@netmindz
Copy link
Member

netmindz commented Aug 8, 2025

I think I follow the basic idea of the effects, but at the moment they feel like they might need a little more refinement, especially the strobe. You might be better looking at only certain frequencies rather than total volume, plus you might need to do things like compare against a running average, you might also possibly find the zero crossing data useful (think this is possibly only in WLED-MM at the moment)

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

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

10654-10655: Avoid aliasing through bool pointer when reading samplePeak.

Casting u_data[3] to bool* risks alignment/aliasing issues. The file’s AR mapping treats samplePeak as a byte; use a uint8_t read and compare to zero.

-  bool samplePeak = *(bool*)um_data->u_data[3]; 
+  bool samplePeak = (*(uint8_t*)um_data->u_data[3]) != 0;

Rationale: matches the documented mapping at the top of this file and avoids strict-aliasing pitfalls while keeping um_data untyped.


10679-10681: Avoid aliasing through bool pointer when reading samplePeak.

Same concern as in Music Strobe. Please read the byte and compare to zero.

-  bool samplePeak = *(bool*)um_data->u_data[3];
+  bool samplePeak = (*(uint8_t*)um_data->u_data[3]) != 0;

10702-10705: Unsigned underflow when y_offset > start_y (wastes iterations).

current_y = start_y - y_offset underflows once y_offset exceeds start_y, then current_y >= SEG_H is true and the loop keeps iterating doing nothing.

-      for (uint16_t y_offset = 0; y_offset < SEGENV.aux0; y_offset++) {
-        uint16_t current_y = start_y - y_offset;
-        if (current_y >= SEG_H) continue; // Boundary check
+      for (uint16_t y_offset = 0; y_offset < SEGENV.aux0; y_offset++) {
+        if (y_offset > start_y) break; // prevent underflow and wasted iterations
+        uint16_t current_y = start_y - y_offset;

This avoids wraparound and unnecessary loop work once the bottom row is exceeded.

🧹 Nitpick comments (7)
platformio.ini (1)

17-17: Use a portable path for build_cache_dir or move to override

Using "~" in paths is not reliably portable across all OS/CI shells. Prefer ${sysenv.HOME}/.buildcache (PlatformIO expands it) or keep this setting in platformio_override.ini so it remains local to developers.

Proposed change:

-build_cache_dir = ~/.buildcache
+build_cache_dir = ${sysenv.HOME}/.buildcache
wled00/FX.cpp (6)

10648-10648: Nit: fix typo in comment (“beat” not “bit”).

“Simple bit strobe” → “Simple beat strobe”.

-  Simple bit strobe
+  Simple beat strobe

10657-10660: Fade mapping may be too coarse (0..20 → 255..235).

Mapping speed to 21 fade steps is quite coarse and makes high speeds only subtly different. Consider widening the range (e.g., 0..96) for more perceptible control.

-  long mappedSpeed = map(SEGMENT.speed, 0, 255, 0, 20);
+  long mappedSpeed = map(SEGMENT.speed, 0, 255, 0, 96);
   uint8_t fadeBy = 255 - mappedSpeed;

10661-10666: Optional: normalize threshold against recent loudness.

Using a fixed threshold (intensity) against volumeRaw can be brittle across setups. Consider a running average (and/or band-limited loudness) to trigger on “above average” peaks rather than absolute values.

Implementation direction:

  • Maintain a small EMA of volumeRaw in SEGENV.aux fields (or SEGMENT.step).
  • Trigger when samplePeak && volumeRaw > avg + offset.
  • Optionally support band selection via SEGMENT.custom1/custom2 in future.

10682-10685: Potentially confusing “Trail Length” mapping to fade strength.

Metadata labels intensity as “Trail Length” while code maps intensity directly to fade strength. That’s OK, but trail length grows non-linearly with fade. If users expect roughly linear trail changes, consider a small LUT or a perceptual remap of intensity to fade.


10757-10758: Metadata palette default unused.

The effect derives color from SEGCOLOR(0); pal=35 likely won’t affect output. Either:

  • Drop pal=35 from metadata, or
  • Optionally derive base color from palette when no explicit color is set (consistency with other fire-like effects).

10967-10968: Placement: Flame Jet supports 1D and 2D.

You registered Flame Jet in the 2D section. That’s fine since the effect detects 1D/2D at runtime. For discoverability, consider a short note or consistent grouping near other audio modes; functionally OK as-is.

📜 Review details

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

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between e970bff and d2874e1.

📒 Files selected for processing (3)
  • platformio.ini (1 hunks)
  • wled00/FX.cpp (3 hunks)
  • wled00/FX.h (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • wled00/FX.h
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: netmindz
PR: wled/WLED#4728
File: wled00/FX.h:378-378
Timestamp: 2025-06-15T09:59:52.720Z
Learning: In WLED's FX.h, MODE_COUNT represents the highest FX_MODE_ ID + 1, not the total count of FX_MODE_ definitions. This is because some effects may have been removed but their IDs are not reused, creating gaps in the numbering sequence. MODE_COUNT is used for array bounds and range checking.
Learnt from: netmindz
PR: wled/WLED#4728
File: wled00/FX.h:378-378
Timestamp: 2025-06-15T09:59:52.720Z
Learning: In WLED's FX.h, MODE_COUNT represents the highest FX_MODE_ ID + 1, not the total count of FX_MODE_ definitions. This is because some effects may have been removed but their IDs are not reused, creating gaps in the numbering sequence. MODE_COUNT is used for array bounds and range checking.
📚 Learning: 2025-08-08T17:22:37.374Z
Learnt from: netmindz
PR: wled/WLED#4819
File: wled00/FX.cpp:10654-10655
Timestamp: 2025-08-08T17:22:37.374Z
Learning: In WLED FX.cpp, um_data returned by getAudioData() is intentionally untyped because different usermods provide different payloads, and simulateSound() may supply dummy data when the AudioReactive usermod isn’t built. Strongly typed accessors are not feasible; prefer explicit casts (e.g., samplePeak as uint8_t at u_data[3]) or small helpers that encapsulate these casts.

Applied to files:

  • wled00/FX.cpp
📚 Learning: 2025-06-15T09:59:52.720Z
Learnt from: netmindz
PR: wled/WLED#4728
File: wled00/FX.h:378-378
Timestamp: 2025-06-15T09:59:52.720Z
Learning: In WLED's FX.h, MODE_COUNT represents the highest FX_MODE_ ID + 1, not the total count of FX_MODE_ definitions. This is because some effects may have been removed but their IDs are not reused, creating gaps in the numbering sequence. MODE_COUNT is used for array bounds and range checking.

Applied to files:

  • wled00/FX.cpp
📚 Learning: 2025-08-08T17:31:47.330Z
Learnt from: netmindz
PR: wled/WLED#4819
File: wled00/FX.cpp:10654-10655
Timestamp: 2025-08-08T17:31:47.330Z
Learning: In WLED core, avoid introducing AR-specific helper wrappers for um_data access. Keep um_data untyped and, when reading samplePeak, prefer `(*(uint8_t*)um_data->u_data[3]) != 0` over `*(bool*)` to avoid alignment/aliasing issues, while staying decoupled from the AudioReactive usermod.

Applied to files:

  • wled00/FX.cpp
🧬 Code Graph Analysis (1)
wled00/FX.cpp (2)
usermods/mpu6050_imu/mpu6050_imu.cpp (1)
  • um_data (124-124)
wled00/fcn_declare.h (3)
  • hw_random8 (558-558)
  • hw_random8 (559-559)
  • hw_random8 (560-560)
🔇 Additional comments (3)
platformio.ini (1)

18-19: LGTM: extra_configs hook is correctly wired

Including platformio_override.ini via extra_configs aligns with the guidance above and keeps local overrides out of VCS. No issues.

wled00/FX.cpp (2)

10893-10894: LGTM: Music Strobe correctly registered among 1D audio effects.

Registration and metadata look consistent with other audio-reactive modes.


10959-10986: Confirm FX IDs and MODE_COUNT update.

Please ensure FX_MODE_MUSIC_STROBE and FX_MODE_FLAME_JET are defined in FX.h and MODE_COUNT is updated to highest ID + 1 (per project rules).

If not already done in this PR: add both constants and bump MODE_COUNT accordingly to avoid selection/index errors.

@@ -10,7 +10,7 @@
# ------------------------------------------------------------------------------

# CI/release binaries
default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, nodemcuv2_160, esp8266_2m_160, esp01_1m_full_160, nodemcuv2_compat, esp8266_2m_compat, esp01_1m_full_compat, esp32dev, esp32_eth, lolin_s2_mini, esp32c3dev, esp32s3dev_16MB_opi, esp32s3dev_8MB_opi, esp32s3_4M_qspi, esp32_wrover, usermods
default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, nodemcuv2_160, esp8266_2m_160, esp01_1m_full_160, nodemcuv2_compat, esp8266_2m_compat, esp01_1m_full_compat, esp32dev, esp32dev_V4, esp32_eth, lolin_s2_mini, esp32c3dev, esp32s3dev_16MB_opi, esp32s3dev_8MB_opi, esp32s3_4M_qspi, esp32_wrover, usermods
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Undefined environment in default_envs: esp32dev_V4

"default_envs" includes "esp32dev_V4", but there is no corresponding [env:esp32dev_V4] defined in this file. This will cause PlatformIO to fail when building CI/release binaries. The existing [env:esp32dev] already targets the V4 toolchain (esp32_idf_V4), so "esp32dev_V4" looks like a mistaken/duplicate entry.

Apply this diff to fix:

-default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, nodemcuv2_160, esp8266_2m_160, esp01_1m_full_160, nodemcuv2_compat, esp8266_2m_compat, esp01_1m_full_compat, esp32dev, esp32dev_V4, esp32_eth, lolin_s2_mini, esp32c3dev, esp32s3dev_16MB_opi, esp32s3dev_8MB_opi, esp32s3_4M_qspi, esp32_wrover, usermods
+default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, nodemcuv2_160, esp8266_2m_160, esp01_1m_full_160, nodemcuv2_compat, esp8266_2m_compat, esp01_1m_full_compat, esp32dev, esp32_eth, lolin_s2_mini, esp32c3dev, esp32s3dev_16MB_opi, esp32s3dev_8MB_opi, esp32s3_4M_qspi, esp32_wrover, usermods

Run to confirm there is no such env:


🏁 Script executed:

#!/bin/bash
rg -nP '^\s*\[env:esp32dev_V4\]' platformio.ini || echo "OK: env:esp32dev_V4 not defined (should not be in default_envs)"

Length of output: 186


Remove undefined “esp32dev_V4” from default_envs

Confirmed there is no [env:esp32dev_V4] defined in platformio.ini, so including it in default_envs will break CI/builds.

Attention:

  • platformio.ini (line 13): default_envs list

Apply this diff:

-default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, nodemcuv2_160, esp8266_2m_160, esp01_1m_full_160, nodemcuv2_compat, esp8266_2m_compat, esp01_1m_full_compat, esp32dev, esp32dev_V4, esp32_eth, lolin_s2_mini, esp32c3dev, esp32s3dev_16MB_opi, esp32s3dev_8MB_opi, esp32s3_4M_qspi, esp32_wrover, usermods
+default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, nodemcuv2_160, esp8266_2m_160, esp01_1m_full_160, nodemcuv2_compat, esp8266_2m_compat, esp01_1m_full_compat, esp32dev, esp32_eth, lolin_s2_mini, esp32c3dev, esp32s3dev_16MB_opi, esp32s3dev_8MB_opi, esp32s3_4M_qspi, esp32_wrover, usermods
🤖 Prompt for AI Agents
In platformio.ini around line 13, the default_envs list includes a non-existent
env `esp32dev_V4` which will break CI/builds; remove `esp32dev_V4` from the
default_envs entry (or alternatively add a matching `[env:esp32dev_V4]` section
if that env is required), ensure commas and spacing remain consistent after
removal, and save the file so CI uses only defined environments.

Comment on lines +10731 to +10746
for (uint16_t i_offset = 0; i_offset < SEGENV.aux0; i_offset++) {
uint16_t current_index = start_index + i_offset;
if (current_index >= SEGLEN) continue; // Boundary check

uint32_t base_color = SEGCOLOR(0);
CRGB fastled_color = CRGB(base_color);

uint8_t heat_reduction = map(i_offset, 0, SEGLEN, 0, 180);
fastled_color.g = qsub8(fastled_color.g, heat_reduction);
fastled_color.b = qsub8(fastled_color.b, heat_reduction * 2);

if (hw_random8() < 50) {
fastled_color.nscale8(220);
}
SEGMENT.setPixelColor(current_index, fastled_color);
}
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

Break early when 1D index passes SEGLEN; honor segment direction.

  • The loop continues even after current_index exceeds SEGLEN, performing useless iterations. Break early to save cycles.
  • Consider drawing from the configured segment “end” when reversed. Many effects respect SEGMENT.reverse.
-      const uint16_t max_height = SEGLEN;
-      uint16_t start_index = 0; // Or SEGLEN - 1 if you want it from the other end
+      const uint16_t max_height = SEGLEN;
+      const bool rev = SEGMENT.reverse;
+      uint16_t start_index = rev ? (SEGLEN - 1) : 0;

-      for (uint16_t i_offset = 0; i_offset < SEGENV.aux0; i_offset++) {
-        uint16_t current_index = start_index + i_offset;
-        if (current_index >= SEGLEN) continue; // Boundary check
+      for (uint16_t i_offset = 0; i_offset < SEGENV.aux0; i_offset++) {
+        uint16_t current_index = rev ? (start_index - i_offset) : (start_index + i_offset);
+        if (current_index >= SEGLEN) break; // Boundary check & early exit
📝 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
for (uint16_t i_offset = 0; i_offset < SEGENV.aux0; i_offset++) {
uint16_t current_index = start_index + i_offset;
if (current_index >= SEGLEN) continue; // Boundary check
uint32_t base_color = SEGCOLOR(0);
CRGB fastled_color = CRGB(base_color);
uint8_t heat_reduction = map(i_offset, 0, SEGLEN, 0, 180);
fastled_color.g = qsub8(fastled_color.g, heat_reduction);
fastled_color.b = qsub8(fastled_color.b, heat_reduction * 2);
if (hw_random8() < 50) {
fastled_color.nscale8(220);
}
SEGMENT.setPixelColor(current_index, fastled_color);
}
const uint16_t max_height = SEGLEN;
const bool rev = SEGMENT.reverse;
uint16_t start_index = rev ? (SEGLEN - 1) : 0;
for (uint16_t i_offset = 0; i_offset < SEGENV.aux0; i_offset++) {
uint16_t current_index = rev
? (start_index - i_offset)
: (start_index + i_offset);
if (current_index >= SEGLEN) break; // Boundary check & early exit
uint32_t base_color = SEGCOLOR(0);
CRGB fastled_color = CRGB(base_color);
uint8_t heat_reduction = map(i_offset, 0, SEGLEN, 0, 180);
fastled_color.g = qsub8(fastled_color.g, heat_reduction);
fastled_color.b = qsub8(fastled_color.b, heat_reduction * 2);
if (hw_random8() < 50) {
fastled_color.nscale8(220);
}
SEGMENT.setPixelColor(current_index, fastled_color);
}
🤖 Prompt for AI Agents
In wled00/FX.cpp around lines 10731 to 10746, the loop currently continues past
the segment end and ignores segment direction; change the logic to break the
loop when the computed 1D index would be out of bounds instead of continuing to
waste cycles, and compute the index using the segment direction (respect
SEGMENT.reverse) so pixels are drawn from the configured segment end when
reversed (e.g., derive current_index from start_index +/- i_offset based on
SEGMENT.reverse, check bounds once and break if out of range, then proceed with
color/heat logic).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants