Skip to content

Conversation

@softhack007
Copy link
Collaborator

@softhack007 softhack007 commented Nov 24, 2024

Waterfall mapping takes a 1D effect on the top row, and copies is down one row in each step.

The code still needs some rework:

  • Waterfall = "Bar" + "reverse" due to limitations in m1D2D
  • m1D2D is only 3bit wide, but we need a new entry "8".
  • slider to control waterfall speed is needed
  • _is2Deffect still looks "not really right". Need to sleep over it.

examples

"Blurz" looks like comets

Screenshot 2024-11-24 005517

"BPM" creates waves

Screenshot 2024-11-24 010606

Summary by CodeRabbit

Release Notes

  • New Features

    • Added a new waterfall mapping option for 2D LED effects, enabling waterfall-like visual patterns on matrix-based LED displays.
  • Improvements

    • Enhanced 2D effect detection and mode handling for improved compatibility across different LED configurations.
    • Improved validation and fallback handling for LED mapping configurations, increasing system stability.

@softhack007 softhack007 marked this pull request as draft November 24, 2024 00:52
@softhack007
Copy link
Collaborator Author

note to myself: don't run waterfall when receiving realtime pixels (DDP, Art-Net, etc)

if (realtimeMode && !realtimeOverride && !useMainSegmentOnly) return; // all leds are under external control

if (realtimeMode && !realtimeOverride && useMainSegmentOnly && this segment is the main segment ) return; // main segment is under external control

@softhack007
Copy link
Collaborator Author

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Oct 21, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link

coderabbitai bot commented Oct 21, 2025

Walkthrough

Introduces a new 2D mapping option (M12_sWaterfall) and per-segment flag (_is2Deffect) to improve 2D effect handling. Adds waterfall mapping logic, optimizes rendering paths with reallyIs2D gating, and includes bounds-checking for the new mapping value.

Changes

Cohort / File(s) Summary
Core 2D Infrastructure
wled00/FX.h
Adds M12_sWaterfall = 8 enum value to 1D-to-2D mapping options; introduces private boolean field _is2Deffect initialized to false in Segment class.
2D Effect State Management
wled00/FX_2Dfcn.cpp
Updates Segment::startFrame() to compute _2dHeight and _2dWidth unconditionally; sets _is2Deffect based on 2D mode detection (";2" and ";12" markers); implements waterfall mapping logic converting M12_pBar reverse scenarios into 2D grid traversal.
Rendering Logic & Optimization
wled00/FX_fcn.cpp
Introduces reallyIs2D condition to gate 2D processing; implements M12_pBar reverse-to-M12_sWaterfall conversion; adds default branches in 2D switch statements for unsupported modes; applies reallyIs2D checks across nrOfVStrips, setPixelColor, getPixelColor, fill, fade_out, and blur operations.
Serialization Bounds Checking
wled00/json.cpp
Adds normalization guards in deserializeSegment and per-LED image processing (iarr) to clamp map1D2D values > 7 to M12_pBar, ensuring out-of-range mappings remain valid.

Sequence Diagram(s)

sequenceDiagram
    participant Renderer as FX Renderer
    participant State as Segment::startFrame()
    participant Logic as FX_fcn (Rendering)
    participant Buffer as Pixel Buffer

    Renderer->>State: Update segment frame
    State->>State: Detect 2D mode & mapping
    State->>State: Set _is2Deffect flag
    
    Renderer->>Logic: Render frame
    Logic->>Logic: Compute reallyIs2D = _is2Deffect || (is2D && !(M12_pBar+reverse))
    
    alt reallyIs2D == true
        Logic->>Buffer: Apply 2D rendering path
    else reallyIs2D == false (e.g., Waterfall)
        Logic->>Buffer: Apply optimized 1D/Waterfall path
    end
    
    Logic->>Logic: Handle M12_pBar+reverse → M12_sWaterfall mapping
    Renderer->>Buffer: Display result
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

The changes introduce heterogeneous modifications across four files with varying complexity: a new flag and enum value (straightforward), state computation logic with pattern detection (moderate), extensive reallyIs2D gating applied across multiple rendering functions with conditional mapping transformations (dense), and bounds-checking logic (simple). The FX_fcn.cpp modifications require careful review of the new reallyIs2D condition application across different rendering paths.

Poem

