From e8d481720a4e3ed878f490c9f24b8febfb10becf Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Sun, 26 Apr 2026 09:29:10 +0200 Subject: [PATCH 01/10] fix refresh of custom palettes --- wled00/data/cpal/cpal.htm | 6 +++++- wled00/data/index.js | 5 +++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/wled00/data/cpal/cpal.htm b/wled00/data/cpal/cpal.htm index b5abad054e..841c95be32 100644 --- a/wled00/data/cpal/cpal.htm +++ b/wled00/data/cpal/cpal.htm @@ -460,7 +460,11 @@ rm.className = 'sml'; rm.title = 'Delete palette'; rm.innerHTML = '✖'; - rm.onclick = () => { requestJson({rmcpal:i}); setTimeout(refr, 500); }; + rm.onclick = () => { + requestJson({rmcpal:i}); // send remove command + setTimeout(refr, 500); // slight delay to allow ESP to process deletion before fetching updated list + localStorage.removeItem('wledPalx'); // invalidate main UI cache + }; const name = isEmpty(p.palette) ? 'Empty slot' : 'Custom' + i; const css = isEmpty(p.palette) ? '#666' : cssArr(p.palette); diff --git a/wled00/data/index.js b/wled00/data/index.js index 023c2f00ab..ad2c812dc8 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -2819,7 +2819,7 @@ function loadPalettesData() { if (lsPalData) { try { var d = JSON.parse(lsPalData); - if (d && d.vid == lastinfo.vid) { + if (d && d.vid == lastinfo.vid && d.pcount == lastinfo.palcount) { palettesData = d.p; redrawPalPrev(); return resolve(); @@ -2831,7 +2831,8 @@ function loadPalettesData() { getPalettesData(0, () => { localStorage.setItem("wledPalx", JSON.stringify({ p: palettesData, - vid: lastinfo.vid + vid: lastinfo.vid, + pcount: lastinfo.palcount // total palette count, refresh cache if it changes })); redrawPalPrev(); setTimeout(resolve, 99); // delay optional From fd6f568023fca8500e4cb40712b913a8612b827d Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Sun, 26 Apr 2026 11:11:14 +0200 Subject: [PATCH 02/10] add usermod palettes to fix AR palette indexing --- usermods/audioreactive/audio_reactive.cpp | 41 +++++++++++------------ wled00/FX_fcn.cpp | 26 +++++++++++--- wled00/colors.cpp | 9 +++++ wled00/colors.h | 13 ++++++- wled00/const.h | 10 +++++- wled00/data/index.js | 27 +++++++++++---- wled00/json.cpp | 37 ++++++++++++++++---- wled00/util.cpp | 21 +++++++++--- wled00/wled.h | 3 +- 9 files changed, 140 insertions(+), 47 deletions(-) diff --git a/usermods/audioreactive/audio_reactive.cpp b/usermods/audioreactive/audio_reactive.cpp index 757ad35482..614367af20 100644 --- a/usermods/audioreactive/audio_reactive.cpp +++ b/usermods/audioreactive/audio_reactive.cpp @@ -1951,14 +1951,11 @@ class AudioReactive : public Usermod { } #endif } - if (palettes > 0 && root.containsKey(F("rmcpal"))) { - // handle removal of custom palettes from JSON call so we don't break things - removeAudioPalettes(); - } } void onStateChange(uint8_t callMode) override { - if (initDone && enabled && addPalettes && palettes==0 && customPalettes.size()0) { - customPalettes.pop_back(); - DEBUG_PRINTLN(palettes); - palettes--; - } - DEBUG_PRINT(F("Total # of palettes: ")); DEBUG_PRINTLN(customPalettes.size()); + palettes -= (int8_t)removeUsermodnPalettes(_name); + if (palettes < 0) palettes = 0; // safeguard + DEBUG_PRINT(F("Total # of usermod palettes: ")); DEBUG_PRINTLN(usermodnPalettes.size()); } void AudioReactive::createAudioPalettes(void) { - DEBUG_PRINT(F("Total # of palettes: ")); DEBUG_PRINTLN(customPalettes.size()); + DEBUG_PRINT(F("Total # of usermod palettes: ")); DEBUG_PRINTLN(usermodnPalettes.size()); if (palettes) return; DEBUG_PRINTLN(F("Adding audio palettes.")); - for (int i=0; i= palettes) lastCustPalette -= palettes; - for (int pal=0; pal= FIXED_PALETTE_COUNT && pal <= 255-customPalettes.size()) pal = 0; // out of bounds palette + const int umCount = usermodnPalettes.size(); + const int custCount = customPalettes.size(); + if (pal >= FIXED_PALETTE_COUNT) { + if (pal > WLED_CUSTOM_PALETTE_ID_BASE) { // usermod range (IDs 201-255) + if ((WLED_USERMOD_PALETTE_ID_BASE - pal) >= umCount) pal = 0; + } else { // custom range + if ((WLED_CUSTOM_PALETTE_ID_BASE - pal) >= custCount) pal = 0; + } + } //default palette. Differs depending on effect if (pal == 0) pal = _default_palette; // _default_palette is set in setMode() switch (pal) { @@ -267,8 +275,10 @@ void Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) { } break;} default: //progmem palettes - if (pal > 255 - customPalettes.size()) { - targetPalette = customPalettes[255-pal]; // we checked bounds above + if (pal > WLED_CUSTOM_PALETTE_ID_BASE) { // usermod palette + targetPalette = usermodnPalettes[WLED_USERMOD_PALETTE_ID_BASE - pal].palette; + } else if (pal >= FIXED_PALETTE_COUNT) { // user custom palette + targetPalette = customPalettes[WLED_CUSTOM_PALETTE_ID_BASE - pal]; } else if (pal < DYNAMIC_PALETTE_COUNT + FASTLED_PALETTE_COUNT) { // palette 6 - 12, fastled palettes targetPalette = *fastledPalettes[pal - DYNAMIC_PALETTE_COUNT]; } else { @@ -585,7 +595,13 @@ Segment &Segment::setMode(uint8_t fx, bool loadDefaults) { } Segment &Segment::setPalette(uint8_t pal) { - if (pal <= 255-customPalettes.size() && pal > FIXED_PALETTE_COUNT) pal = 0; // not built in palette or custom palette + if (pal >= FIXED_PALETTE_COUNT) { + if (pal > WLED_CUSTOM_PALETTE_ID_BASE) { // usermod range + if ((WLED_USERMOD_PALETTE_ID_BASE - pal) >= (int)usermodnPalettes.size()) pal = 0; + } else { // custom range + if ((WLED_CUSTOM_PALETTE_ID_BASE - pal) >= (int)customPalettes.size()) pal = 0; + } + } if (pal != palette) { //DEBUG_PRINTF_P(PSTR("- Starting palette transition: %d\n"), pal); startTransition(strip.getTransition(), blendingStyle != TRANSITION_FADE); // start transition prior to change (no need to copy segment) diff --git a/wled00/colors.cpp b/wled00/colors.cpp index 32fc521045..29d3e6ddf1 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -312,6 +312,15 @@ void loadCustomPalettes() { } } +size_t removeUsermodnPalettes(const char *name) { + size_t before = usermodnPalettes.size(); + for (int i = usermodnPalettes.size() - 1; i >= 0; i--) { + if (usermodnPalettes[i].name == name) + usermodnPalettes.erase(usermodnPalettes.begin() + i); + } + return before - usermodnPalettes.size(); +} + // convert HSV (16bit hue) to RGB (32bit with white = 0), optimized for speed WLED_O2_ATTR void hsv2rgb_spectrum(const CHSV32& hsv, CRGBW& rgb) { unsigned p, q, t; diff --git a/wled00/colors.h b/wled00/colors.h index 7b8c791c47..2cddcd63cc 100644 --- a/wled00/colors.h +++ b/wled00/colors.h @@ -61,9 +61,20 @@ void adjust_color(CRGBW& rgb, int32_t hueShift, int32_t satChange,int32_t valueC [[gnu::hot, gnu::pure]] uint32_t ColorFromPalette(const CRGBPalette16 &pal, unsigned index, uint8_t brightness = (uint8_t)255U, TBlendType blendType = LINEARBLEND); CRGBPalette16 generateHarmonicRandomPalette(const CRGBPalette16 &basepalette); CRGBPalette16 generateRandomPalette(); +// Palette registered by a usermod at fixed IDs (255, 254, 253... 201) +// palette name name will be um_name + index (e.g. "AudioReactive 1"), see util.cpp +struct UsermodnPalette { + CRGBPalette16 palette; + const char *name; // PROGMEM base name string (must not be nullptr) + uint8_t palIndex; // index of the palette for a usermod +}; + void loadCustomPalettes(); +// Remove all entries from usermodnPalettes whose name pointer matches 'name'. +size_t removeUsermodnPalettes(const char *name); extern std::vector customPalettes; -inline size_t getPaletteCount() { return FIXED_PALETTE_COUNT + customPalettes.size(); } +extern std::vector usermodnPalettes; +inline size_t getPaletteCount() { return FIXED_PALETTE_COUNT + usermodnPalettes.size() + customPalettes.size(); } void hsv2rgb_spectrum(const CHSV32& hsv, CRGBW& rgb); void hsv2rgb_spectrum(const CHSV& hsv, CRGB& rgb); diff --git a/wled00/const.h b/wled00/const.h index e49dd2900a..5808d4ebc9 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -10,8 +10,16 @@ constexpr size_t FASTLED_PALETTE_COUNT = 7; // 6-12 = sizeof(fastledPalettes) constexpr size_t GRADIENT_PALETTE_COUNT = 59; // 13-72 = sizeof(gGradientPalettes) / sizeof(gGradientPalettes[0]); constexpr size_t DYNAMIC_PALETTE_COUNT = 6; // 0- 5 = dynamic palettes (0=default(virtual),1=random,2=primary,3=primary+secondary,4=primary+secondary+tertiary,5=primary+secondary(+tertiary if not black) constexpr size_t FIXED_PALETTE_COUNT = DYNAMIC_PALETTE_COUNT + FASTLED_PALETTE_COUNT + GRADIENT_PALETTE_COUNT; // total number of fixed palettes + +// Palette ID space layout (palette IDs are uint8_t, 0-255): +// 0 .. FIXED_PALETTE_COUNT-1 : fixed built-in palettes +// 72 .. WLED_CUSTOM_PALETTE_ID_BASE(200) : user custom palettes (index 0 = ID 200, growing downward) +// 201.. WLED_USERMOD_PALETTE_ID_BASE(255): usermod-registered palettes (index 0 = ID 255, growing downward) +constexpr uint8_t WLED_USERMOD_PALETTE_ID_BASE = 255; // highest ID for usermod palettes +constexpr uint8_t WLED_CUSTOM_PALETTE_ID_BASE = 200; // highest ID for user custom palettes +constexpr size_t WLED_MAX_USERMOD_PALETTES = WLED_USERMOD_PALETTE_ID_BASE - WLED_CUSTOM_PALETTE_ID_BASE; // 55 slots (IDs 201-255) #ifndef ESP8266 - #define WLED_MAX_CUSTOM_PALETTES (255 - FIXED_PALETTE_COUNT) // allow up to 255 total palettes, user is warned about stability issues when adding more than 10 + #define WLED_MAX_CUSTOM_PALETTES (WLED_CUSTOM_PALETTE_ID_BASE - FIXED_PALETTE_COUNT + 1) // 129 slots (IDs 72-200) #else #define WLED_MAX_CUSTOM_PALETTES 10 // ESP8266: limit custom palettes to 10 #endif diff --git a/wled00/data/index.js b/wled00/data/index.js index ad2c812dc8..e49a8d2651 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -989,23 +989,38 @@ function populatePalettes() ); } gId('pallist').innerHTML=html; - // append custom palettes (when loading for the 1st time) + // append usermod palettes (fixed ID space: 255 down to 201) let li = lastinfo; - if (!isEmpty(li) && li.cpalcount) { - for (let j = 0; j e[1] === 128 && e[2] === 128 && e[3] === 128)) continue; // skip all gray gap-placeholder entries + if (!isEmpty(li) && li.umpalcount && li.umpalnames) { + for (let j = 0; j < li.umpalcount; j++) { let div = d.createElement("div"); gId('pallist').appendChild(div); div.outerHTML = generateListItemHtml( 'palette', 255-j, - '~ Custom '+j+' ~', + li.umpalnames[j], 'setPalette', `
` ); } } + // append user custom palettes (fixed ID space: 200 down to FIXED_PALETTE_COUNT+1) + if (!isEmpty(li) && li.cpalcount) { + for (let j = 0; j < li.cpalcount; j++) { + const id = 200 - j; + const pd = palettesData[id]; + if (pd && pd.length === 16 && pd.every(e => e[1] === 128 && e[2] === 128 && e[3] === 128)) continue; // skip gray gap-placeholder entries + let div = d.createElement("div"); + gId('pallist').appendChild(div); + div.outerHTML = generateListItemHtml( + 'palette', + id, + '~ Custom '+j+' ~', + 'setPalette', + `
` + ); + } + } } function redrawPalPrev() diff --git a/wled00/json.cpp b/wled00/json.cpp index b8f29e08d6..fa5c85a268 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -774,8 +774,18 @@ void serializeInfo(JsonObject root) root[F("fxcount")] = strip.getModeCount(); root[F("palcount")] = getPaletteCount(); - root[F("cpalcount")] = customPalettes.size(); // number of custom palettes (includes gray placeholders) + root[F("cpalcount")] = customPalettes.size(); // number of user custom palettes (includes gray placeholders) + root[F("umpalcount")] = usermodnPalettes.size(); // number of usermod-registered palettes root[F("cpalmax")] = WLED_MAX_CUSTOM_PALETTES; // maximum number of custom palettes + // send usermod palette names so the UI can label them correctly + if (usermodnPalettes.size() > 0) { + JsonArray umpalnames = root.createNestedArray(F("umpalnames")); + for (size_t j = 0; j < usermodnPalettes.size(); j++) { + char buf[34]; + extractModeName(WLED_USERMOD_PALETTE_ID_BASE - j, JSON_palette_names, buf, sizeof(buf) - 1); + umpalnames.add(buf); + } + } JsonArray ledmaps = root.createNestedArray(F("maps")); for (size_t i=0; i maxPage) page = maxPage; const int start = itemPerPage * page; - int end = min(start + itemPerPage, palettesCount + customPalettesCount); + int end = min(start + itemPerPage, palettesCount + umPalettesCount + customPalettesCount); root[F("m")] = maxPage; // inform caller how many pages there are JsonObject palettes = root.createNestedObject("p"); for (int i = start; i < end; i++) { - JsonArray curPalette = palettes.createNestedArray(String(i >= palettesCount ? 255 - i + palettesCount : i)); + // compute the palette ID for this sequential index + int paletteId; + if (i >= palettesCount + umPalettesCount) // user custom palette (IDs 200, 199, ...) + paletteId = WLED_CUSTOM_PALETTE_ID_BASE - (i - palettesCount - umPalettesCount); + else if (i >= palettesCount) // usermod palette (IDs 255, 254, ...) + paletteId = WLED_USERMOD_PALETTE_ID_BASE - (i - palettesCount); + else + paletteId = i; // fixed palette + JsonArray curPalette = palettes.createNestedArray(String(paletteId)); switch (i) { case 0: //default palette setPaletteColors(curPalette, PartyColors_gc22); @@ -987,9 +1006,13 @@ void serializePalettes(JsonObject root, int page) curPalette.add("c1"); break; default: - if (i >= palettesCount) // custom palettes - setPaletteColors(curPalette, customPalettes[i - palettesCount]); - else if (i < DYNAMIC_PALETTE_COUNT + FASTLED_PALETTE_COUNT) // palette 6 - 12, fastled palettes + if (i >= palettesCount + umPalettesCount) { // user custom palettes (lowest IDs in the custom range) + int custIdx = i - palettesCount - umPalettesCount; + setPaletteColors(curPalette, customPalettes[custIdx]); + } else if (i >= palettesCount) { // usermod palettes (IDs 255, 254, ...) + int umIdx = i - palettesCount; + setPaletteColors(curPalette, usermodnPalettes[umIdx].palette); + } else if (i < DYNAMIC_PALETTE_COUNT + FASTLED_PALETTE_COUNT) // palette 6 - 12, fastled palettes setPaletteColors(curPalette, *fastledPalettes[i - DYNAMIC_PALETTE_COUNT]); else { memcpy_P(tcp, (byte*)pgm_read_dword(&(gGradientPalettes[i - (DYNAMIC_PALETTE_COUNT + FASTLED_PALETTE_COUNT)])), sizeof(tcp)); diff --git a/wled00/util.cpp b/wled00/util.cpp index 9830d5b6f6..6158d83f99 100644 --- a/wled00/util.cpp +++ b/wled00/util.cpp @@ -317,10 +317,23 @@ uint8_t extractModeName(uint8_t mode, const char *src, char *dest, uint8_t maxLe } else return 0; } - if (src == JSON_palette_names && mode > 255-customPalettes.size()) { - snprintf_P(dest, maxLen, PSTR("~ Custom %d ~"), 255-mode); - dest[maxLen] = '\0'; - return strlen(dest); + if (src == JSON_palette_names) { + if (mode > WLED_CUSTOM_PALETTE_ID_BASE) { + // usermod palette (IDs 201-255) + uint8_t umIdx = WLED_USERMOD_PALETTE_ID_BASE - mode; + const UsermodnPalette &ump = usermodnPalettes[umIdx]; + char base[33]; + strncpy_P(base, ump.name, sizeof(base) - 1); + base[sizeof(base) - 1] = '\0'; + snprintf(dest, maxLen + 1, "%s %u", base, (unsigned)ump.palIndex); // palette name is um name + index (e.g. "AudioReactive 1") + return strlen(dest); + } + if (mode >= FIXED_PALETTE_COUNT && mode <= WLED_CUSTOM_PALETTE_ID_BASE) { + // user custom palette (IDs FIXED_PALETTE_COUNT up to WLED_CUSTOM_PALETTE_ID_BASE=200) + snprintf_P(dest, maxLen, PSTR("~ Custom %d ~"), WLED_CUSTOM_PALETTE_ID_BASE - mode); + dest[maxLen] = '\0'; + return strlen(dest); + } } unsigned qComma = 0; diff --git a/wled00/wled.h b/wled00/wled.h index b96264c25c..794c59e1a1 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -599,7 +599,8 @@ WLED_GLOBAL bool wasConnected _INIT(false); // color WLED_GLOBAL byte lastRandomIndex _INIT(0); // used to save last random color so the new one is not the same -WLED_GLOBAL std::vector customPalettes; // custom palettes +WLED_GLOBAL std::vector customPalettes; // custom palettes (file-based, IDs grow downwards starting at 200) +WLED_GLOBAL std::vector usermodnPalettes; // usermod-registered palettes (IDs 255, 254, 253...) WLED_GLOBAL uint8_t paletteBlend _INIT(0); // determines blending and wrapping of palette: 0: blend, wrap if moving (SEGMENT.speed>0); 1: blend, always wrap; 2: blend, never wrap; 3: don't blend or wrap // transitions From f16ca9c8ae2e2609657cbd7d01b37e6d7ea5742d Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Sun, 26 Apr 2026 22:22:04 +0200 Subject: [PATCH 03/10] rename and bugfixes --- usermods/audioreactive/audio_reactive.cpp | 14 ++++++-------- wled00/FX_fcn.cpp | 6 +++--- wled00/colors.cpp | 12 ++++++------ wled00/colors.h | 14 ++++++-------- wled00/data/index.js | 1 + wled00/json.cpp | 10 +++++----- wled00/util.cpp | 6 +++++- wled00/wled.h | 2 +- 8 files changed, 33 insertions(+), 32 deletions(-) diff --git a/usermods/audioreactive/audio_reactive.cpp b/usermods/audioreactive/audio_reactive.cpp index 614367af20..91d07e4c73 100644 --- a/usermods/audioreactive/audio_reactive.cpp +++ b/usermods/audioreactive/audio_reactive.cpp @@ -1955,7 +1955,7 @@ class AudioReactive : public Usermod { void onStateChange(uint8_t callMode) override { if (initDone && enabled && addPalettes && palettes==0 - && (int)(usermodnPalettes.size() + MAX_PALETTES) <= WLED_MAX_USERMOD_PALETTES) { + && (int)(usermodPalettes.size() + MAX_PALETTES) <= WLED_MAX_USERMOD_PALETTES) { // if palettes were removed during JSON call re-add them createAudioPalettes(); } @@ -2184,19 +2184,17 @@ class AudioReactive : public Usermod { void AudioReactive::removeAudioPalettes(void) { DEBUG_PRINTLN(F("Removing audio palettes.")); - palettes -= (int8_t)removeUsermodnPalettes(_name); + palettes -= (int8_t)removeusermodPalettes(_name); if (palettes < 0) palettes = 0; // safeguard - DEBUG_PRINT(F("Total # of usermod palettes: ")); DEBUG_PRINTLN(usermodnPalettes.size()); } void AudioReactive::createAudioPalettes(void) { - DEBUG_PRINT(F("Total # of usermod palettes: ")); DEBUG_PRINTLN(usermodnPalettes.size()); if (palettes) return; DEBUG_PRINTLN(F("Adding audio palettes.")); for (int i=0; i= FIXED_PALETTE_COUNT) { if (pal > WLED_CUSTOM_PALETTE_ID_BASE) { // usermod range (IDs 201-255) @@ -276,7 +276,7 @@ void Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) { break;} default: //progmem palettes if (pal > WLED_CUSTOM_PALETTE_ID_BASE) { // usermod palette - targetPalette = usermodnPalettes[WLED_USERMOD_PALETTE_ID_BASE - pal].palette; + targetPalette = usermodPalettes[WLED_USERMOD_PALETTE_ID_BASE - pal].palette; } else if (pal >= FIXED_PALETTE_COUNT) { // user custom palette targetPalette = customPalettes[WLED_CUSTOM_PALETTE_ID_BASE - pal]; } else if (pal < DYNAMIC_PALETTE_COUNT + FASTLED_PALETTE_COUNT) { // palette 6 - 12, fastled palettes @@ -597,7 +597,7 @@ Segment &Segment::setMode(uint8_t fx, bool loadDefaults) { Segment &Segment::setPalette(uint8_t pal) { if (pal >= FIXED_PALETTE_COUNT) { if (pal > WLED_CUSTOM_PALETTE_ID_BASE) { // usermod range - if ((WLED_USERMOD_PALETTE_ID_BASE - pal) >= (int)usermodnPalettes.size()) pal = 0; + if ((WLED_USERMOD_PALETTE_ID_BASE - pal) >= (int)usermodPalettes.size()) pal = 0; } else { // custom range if ((WLED_CUSTOM_PALETTE_ID_BASE - pal) >= (int)customPalettes.size()) pal = 0; } diff --git a/wled00/colors.cpp b/wled00/colors.cpp index 29d3e6ddf1..92fc934eee 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -312,13 +312,13 @@ void loadCustomPalettes() { } } -size_t removeUsermodnPalettes(const char *name) { - size_t before = usermodnPalettes.size(); - for (int i = usermodnPalettes.size() - 1; i >= 0; i--) { - if (usermodnPalettes[i].name == name) - usermodnPalettes.erase(usermodnPalettes.begin() + i); +size_t removeusermodPalettes(const char *name) { + size_t before = usermodPalettes.size(); + for (int i = usermodPalettes.size() - 1; i >= 0; i--) { + if (usermodPalettes[i].name == name) + usermodPalettes.erase(usermodPalettes.begin() + i); } - return before - usermodnPalettes.size(); + return before - usermodPalettes.size(); } // convert HSV (16bit hue) to RGB (32bit with white = 0), optimized for speed diff --git a/wled00/colors.h b/wled00/colors.h index 2cddcd63cc..288e1e421a 100644 --- a/wled00/colors.h +++ b/wled00/colors.h @@ -61,20 +61,18 @@ void adjust_color(CRGBW& rgb, int32_t hueShift, int32_t satChange,int32_t valueC [[gnu::hot, gnu::pure]] uint32_t ColorFromPalette(const CRGBPalette16 &pal, unsigned index, uint8_t brightness = (uint8_t)255U, TBlendType blendType = LINEARBLEND); CRGBPalette16 generateHarmonicRandomPalette(const CRGBPalette16 &basepalette); CRGBPalette16 generateRandomPalette(); -// Palette registered by a usermod at fixed IDs (255, 254, 253... 201) -// palette name name will be um_name + index (e.g. "AudioReactive 1"), see util.cpp +// Palette registered by a usermod at fixed IDs (255, 254, 253... 201), palette name will be name + index (e.g. "AudioReactive 1"), see util.cpp struct UsermodnPalette { CRGBPalette16 palette; - const char *name; // PROGMEM base name string (must not be nullptr) - uint8_t palIndex; // index of the palette for a usermod + const char *name; // PROGMEM base name string (must not be nullptr), this name is used in removeusermodPalettes() + uint8_t palIndex; // index of the palette for a usermod }; void loadCustomPalettes(); -// Remove all entries from usermodnPalettes whose name pointer matches 'name'. -size_t removeUsermodnPalettes(const char *name); +size_t removeusermodPalettes(const char *name); // remove all entries from usermodPalettes whose name pointer matches 'name' extern std::vector customPalettes; -extern std::vector usermodnPalettes; -inline size_t getPaletteCount() { return FIXED_PALETTE_COUNT + usermodnPalettes.size() + customPalettes.size(); } +extern std::vector usermodPalettes; +inline size_t getPaletteCount() { return FIXED_PALETTE_COUNT + usermodPalettes.size() + customPalettes.size(); } void hsv2rgb_spectrum(const CHSV32& hsv, CRGBW& rgb); void hsv2rgb_spectrum(const CHSV& hsv, CRGB& rgb); diff --git a/wled00/data/index.js b/wled00/data/index.js index e49a8d2651..ee5126973c 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -1021,6 +1021,7 @@ function populatePalettes() ); } } + updateSelectedPalette(selectedPal); // update selection after adding usermod and custom palettes } function redrawPalPrev() diff --git a/wled00/json.cpp b/wled00/json.cpp index fa5c85a268..74e12b5470 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -775,12 +775,12 @@ void serializeInfo(JsonObject root) root[F("fxcount")] = strip.getModeCount(); root[F("palcount")] = getPaletteCount(); root[F("cpalcount")] = customPalettes.size(); // number of user custom palettes (includes gray placeholders) - root[F("umpalcount")] = usermodnPalettes.size(); // number of usermod-registered palettes + root[F("umpalcount")] = usermodPalettes.size(); // number of usermod-registered palettes root[F("cpalmax")] = WLED_MAX_CUSTOM_PALETTES; // maximum number of custom palettes // send usermod palette names so the UI can label them correctly - if (usermodnPalettes.size() > 0) { + if (usermodPalettes.size() > 0) { JsonArray umpalnames = root.createNestedArray(F("umpalnames")); - for (size_t j = 0; j < usermodnPalettes.size(); j++) { + for (size_t j = 0; j < usermodPalettes.size(); j++) { char buf[34]; extractModeName(WLED_USERMOD_PALETTE_ID_BASE - j, JSON_palette_names, buf, sizeof(buf) - 1); umpalnames.add(buf); @@ -956,7 +956,7 @@ void serializePalettes(JsonObject root, int page) #endif const int customPalettesCount = customPalettes.size(); - const int umPalettesCount = usermodnPalettes.size(); + const int umPalettesCount = usermodPalettes.size(); const int palettesCount = FIXED_PALETTE_COUNT; // palettesCount is number of palettes, not palette index const int maxPage = (palettesCount + umPalettesCount + customPalettesCount) / itemPerPage; @@ -1011,7 +1011,7 @@ void serializePalettes(JsonObject root, int page) setPaletteColors(curPalette, customPalettes[custIdx]); } else if (i >= palettesCount) { // usermod palettes (IDs 255, 254, ...) int umIdx = i - palettesCount; - setPaletteColors(curPalette, usermodnPalettes[umIdx].palette); + setPaletteColors(curPalette, usermodPalettes[umIdx].palette); } else if (i < DYNAMIC_PALETTE_COUNT + FASTLED_PALETTE_COUNT) // palette 6 - 12, fastled palettes setPaletteColors(curPalette, *fastledPalettes[i - DYNAMIC_PALETTE_COUNT]); else { diff --git a/wled00/util.cpp b/wled00/util.cpp index 6158d83f99..ff9a68cc7d 100644 --- a/wled00/util.cpp +++ b/wled00/util.cpp @@ -321,7 +321,11 @@ uint8_t extractModeName(uint8_t mode, const char *src, char *dest, uint8_t maxLe if (mode > WLED_CUSTOM_PALETTE_ID_BASE) { // usermod palette (IDs 201-255) uint8_t umIdx = WLED_USERMOD_PALETTE_ID_BASE - mode; - const UsermodnPalette &ump = usermodnPalettes[umIdx]; + if (umIdx >= usermodPalettes.size()) { + dest[0] = '\0'; // empty string if requested index is out of bounds + return 0; + } + const UsermodnPalette &ump = usermodPalettes[umIdx]; char base[33]; strncpy_P(base, ump.name, sizeof(base) - 1); base[sizeof(base) - 1] = '\0'; diff --git a/wled00/wled.h b/wled00/wled.h index 794c59e1a1..779d6b37ea 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -600,7 +600,7 @@ WLED_GLOBAL bool wasConnected _INIT(false); // color WLED_GLOBAL byte lastRandomIndex _INIT(0); // used to save last random color so the new one is not the same WLED_GLOBAL std::vector customPalettes; // custom palettes (file-based, IDs grow downwards starting at 200) -WLED_GLOBAL std::vector usermodnPalettes; // usermod-registered palettes (IDs 255, 254, 253...) +WLED_GLOBAL std::vector usermodPalettes; // usermod-registered palettes (IDs 255, 254, 253...) WLED_GLOBAL uint8_t paletteBlend _INIT(0); // determines blending and wrapping of palette: 0: blend, wrap if moving (SEGMENT.speed>0); 1: blend, always wrap; 2: blend, never wrap; 3: don't blend or wrap // transitions From d7d4e7dfb43239f3fd08a48991ff400cc3aad37d Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Sun, 26 Apr 2026 22:25:47 +0200 Subject: [PATCH 04/10] apply default pal before bounds check --- wled00/FX_fcn.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 704be43295..cbd937fb73 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -231,6 +231,7 @@ void Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) { // then come user custom palettes (IDs <=200) and usermod palettes (IDs 201-255), both growing downward from their respective base IDs // palette 0 is a varying palette depending on effect and may be replaced by segment's color if so // instructed in color_from_palette() + if (pal == 0) pal = _default_palette; // _default_palette is set in setMode(), differs depending on effect const int umCount = usermodPalettes.size(); const int custCount = customPalettes.size(); if (pal >= FIXED_PALETTE_COUNT) { @@ -240,8 +241,6 @@ void Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) { if ((WLED_CUSTOM_PALETTE_ID_BASE - pal) >= custCount) pal = 0; } } - //default palette. Differs depending on effect - if (pal == 0) pal = _default_palette; // _default_palette is set in setMode() switch (pal) { case 0: //default palette. Exceptions for specific effects above targetPalette = PartyColors_gc22; From 449069a344c8b8218af433a5e34cc40301e9909b Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Sun, 26 Apr 2026 22:32:18 +0200 Subject: [PATCH 05/10] rename removeUsermodPalettes --- usermods/audioreactive/audio_reactive.cpp | 2 +- wled00/colors.cpp | 2 +- wled00/colors.h | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/usermods/audioreactive/audio_reactive.cpp b/usermods/audioreactive/audio_reactive.cpp index 91d07e4c73..10ffa85772 100644 --- a/usermods/audioreactive/audio_reactive.cpp +++ b/usermods/audioreactive/audio_reactive.cpp @@ -2184,7 +2184,7 @@ class AudioReactive : public Usermod { void AudioReactive::removeAudioPalettes(void) { DEBUG_PRINTLN(F("Removing audio palettes.")); - palettes -= (int8_t)removeusermodPalettes(_name); + palettes -= (int8_t)removeUsermodPalettes(_name); if (palettes < 0) palettes = 0; // safeguard } diff --git a/wled00/colors.cpp b/wled00/colors.cpp index 92fc934eee..6ddc4ec892 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -312,7 +312,7 @@ void loadCustomPalettes() { } } -size_t removeusermodPalettes(const char *name) { +size_t removeUsermodPalettes(const char *name) { size_t before = usermodPalettes.size(); for (int i = usermodPalettes.size() - 1; i >= 0; i--) { if (usermodPalettes[i].name == name) diff --git a/wled00/colors.h b/wled00/colors.h index 288e1e421a..95e8f84ee6 100644 --- a/wled00/colors.h +++ b/wled00/colors.h @@ -64,12 +64,12 @@ CRGBPalette16 generateRandomPalette(); // Palette registered by a usermod at fixed IDs (255, 254, 253... 201), palette name will be name + index (e.g. "AudioReactive 1"), see util.cpp struct UsermodnPalette { CRGBPalette16 palette; - const char *name; // PROGMEM base name string (must not be nullptr), this name is used in removeusermodPalettes() + const char *name; // PROGMEM base name string (must not be nullptr), this name is used in removeUsermodPalettes() uint8_t palIndex; // index of the palette for a usermod }; void loadCustomPalettes(); -size_t removeusermodPalettes(const char *name); // remove all entries from usermodPalettes whose name pointer matches 'name' +size_t removeUsermodPalettes(const char *name); // remove all entries from usermodPalettes whose name pointer matches 'name' extern std::vector customPalettes; extern std::vector usermodPalettes; inline size_t getPaletteCount() { return FIXED_PALETTE_COUNT + usermodPalettes.size() + customPalettes.size(); } From eb60ff91e4667ac7eda6b68e4b7dea8b2e5e6dc5 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Sun, 26 Apr 2026 22:35:12 +0200 Subject: [PATCH 06/10] fix typo --- wled00/colors.h | 4 ++-- wled00/util.cpp | 2 +- wled00/wled.h | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/wled00/colors.h b/wled00/colors.h index 95e8f84ee6..a5073fbb02 100644 --- a/wled00/colors.h +++ b/wled00/colors.h @@ -62,7 +62,7 @@ void adjust_color(CRGBW& rgb, int32_t hueShift, int32_t satChange,int32_t valueC CRGBPalette16 generateHarmonicRandomPalette(const CRGBPalette16 &basepalette); CRGBPalette16 generateRandomPalette(); // Palette registered by a usermod at fixed IDs (255, 254, 253... 201), palette name will be name + index (e.g. "AudioReactive 1"), see util.cpp -struct UsermodnPalette { +struct UsermodPalette { CRGBPalette16 palette; const char *name; // PROGMEM base name string (must not be nullptr), this name is used in removeUsermodPalettes() uint8_t palIndex; // index of the palette for a usermod @@ -71,7 +71,7 @@ struct UsermodnPalette { void loadCustomPalettes(); size_t removeUsermodPalettes(const char *name); // remove all entries from usermodPalettes whose name pointer matches 'name' extern std::vector customPalettes; -extern std::vector usermodPalettes; +extern std::vector usermodPalettes; inline size_t getPaletteCount() { return FIXED_PALETTE_COUNT + usermodPalettes.size() + customPalettes.size(); } void hsv2rgb_spectrum(const CHSV32& hsv, CRGBW& rgb); diff --git a/wled00/util.cpp b/wled00/util.cpp index ff9a68cc7d..1bcdef6540 100644 --- a/wled00/util.cpp +++ b/wled00/util.cpp @@ -325,7 +325,7 @@ uint8_t extractModeName(uint8_t mode, const char *src, char *dest, uint8_t maxLe dest[0] = '\0'; // empty string if requested index is out of bounds return 0; } - const UsermodnPalette &ump = usermodPalettes[umIdx]; + const UsermodPalette &ump = usermodPalettes[umIdx]; char base[33]; strncpy_P(base, ump.name, sizeof(base) - 1); base[sizeof(base) - 1] = '\0'; diff --git a/wled00/wled.h b/wled00/wled.h index 779d6b37ea..c1887116b2 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -600,7 +600,7 @@ WLED_GLOBAL bool wasConnected _INIT(false); // color WLED_GLOBAL byte lastRandomIndex _INIT(0); // used to save last random color so the new one is not the same WLED_GLOBAL std::vector customPalettes; // custom palettes (file-based, IDs grow downwards starting at 200) -WLED_GLOBAL std::vector usermodPalettes; // usermod-registered palettes (IDs 255, 254, 253...) +WLED_GLOBAL std::vector usermodPalettes; // usermod-registered palettes (IDs 255, 254, 253...) WLED_GLOBAL uint8_t paletteBlend _INIT(0); // determines blending and wrapping of palette: 0: blend, wrap if moving (SEGMENT.speed>0); 1: blend, always wrap; 2: blend, never wrap; 3: don't blend or wrap // transitions From 120da32d696a8b2f145e72f44e8c8acd349c5111 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Sun, 26 Apr 2026 22:42:07 +0200 Subject: [PATCH 07/10] remove unneeded check --- usermods/audioreactive/audio_reactive.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/usermods/audioreactive/audio_reactive.cpp b/usermods/audioreactive/audio_reactive.cpp index 10ffa85772..16137b9126 100644 --- a/usermods/audioreactive/audio_reactive.cpp +++ b/usermods/audioreactive/audio_reactive.cpp @@ -1954,8 +1954,7 @@ class AudioReactive : public Usermod { } void onStateChange(uint8_t callMode) override { - if (initDone && enabled && addPalettes && palettes==0 - && (int)(usermodPalettes.size() + MAX_PALETTES) <= WLED_MAX_USERMOD_PALETTES) { + if (initDone && enabled && addPalettes && palettes==0) { // if palettes were removed during JSON call re-add them createAudioPalettes(); } @@ -2192,7 +2191,7 @@ void AudioReactive::createAudioPalettes(void) { if (palettes) return; DEBUG_PRINTLN(F("Adding audio palettes.")); for (int i=0; i Date: Sun, 3 May 2026 13:40:31 +0100 Subject: [PATCH 08/10] Add name to usermod palette --- usermods/audioreactive/audio_reactive.cpp | 12 +++++++++--- wled00/colors.h | 4 +++- wled00/util.cpp | 15 +++++++++++---- 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/usermods/audioreactive/audio_reactive.cpp b/usermods/audioreactive/audio_reactive.cpp index 16137b9126..afcaac877c 100644 --- a/usermods/audioreactive/audio_reactive.cpp +++ b/usermods/audioreactive/audio_reactive.cpp @@ -879,6 +879,9 @@ class AudioReactive : public Usermod { #endif static const char _digitalmic[]; static const char _addPalettes[]; + static const char _palName0[]; + static const char _palName1[]; + static const char _palName2[]; static const char UDP_SYNC_HEADER[]; static const char UDP_SYNC_HEADER_v1[]; @@ -2190,10 +2193,10 @@ void AudioReactive::removeAudioPalettes(void) { void AudioReactive::createAudioPalettes(void) { if (palettes) return; DEBUG_PRINTLN(F("Adding audio palettes.")); + static const char *const palNames[MAX_PALETTES] PROGMEM = {_palName0, _palName1, _palName2}; for (int i=0; i= FIXED_PALETTE_COUNT && mode <= WLED_CUSTOM_PALETTE_ID_BASE) { From 41fd877adbce2459579f447a1069bdf6b29b7bd1 Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Sun, 3 May 2026 14:24:27 +0100 Subject: [PATCH 09/10] Use usermod name prefix for usermod-supplied palette names Palette display names now follow the format "UMName: palName" (e.g. "AudioReactive: Audio Responsive Hue") when a usermod supplies a specific palName, keeping the usermod name as a consistent prefix. Fallback when palName is nullptr remains "UMName index". Update AudioReactive palette names to the full descriptive names from the original WLED-MM port: "Audio Responsive Ratio", "Audio Responsive Hue", and "Audio Responsive Spectrum". --- usermods/audioreactive/audio_reactive.cpp | 6 +++--- wled00/colors.h | 4 ++-- wled00/util.cpp | 14 ++++++++------ 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/usermods/audioreactive/audio_reactive.cpp b/usermods/audioreactive/audio_reactive.cpp index afcaac877c..0b3c93fdb3 100644 --- a/usermods/audioreactive/audio_reactive.cpp +++ b/usermods/audioreactive/audio_reactive.cpp @@ -2280,9 +2280,9 @@ const char AudioReactive::_analogmic[] PROGMEM = "analogmic"; #endif const char AudioReactive::_digitalmic[] PROGMEM = "digitalmic"; const char AudioReactive::_addPalettes[] PROGMEM = "add-palettes"; -const char AudioReactive::_palName0[] PROGMEM = "Ratio"; -const char AudioReactive::_palName1[] PROGMEM = "Hue"; -const char AudioReactive::_palName2[] PROGMEM = "Spectrum"; +const char AudioReactive::_palName0[] PROGMEM = "Audio Responsive Ratio"; +const char AudioReactive::_palName1[] PROGMEM = "Audio Responsive Hue"; +const char AudioReactive::_palName2[] PROGMEM = "Audio Responsive Spectrum"; const char AudioReactive::UDP_SYNC_HEADER[] PROGMEM = "00002"; // new sync header version, as format no longer compatible with previous structure const char AudioReactive::UDP_SYNC_HEADER_v1[] PROGMEM = "00001"; // old sync header version - need to add backwards-compatibility feature diff --git a/wled00/colors.h b/wled00/colors.h index 5b4703a4d9..105048d35d 100644 --- a/wled00/colors.h +++ b/wled00/colors.h @@ -62,12 +62,12 @@ void adjust_color(CRGBW& rgb, int32_t hueShift, int32_t satChange,int32_t valueC CRGBPalette16 generateHarmonicRandomPalette(const CRGBPalette16 &basepalette); CRGBPalette16 generateRandomPalette(); // Palette registered by a usermod at fixed IDs (255, 254, 253... 201). -// Display name is palName (if non-null) or falls back to name + index (e.g. "AudioReactive 1"), see util.cpp +// Display name is "name: palName" (if palName non-null) or falls back to "name index" (e.g. "AudioReactive 1"), see util.cpp struct UsermodPalette { CRGBPalette16 palette; const char *name; // PROGMEM base name string (must not be nullptr), this name is used in removeUsermodPalettes() uint8_t palIndex; // index of the palette for a usermod - const char *palName; // optional PROGMEM display name for this palette; if nullptr, falls back to "name index" + const char *palName; // optional PROGMEM display name; if set, shown as "name: palName" (e.g. "AudioReactive: Audio Responsive Hue"), otherwise falls back to "name index" }; void loadCustomPalettes(); diff --git a/wled00/util.cpp b/wled00/util.cpp index bbb3944ede..66aaea97fc 100644 --- a/wled00/util.cpp +++ b/wled00/util.cpp @@ -326,15 +326,17 @@ uint8_t extractModeName(uint8_t mode, const char *src, char *dest, uint8_t maxLe return 0; } const UsermodPalette &ump = usermodPalettes[umIdx]; + char base[33]; + strncpy_P(base, ump.name, sizeof(base) - 1); + base[sizeof(base) - 1] = '\0'; if (ump.palName) { - // usermod supplied a specific display name for this palette - strncpy_P(dest, ump.palName, maxLen); - dest[maxLen] = '\0'; + // usermod supplied a specific display name — prefix with the usermod name (e.g. "AudioReactive: Hue") + char palName[33]; + strncpy_P(palName, ump.palName, sizeof(palName) - 1); + palName[sizeof(palName) - 1] = '\0'; + snprintf(dest, maxLen + 1, "%s: %s", base, palName); } else { // fallback: "UMName index" (e.g. "AudioReactive 1") - char base[33]; - strncpy_P(base, ump.name, sizeof(base) - 1); - base[sizeof(base) - 1] = '\0'; snprintf(dest, maxLen + 1, "%s %u", base, (unsigned)ump.palIndex); } return strlen(dest); From 8aa55015719da3df8fe27bab5a03b3d86fa199dc Mon Sep 17 00:00:00 2001 From: Will Tatam Date: Sun, 3 May 2026 14:27:08 +0100 Subject: [PATCH 10/10] Fix AudioReactive palette names to avoid duplication with usermod prefix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since the display format is now "UMName: palName", shorten the palette names to just "Ratio", "Hue", and "Spectrum" — rendering as "AudioReactive: Ratio" etc. rather than "AudioReactive: Audio Responsive Ratio". --- usermods/audioreactive/audio_reactive.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/usermods/audioreactive/audio_reactive.cpp b/usermods/audioreactive/audio_reactive.cpp index 0b3c93fdb3..afcaac877c 100644 --- a/usermods/audioreactive/audio_reactive.cpp +++ b/usermods/audioreactive/audio_reactive.cpp @@ -2280,9 +2280,9 @@ const char AudioReactive::_analogmic[] PROGMEM = "analogmic"; #endif const char AudioReactive::_digitalmic[] PROGMEM = "digitalmic"; const char AudioReactive::_addPalettes[] PROGMEM = "add-palettes"; -const char AudioReactive::_palName0[] PROGMEM = "Audio Responsive Ratio"; -const char AudioReactive::_palName1[] PROGMEM = "Audio Responsive Hue"; -const char AudioReactive::_palName2[] PROGMEM = "Audio Responsive Spectrum"; +const char AudioReactive::_palName0[] PROGMEM = "Ratio"; +const char AudioReactive::_palName1[] PROGMEM = "Hue"; +const char AudioReactive::_palName2[] PROGMEM = "Spectrum"; const char AudioReactive::UDP_SYNC_HEADER[] PROGMEM = "00002"; // new sync header version, as format no longer compatible with previous structure const char AudioReactive::UDP_SYNC_HEADER_v1[] PROGMEM = "00001"; // old sync header version - need to add backwards-compatibility feature