Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 121 additions & 0 deletions wled00/FX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11024,6 +11024,126 @@ void mode_slow_transition(void) {
}
static const char _data_FX_MODE_SLOW_TRANSITION[] PROGMEM = "Slow Transition@Time (min),,,,,,Sweep;!;!;1;pal=2,sx=0,ix=0";

////////////////////////
// Liquid //
////////////////////////
/*
* Liquid: a calm, waving effect for 1-D strips. The field is the amplitude-
* weighted sum of up to 16 travelling sine waves (of decaying amplitude, rising frequency and alternating direction)
* rendered into the Primary and Secondary color wells (even / odd-indexed waves).
* Additionally, a 'shimmer' effect takes place within a configurable band around a configurable wave height,
* mimicking 'catching the light' of the wave, blending the pixel toward the Tertiary color.
*
* Sliders (0..255; "Shimmer Width" uses the 0..31 slider on WLED 17):
* 0 Speed wave motion (0 = frozen in place)
* 1 Waves number of active waves (byte / 16; 0 = off)
* 2 Wavelength spatial scale (0 = tiny waves; 255 = enormous but flat waves)
* 3 Shimmer Height weight height around which shimmer effect occurs, as a fraction (0..1) of field brightness.
* 4 Shimmer Width band half-width (0 = shimmer off; 31 = 0.2 brightness) around shimmer height
* Colors:
* Primary even-indexed waves
* Secondary odd-indexed waves
* Tertiary shimmer / "caught light" color
*
* Wave table (index k): spatial cycles per strip, relative amplitude, Hz,
* direction, and a fixed phase offset in radians.
*
* AI: below section was generated by an AI
*/
static const float LIQ_AMPS [16] = { 0.35f, 0.28f, 0.22f, 0.18f, 0.15f, 0.12f, 0.10f, 0.08f,
0.07f, 0.06f, 0.05f, 0.05f, 0.04f, 0.04f, 0.03f, 0.03f };
static const float LIQ_FREQ [16] = { 0.080f, 0.130f, 0.200f, 0.300f, 0.420f, 0.550f, 0.700f, 0.850f,
1.000f, 1.150f, 1.300f, 1.400f, 1.500f, 1.600f, 1.700f, 1.800f };
static const float LIQ_PHI [16] = { 0.00f, 1.30f, 2.60f, 3.90f, 5.20f, 6.30f, 7.40f, 8.60f,
9.70f, 10.80f, 11.90f, 13.00f, 14.10f, 15.20f, 16.30f, 17.40f };
static const float LIQ_DIR [16] = { 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1 };

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

this is just altering between 1 and -1, right?

could be simplified to something like index % 2 != 0 ? -1 : 1


void mode_liquid(void) {
if (SEGLEN <= 1) FX_FALLBACK_STATIC;

unsigned len = SEGLEN;
unsigned waves = SEGMENT.intensity / 16; // "Waves": 0..15
if (waves == 0) { // no waves -> off
SEGMENT.fill(0);
return;
}

// Slider parameters
float cscale = 5.0f - (float)SEGMENT.custom1 / 51.0f; // "Wavelength" spatial scale
float center = (float)SEGMENT.custom2 / 255.0f; // "Shimmer Height"
float hw = 0.30f * (float)SEGMENT.custom3 / 31.0f; // "Shimmer Width" (0 = off)
float times = (float)SEGMENT.speed * 2.0f / 255.0f; // "Speed" (0 = frozen)

// Well colors (RGBW32 -> RGB)
unsigned pR = (SEGCOLOR(0) >> 16) & 0xFF, pG = (SEGCOLOR(0) >> 8) & 0xFF, pB = SEGCOLOR(0) & 0xFF;
unsigned sR = (SEGCOLOR(1) >> 16) & 0xFF, sG = (SEGCOLOR(1) >> 8) & 0xFF, sB = SEGCOLOR(1) & 0xFF;
unsigned tR = (SEGCOLOR(2) >> 16) & 0xFF, tG = (SEGCOLOR(2) >> 8) & 0xFF, tB = SEGCOLOR(2) & 0xFF;

float now = (float)strip.now;

// Field time. Absolute seconds since boot scale by speed: the wave phases
// are absolute (2*pi*f*t), so the animation origin is fixed and the speed
// slider only slows or speeds the apparent motion (0 = frozen in place).
float t = now / 1000.0f * times;

float twoPi = M_TWOPI;

// Calculate color for all pixels
for (unsigned px = 0; px < len; px++) {
float primary = 0.0f, secondary = 0.0f, ampSum = 0.0f, briSum = 0.0f;
for (unsigned k = 0; k < waves; k++) {
float cycle = k + 1;
float direction = k % 2 != 0 ? -1 : 1;
float phase = twoPi * cycle * cscale * (float)px * direction / (float)len
+ twoPi * LIQ_FREQ[k] * t
+ LIQ_PHI[k];

@softhack007 softhack007 Oct 13, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

optimization: for more speed, try to move parts of this long computation out of the inner loop. For example, these parts of the above expression are not depending on k:

     twoPi * cscale * (float)px / (float)len
     twoPi * t

float bri = 0.5f + 0.5f * sin_t(phase); // 0..1
float w = LIQ_AMPS[k];
if (k & 1) {
// Add individual wave to secondary color brightness
secondary += w * bri;
} else {
// Add individual wave to primary color brightness
primary += w * bri;
}
ampSum += w;
briSum += bri;
}
if (ampSum <= 0.0f) {
ampSum = 1.0f;
}

// Wave color: amplitude-weighted blend of the two colors (0..1)
float r = ((float)pR * primary + (float)sR * secondary) / ampSum;
float g = ((float)pG * primary + (float)sG * secondary) / ampSum;
float b = ((float)pB * primary + (float)sB * secondary) / ampSum;

// Shimmer band gate (linear band around `center`, half-width `hw`), on mean brightness
float br = briSum / (float)waves; // 0..1
float gate = 0.0f;
if (hw > 0.0f) {
float d = br - center;
if (d < 0.0f) {
d = -d; // make absolute
}
if (d < hw) {
gate = (hw - d) / hw; // 0 at edges, 1 at center
}
}
// Calculate amount of 'shimmer' to be mixed into the color
unsigned mix = (unsigned)(gate * 255.0f * 0.3f); // max 30% mix to avoid too much flashing

// Final: wave color blended toward the Tertiary shimmer color
SEGMENT.setPixelColor((int)px,
(unsigned)(((r * (float)(255 - mix)) + (float)tR * (float)mix) / 255.0f + 0.5f),
(unsigned)(((g * (float)(255 - mix)) + (float)tG * (float)mix) / 255.0f + 0.5f),
(unsigned)(((b * (float)(255 - mix)) + (float)tB * (float)mix) / 255.0f + 0.5f));
}
}
static const char _data_FX_MODE_LIQUID[] PROGMEM =

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@coderabbitai please check if the metadata string is consistent with the effect code.

