Skip to content

Commit 402995d

Browse files
authored
Update README.md
1 parent 7b6031b commit 402995d

File tree

1 file changed

+129
-3
lines changed

1 file changed

+129
-3
lines changed

usermods/user_fx/README.md

Lines changed: 129 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,135 @@ The string format WLED uses is semicolon-separated sections, with commas used fo
242242
Let’s split this into logical sections (delimited by semicolons ;):
243243
**TODO**
244244

245+
246+
247+
## Understanding 1D WLED Effects
248+
249+
Next, we will look at a 1D WLED effect called `Sinelon`. This one is an especially interesting example because it shows how a single effect function can be used to create several different selectable effects in the UI.
250+
we will break this effect down tep by step.
251+
(This effect was originally one of the FastLED example effects; more information on FastLED can be found [here](https://fastled.io/).)
252+
253+
```cpp
254+
static uint16_t sinelon_base(bool dual, bool rainbow=false) {
255+
```
256+
* The first line of code defines `sinelon base` as static helper function. This is how all effects are initially defined.
257+
* Notice that it has some optional flags; these parameters will allow us to easily define the effect in different ways in the UI.
258+
259+
```cpp
260+
if (SEGLEN <= 1) return mode_static();
261+
```
262+
* If segment length ≤ 1, there’s nothing to animate. Just show static mode.
263+
264+
The line of code helps create the "Fade Out" Trail:
265+
```cpp
266+
SEGMENT.fade_out(SEGMENT.intensity);
267+
```
268+
* Gradually dims all LEDs each frame using SEGMENT.intensity as fade amount.
269+
* Creates the trailing "comet" effect by leaving a fading path behind the moving dot.
270+
271+
Next, the effect computes some position information for the actively changing pixel, and the rest of the pixels as well:
272+
```cpp
273+
unsigned pos = beatsin16_t(SEGMENT.speed/10, 0, SEGLEN-1);
274+
if (SEGENV.call == 0) SEGENV.aux0 = pos;
275+
```
276+
* Calculates a sine-based oscillation to move the dot smoothly back and forth.
277+
* `beatsin16_t` is a variant of FastLED’s beatsin16 function, generating smooth oscillations
278+
* SEGMENT.speed / 10: affects oscillation speed. Higher = faster.
279+
* 0: minimum position.
280+
* SEGLEN-1: maximum position.
281+
* On first call `(SEGENV.call == 0)`, stores initial position in `SEGENV.aux0`. (`SEGENV.aux0` is a temporary state variable to keep track of last position.)
282+
283+
The next lines of code help determine the colors to be used:
284+
```cpp
285+
uint32_t color1 = SEGMENT.color_from_palette(pos, true, false, 0);
286+
uint32_t color2 = SEGCOLOR(2);
287+
```
288+
* `color1`: main moving dot color, chosen from palette using the current position as index.
289+
* `color2`: secondary color from user-configured color slot 2.
290+
291+
The next part taked into account the optional argument for if a Rainbow colored palette is in use:
292+
```cpp
293+
if (rainbow) {
294+
color1 = SEGMENT.color_wheel((pos & 0x07) * 32);
295+
}
296+
```
297+
* If `rainbow` is true, override color1 using a rainbow wheel, producing rainbow cycling colors.
298+
* `(pos & 0x07) * 32` ensures the color changes gradually with position.
299+
300+
```cpp
301+
SEGMENT.setPixelColor(pos, color1);
302+
```
303+
* Lights up the computed position with the selected color.
304+
305+
The next line takes into account another one of the optional arguments for the effect to potentially handle dual mirrored dots which create the animation:
306+
```cpp
307+
if (dual) {
308+
if (!color2) color2 = SEGMENT.color_from_palette(pos, true, false, 0);
309+
if (rainbow) color2 = color1; // share rainbow color
310+
SEGMENT.setPixelColor(SEGLEN-1-pos, color2);
311+
}
312+
```
313+
* If dual is true:
314+
* Uses `color2` for mirrored dot on opposite side.
315+
* If `color2` is not set (0), fallback to same palette color as `color1`.
316+
* In `rainbow` mode, force both dots to share the rainbow color.
317+
* Sets pixel at `SEGLEN-1-pos` to `color2`.
318+
319+
This final part of the effect function will fill in the 'trailing' pixels to complete the animation:
320+
```cpp
321+
if (SEGENV.aux0 < pos) {
322+
for (unsigned i = SEGENV.aux0; i < pos ; i++) {
323+
SEGMENT.setPixelColor(i, color1);
324+
if (dual) SEGMENT.setPixelColor(SEGLEN-1-i, color2);
325+
}
326+
} else {
327+
for (unsigned i = SEGENV.aux0; i > pos ; i--) {
328+
SEGMENT.setPixelColor(i, color1);
329+
if (dual) SEGMENT.setPixelColor(SEGLEN-1-i, color2);
330+
}
331+
}
332+
SEGENV.aux0 = pos;
333+
}
334+
```
335+
* The first line checks if current position has changed since last frame. (Prevents holes if the dot moves quickly and "skips" pixels.) If the position has changed, then it will implement the logic to update the rest of the pixels.
336+
* Fills in all pixels between previous position (SEGENV.aux0) and new position (pos) to ensure smooth continuous trail.
337+
* Works in both directions: Forward (if new pos > old pos), and Backward (if new pos < old pos).
338+
* Updates `SEGENV.aux0` to current position at the end.
339+
340+
Finally, we return the `FRAMETIME`, as with all effect functions:
341+
```cpp
342+
return FRAMETIME;
343+
}
344+
```
345+
* Returns `FRAMETIME` constant to set effect update rate (usually ~16 ms).
346+
347+
The last part of this effect has the Wrapper functions for different Sinelon modes.
348+
Notice that there are three different modes that we can define from the single effect definition by leveraging the arguments in the function:
349+
```cpp
350+
uint16_t mode_sinelon(void) {
351+
return sinelon_base(false);
352+
}
353+
# Calls sinelon_base with dual = false and rainbow = false
354+
355+
uint16_t mode_sinelon_dual(void) {
356+
return sinelon_base(true);
357+
}
358+
# Calls sinelon_base with dual = true and rainbow = false
359+
360+
uint16_t mode_sinelon_rainbow(void) {
361+
return sinelon_base(false, true);
362+
}
363+
# Calls sinelon_base with dual = false and rainbow = true
364+
```
365+
366+
And then the last part defines the metadata strings for each effect to speicfy how it will be portrayed in the UI:
367+
```cpp
368+
static const char _data_FX_MODE_SINELON[] PROGMEM = "Sinelon@!,Trail;!,!,!;!";
369+
static const char _data_FX_MODE_SINELON_DUAL[] PROGMEM = "Sinelon Dual@!,Trail;!,!,!;!";
370+
static const char _data_FX_MODE_SINELON_RAINBOW[] PROGMEM = "Sinelon Rainbow@!,Trail;,,!;!";
371+
```
372+
373+
245374
### The UserFxUsermod Class
246375

247376
The `UserFxUsermod` class registers the `mode_diffusionfire` effect with WLED. This section starts right after the effect function and metadata string, and is responsible for making the effect usable in the WLED interface:
@@ -290,9 +419,6 @@ REGISTER_USERMOD(user_fx);
290419
* The last line is a macro that tells WLED: “This is a valid usermod — load it during startup.”
291420
* WLED adds it to the list of active usermods, calls `setup()` and `loop()`, and lets it interact with the system.
292421

293-
## Understanding 1D WLED Effects
294-
295-
**TODO**
296422

297423
## Combining Multiple Effects in this Usermod
298424

0 commit comments

Comments
 (0)