🐰 A waterfall of pixels flows so free,
With new mappings set, the 2D flags decree!
From bars to cascades, the logic does dance,
Rendering optimized with reallyIs2D's stance—
One byte to rule them all, quite the enhanced romance!

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The pull request title "new mapping mode 'Waterfall'" directly and clearly describes the main change in the changeset. The primary objective is to introduce a new mapping mode called "Waterfall" that copies a 1D effect down one row per step, and this title precisely captures that intent. The title is concise, specific, and free of vague terminology or noise, making it clear to anyone scanning the git history what the core change is about. All modifications across the codebase (FX.h enum addition, segment flag, FX_2Dfcn.cpp implementation, FX_fcn.cpp logic, and json.cpp guards) are in service of implementing this feature.
Docstring Coverage ✅ Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 1D2D_Waterfall

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.

Copy link

@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 (5)
wled00/FX.h (1)

428-429: _is2Deffect flag addition looks good; add brief doc on lifecycle.

Flag is consumed to gate 2D paths. Add a short comment that it’s computed in Segment::startFrame() and not persisted across frames to avoid misuse.

wled00/json.cpp (2)

168-169: Normalization hides Waterfall (8) by clamping to Bar (1).

Clamping map1D2D > 7 to M12_pBar means a caller sending 8 (Waterfall) silently becomes Bar, relying on reverse to recreate Waterfall. Consider:

  • If keeping aliasing: explicitly map 8 to M12_pBar and set rev=true (if safe), or
  • Return a 400/validation error for unsupported values to avoid silent behavior changes.

At minimum, add a comment here noting Waterfall is represented as Bar+reverse.


331-332: Same clamp applied for per‑LED image path; mirror the Waterfall alias note.

Replicate the explanatory comment or alias handling here too for consistency with segment deserialization.

wled00/FX_2Dfcn.cpp (1)

270-279: _is2Deffect detection logic OK; cache getModeData() once.

Multiple strstr(strip.getModeData(mode), ...) calls per frame. Cache pointer once before checks to trim flash reads. Minor but cheap win.

wled00/FX_fcn.cpp (1)

1445-1466: Gating 2D ops with reallyIs2D avoids wasted 2D work for Waterfall.

Solid refactor; reduces unnecessary 2D processing.

Minor: hoist reallyIs2D into a small inline helper to avoid repeating the expression.

Also applies to: 1503-1543, 1547-1551, 1574-1586

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f2f621b and 9f01ec4.

📒 Files selected for processing (4)
  • wled00/FX.h (2 hunks)
  • wled00/FX_2Dfcn.cpp (1 hunks)
  • wled00/FX_fcn.cpp (12 hunks)
  • wled00/json.cpp (2 hunks)
🔇 Additional comments (4)
wled00/FX_fcn.cpp (4)

646-664: Aliasing Bar+reverse to Waterfall is clean; LGTM.

Local map1d2d alias keeps storage ≤7 while enabling distinct handling in switches.


876-883: virtualLength accounts for Waterfall; LGTM.

Waterfall length = virtualWidth so only top row is written; consistent with copy‑down in startFrame.


970-974: Waterfall setPixelColor writes only first row; matches design.

Good separation of concerns with copy‑down in startFrame().

Please confirm this still looks correct when transpose/reverse_y are set (top row remains the “source” after transforms).


1246-1253: Waterfall getPixelColor returns first‑row color; LGTM.

Consistent with write path.