Copy link
Copy Markdown
Contributor

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
rg -n -C3 'extractMode|modeData|effect metadata' wled00 -g '*.{cpp,h,js}' | head -180
fd -i '(^AGENTS\.md$|cpp\.instructions\.md$|copilot-instructions\.md$)'
rg -n -C3 'Liquid@|void mode_liquid' wled00/FX.cpp

Length of output: 10876


🏁 Script executed:

#!/bin/bash
ast-grep outline wled00/util.cpp --match 'extractMode' --view expanded
sed -n '405,515p' wled00/util.cpp
rg -n -C4 "split\\(['\"];" --glob '*.js' .
rg -n -C4 'fxdef|fxdata|fxData|effectData' --glob '*.js' . | head -180

Length of output: 12108


@softhack007 The metadata string at Line 11114 is consistent with the effect code.

Metadata control Code field Default Result at default
Speed SEGMENT.speed 128 Time scale ≈ 1.004
Waves SEGMENT.intensity 128 8 active waves
Wavelength SEGMENT.custom1 128 Spatial scale ≈ 2.49
Shimmer Height SEGMENT.custom2 227 Center brightness ≈ 0.890
Shimmer Width SEGMENT.custom3 10 Band half-width ≈ 0.097

The remaining fields also match:

  • ;!,!,!; exposes all three color controls used by the effect.
  • The empty palette field hides the palette control. The effect does not use palettes.
  • The empty capability field defaults to 1 in the UI, which identifies a 1-D effect.
  • The defaults appear after the final semicolon, as required by extractModeDefaults().

Two code comments do not match the implementation:

  • The effect supports at most 15 waves, not 16: 255 / 16 == 15.
  • Shimmer Width reaches 0.30, not 0.20, at slider value 31.

Optionally, make the 1-D flag explicit and label the color controls by role:

static const char _data_FX_MODE_LIQUID[] PROGMEM =
  "Liquid@Speed,Waves,Wavelength,Shimmer Height,Shimmer Width;Primary,Secondary,Shimmer;;1;sx=128,ix=128,c1=128,c2=227,c3=10";

This is a clarity improvement, not a required metadata fix.

You are interacting with an AI system.

"Liquid@Speed,Waves,Wavelength,Shimmer Height,Shimmer Width;!,!,!;;;sx=128,ix=128,c1=128,c2=227,c3=10";
// AI: end

//////////////////////////////////////////////////////////////////////////////////////////
// mode data
static const char _data_RESERVED[] PROGMEM = "RSVD";
Expand Down Expand Up @@ -11190,6 +11310,7 @@ void WS2812FX::setupEffectData() {
addEffect(FX_MODE_DYNAMIC_SMOOTH, &mode_dynamic_smooth, _data_FX_MODE_DYNAMIC_SMOOTH);
addEffect(FX_MODE_PACMAN, &mode_pacman, _data_FX_MODE_PACMAN);
addEffect(FX_MODE_SLOW_TRANSITION, &mode_slow_transition, _data_FX_MODE_SLOW_TRANSITION);
addEffect(FX_MODE_LIQUID, &mode_liquid, _data_FX_MODE_LIQUID);

// --- 1D audio effects ---
addEffect(FX_MODE_PIXELS, &mode_pixels, _data_FX_MODE_PIXELS);
Expand Down
3 changes: 2 additions & 1 deletion wled00/FX.h
Original file line number Diff line number Diff line change
Expand Up @@ -375,8 +375,9 @@ extern byte realtimeMode; // used in getMappedPixelIndex()
#define FX_MODE_COLORCLOUDS 218
#define FX_MODE_SLOW_TRANSITION 219
#define FX_MODE_2DANALOGCLOCK 220 // new in 16.1
#define FX_MODE_LIQUID 221

#define MODE_COUNT 221
#define MODE_COUNT 222


#define TRANSITION_FADE 0x00 // universal
Expand Down