Comment on lines +280 to +287
// WLEDMM Waterfall mapping
if (!_is2Deffect && (map1D2D == M12_pBar) && reverse) { // WLEDMM Waterfall = bar + reverse
if (!ledsrgb || (ledsrgbSize == 0)) setUpLeds(); // create segment buffer if not set up already ==> 30% faster, and prevents interference with other segments.
for (unsigned myRow = _2dHeight-1; myRow > 0; myRow--)
for (unsigned myCol = 0; myCol < _2dWidth; myCol++) {
setPixelColorXY(myCol, myRow, getPixelColorXY(int(myCol), int(myRow)-1));
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Waterfall runs even when frozen or under realtime control; skip copy in those cases.

startFrame() is called before effect execution and even when seg.freeze is true. The Waterfall copy down (writing all rows) will still mutate pixels during DDP/Art‑Net, conflicting with external control.

Guard the copy with freeze/realtime checks and ensure height≥2.

Apply this diff:

-  // WLEDMM Waterfall mapping
-  if (!_is2Deffect && (map1D2D == M12_pBar) && reverse) { // WLEDMM Waterfall = bar + reverse
-    if (!ledsrgb || (ledsrgbSize == 0)) setUpLeds();      // create segment buffer if not set up already ==> 30% faster, and prevents interference with other segments.
-    for (unsigned myRow = _2dHeight-1; myRow > 0; myRow--)
-      for (unsigned myCol = 0; myCol < _2dWidth; myCol++) {
-        setPixelColorXY(myCol, myRow, getPixelColorXY(int(myCol), int(myRow)-1));
-      }
-  }
+  // WLEDMM Waterfall mapping (alias: Bar + reverse). Skip under external control or freeze.
+  if (!_is2Deffect && (map1D2D == M12_pBar) && reverse) {
+    // External control guards
+    bool allUnderExt = (realtimeMode && !realtimeOverride && !useMainSegmentOnly);
+    bool mainUnderExt = (realtimeMode && !realtimeOverride && useMainSegmentOnly
+                         && (strip.getCurrSegmentId() == strip.getMainSegmentId()));
+    if (!freeze && !(allUnderExt || mainUnderExt) && _2dHeight >= 2) {
+      if (!ledsrgb || (ledsrgbSize == 0)) setUpLeds(); // ensure local buffer
+      for (unsigned myRow = _2dHeight - 1; myRow > 0; myRow--) {
+        for (unsigned myCol = 0; myCol < _2dWidth; myCol++) {
+          setPixelColorXY(myCol, myRow, getPixelColorXY(int(myCol), int(myRow) - 1));
+        }
+      }
+    }
+  }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In wled00/FX_2Dfcn.cpp around lines 280 to 287, the Waterfall copy still mutates
pixels during freeze or realtime external control and can run when height < 2;
update the conditional so the row-copy runs only when not frozen, not in
realtime mode, and when _2dHeight >= 2 (e.g. add checks like && !seg.freeze &&
!realtime && (_2dHeight >= 2) to the existing if), leaving the setUpLeds() call
and inner loops unchanged so they are skipped when those guards are true.

Comment on lines +350 to 352
M12_sPinwheel = 7, //WLEDMM Pinwheel
M12_sWaterfall = 8 //WLEDMM Waterfall
} mapping1D2D_t;
Copy link

@coderabbitai coderabbitai bot Oct 21, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Waterfall value (8) can’t be stored in 3‑bit map1D2D; clarify intent or widen field.

map1D2D is 3 bits (values 0–7). Adding M12_sWaterfall = 8 makes it unrepresentable in the bitfield and JSON will clamp it back to ≤7. Either:

  • Treat Waterfall strictly as an alias (don’t attempt to serialize/store 8; document this), or
  • Repack options to give map1D2D 4 bits and update serialization accordingly.

Right now this is easy to misconfigure and silently falls back. Recommend adding a comment/static_assert to make this explicit, or widening when feasible.

🤖 Prompt for AI Agents
In wled00/FX.h around lines 350 to 352, the enum assigns M12_sWaterfall = 8 but
map1D2D is stored in a 3-bit field (0–7), so value 8 cannot be represented and
will be clamped; fix by either treating Waterfall as an alias (change its value
to an existing representable value and add a comment documenting it is an alias
and must not be serialized as 8) or by widening the bitfield to 4 bits (repack
the surrounding options, update any serialization/JSON read/write code to use
the new bit width, and add a static_assert ensuring the enum max fits the
bitfield). Ensure you add a clear comment/static_assert so future changes cannot
silently overflow the field.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@coderabbitai you are right, this part is still temporary and i'm looking for a better solution. do you have an elegant solution to extend map1D2D to 4 bit or more, while not breaking other segment handling functions like Segment::differs() ?

Copy link

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find the Segment::differs() implementation
echo "=== Segment::differs() implementation ==="
ast-grep --pattern $'uint8_t differs($$$) const {
  $$$
}'

echo -e "\n=== Alternative search for differs ==="
rg -nP -A 20 'uint8_t\s+Segment::differs|uint8_t\s+differs\s*\(' --type=cpp

Length of output: 3196


🏁 Script executed:

#!/bin/bash
# Search for segment serialization/deserialization in JSON
echo "=== Segment serialization (serializeSegment) ==="
rg -nP -B 3 -A 15 'serializeSegment' --type=cpp | head -100

echo -e "\n=== Segment deserialization (deserializeSegment) ==="
rg -nP -B 3 -A 15 'deserializeSegment' --type=cpp | head -100

Length of output: 12522


🏁 Script executed:

#!/bin/bash
# Find usage of the 'set' field in segments
echo "=== Usage of segment 'set' field ==="
rg -nP '\bset\s*[=:]|\.set\b|\->set\b|segment\.set|SEGMENT\.set' --type=cpp -C 2 | head -150

echo -e "\n=== Bitfield 'set' definition ==="
rg -nP 'uint8_t\s+set\s*:\s*2' --type=cpp -C 3

Length of output: 10714


🏁 Script executed:

#!/bin/bash
# Check for any hardcoded size assumptions about options
echo "=== Options size assumptions ==="
rg -nP 'sizeof.*options|options.*16|uint16_t\s+options' --type=cpp -C 2 | head -80

Length of output: 363


🏁 Script executed:

#!/bin/bash
# Find where map1D2D is read/written in JSON
echo "=== map1D2D in JSON handling ==="
rg -nP 'map1D2D|"m12"|m12' wled00/json.cpp -C 3 | head -120

Length of output: 2390


@softhack007, I have an elegant solution for you! Here's my recommendation to extend map1D2D to 4 bits:

Recommended Solution: Expand options from uint16_t to uint32_t

This is the most future-proof approach and gives you 16 additional bits for future segment features.

Changes needed:

1. In wled00/FX.h (around line 365):

union {
  uint32_t options; //bit pattern: msb first: [... future bits ...] set:2, soundSim:1, mapping:4, transposed, mirrorY, reverseY, transitional, freeze, reset, mirrored, on, reverse, selected
  struct {
    bool    selected    : 1;  //     0 : selected
    bool    reverse     : 1;  //     1 : reversed
    bool    on          : 1;  //     2 : is On
    bool    mirror      : 1;  //     3 : mirrored
    bool    freeze      : 1;  //     4 : paused/frozen
    bool    reset       : 1;  //     5 : indicates that Segment runtime requires reset
    bool    transitional: 1;  //     6 : transitional (there is transition occuring)
    bool    reverse_y   : 1;  //     7 : reversed Y (2D)
    bool    mirror_y    : 1;  //     8 : mirrored Y (2D)
    bool    transpose   : 1;  //     9 : transposed (2D, swapped X & Y)
    uint8_t map1D2D     : 4;  // 10-13 : mapping for 1D effect on 2D (0-15 supported)
    uint8_t soundSim    : 1;  //    14 : 0-1 sound simulation types
    uint8_t set         : 2;  // 15-16 : 0-3 UI segment sets/groups
    uint32_t reserved   : 15; // 17-31 : reserved for future use
  };
};

2. In wled00/FX_fcn.cpp (around line 1370-1371):

Update the Segment::differs() mask:

//bit pattern: (msb first) set:2, sound:1, mapping:4, transposed, mirrorY, reverseY, [transitional, reset,] paused, mirrored, on, reverse, [selected]
if ((options & 0b00000000000000011111111110011110U) != (b.options & 0b00000000000000011111111110011110U)) d |= SEG_DIFFERS_OPT;

3. In wled00/json.cpp (around line 178 and 331):

Update the constraint to allow values 0-15:

seg.map1D2D = constrain(map1D2D, 0, 15);

And remove the clamping at lines 168 and 331:

// Remove: if (map1D2D > 7) map1D2D = M12_pBar;

Why this solution is elegant:

  1. Future-proof: Gives you 15 reserved bits for future segment features
  2. Minimal breaking changes: JSON serialization already uses named fields, so no changes needed there
  3. Clean: No aliases or workarounds needed
  4. Memory impact: Only +2 bytes per segment (52 → 54 bytes), which is acceptable
  5. No functional breakage: differs() still works correctly with updated mask

Testing checklist:

  • ✅ Verify segment serialization/deserialization still works
  • ✅ Test that differs() correctly detects option changes
  • ✅ Confirm M12_sWaterfall (8) can now be stored and retrieved
  • ✅ Check memory usage is acceptable (should be ~64 bytes more for 32 segments)

This approach won't break any existing segment handling functions—you just need to update the differs() mask and the constraint values. The JSON code already handles this dynamically through field names.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@coderabbitai very good, thank you bunny,

Copy link

Choose a reason for hiding this comment

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

@softhack007, you're very welcome! 🐰

Happy to help! If you run into any issues during implementation or need further assistance with the waterfall mapping mode, feel free to reach out. Good luck with the PR! 🌊

@softhack007 softhack007 self-assigned this Oct 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant