Skip to content
Merged
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
46 changes: 23 additions & 23 deletions usermods/audioreactive/audio_reactive.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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[];

Expand Down Expand Up @@ -1951,14 +1954,10 @@ 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()<WLED_MAX_CUSTOM_PALETTES) {
if (initDone && enabled && addPalettes && palettes==0) {
// if palettes were removed during JSON call re-add them
createAudioPalettes();
}
Comment on lines +1960 to 1963

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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Partial palette creation can get stuck and never backfill.

When WLED_MAX_USERMOD_PALETTES is temporarily near full, this can create only some AR palettes, set palettes > 0, and then permanently skip adding the missing ones (if (palettes) return; + palettes == 0 gate).

Suggested fix
 void AudioReactive::createAudioPalettes(void) {
-  if (palettes) return;
+  bool present[MAX_PALETTES] = {false};
+  palettes = 0;
+  for (const auto &ump : usermodPalettes) {
+    if (ump.name != _name || ump.palIndex >= MAX_PALETTES) continue;
+    if (!present[ump.palIndex]) {
+      present[ump.palIndex] = true;
+      palettes++;
+    }
+  }
+  if (palettes >= MAX_PALETTES) return;
   DEBUG_PRINTLN(F("Adding audio palettes."));
   static const char *const palNames[MAX_PALETTES] PROGMEM = {_palName0, _palName1, _palName2};
-  for (int i=0; i<MAX_PALETTES; i++) {
+  for (uint8_t i = 0; i < MAX_PALETTES; i++) {
+    if (present[i]) continue;
     if (usermodPalettes.size() < WLED_MAX_USERMOD_PALETTES) {
       usermodPalettes.push_back({CRGBPalette16(CRGB(BLACK)), _name, (uint8_t)i, palNames[i]}); // start black, filled each loop by fillAudioPalettes()
       palettes++;
       DEBUG_PRINTLN(palettes);
     } else break;
   }
 }

 void onStateChange(uint8_t callMode) override {
-  if (initDone && enabled && addPalettes && palettes==0) {
+  if (initDone && enabled && addPalettes && palettes < MAX_PALETTES) {
     // if palettes were removed during JSON call re-add them
     createAudioPalettes();
   }
 }

Also applies to: 2194-2203

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@usermods/audioreactive/audio_reactive.cpp` around lines 1960 - 1963, The
current guard only calls createAudioPalettes() when palettes == 0 which leaves
partially-created palettes stuck; change the condition to detect incomplete
palettes and retry creation (e.g., use palettes < WLED_MAX_USERMOD_PALETTES or a
similar “not full” check) so createAudioPalettes() runs whenever addPalettes is
true and the palette count is below the configured maximum; apply the same fix
to the other similar check around createAudioPalettes() later in the file so
both spots use palettes < WLED_MAX_USERMOD_PALETTES (or equivalent) instead of
palettes == 0.

Expand Down Expand Up @@ -2187,24 +2186,21 @@ class AudioReactive : public Usermod {

void AudioReactive::removeAudioPalettes(void) {
DEBUG_PRINTLN(F("Removing audio palettes."));
while (palettes>0) {
customPalettes.pop_back();
DEBUG_PRINTLN(palettes);
palettes--;
}
DEBUG_PRINT(F("Total # of palettes: ")); DEBUG_PRINTLN(customPalettes.size());
palettes -= (int8_t)removeUsermodPalettes(_name);
if (palettes < 0) palettes = 0; // safeguard
}

void AudioReactive::createAudioPalettes(void) {
DEBUG_PRINT(F("Total # of palettes: ")); DEBUG_PRINTLN(customPalettes.size());
if (palettes) return;
DEBUG_PRINTLN(F("Adding audio palettes."));
for (int i=0; i<MAX_PALETTES; i++)
if (customPalettes.size() < WLED_MAX_CUSTOM_PALETTES) {
customPalettes.push_back(CRGBPalette16(CRGB(BLACK)));
static const char *const palNames[MAX_PALETTES] PROGMEM = {_palName0, _palName1, _palName2};
for (int i=0; i<MAX_PALETTES; i++) {
if (usermodPalettes.size() < WLED_MAX_USERMOD_PALETTES) {
usermodPalettes.push_back({CRGBPalette16(CRGB(BLACK)), _name, (uint8_t)i, palNames[i]}); // start black, filled each loop by fillAudioPalettes()
palettes++;
DEBUG_PRINTLN(palettes);
} else break;
}
}

// credit @netmindz ar palette, adapted for usermod @blazoncek
Expand Down Expand Up @@ -2238,36 +2234,37 @@ CRGB AudioReactive::getCRGBForBand(int x, int pal) {

void AudioReactive::fillAudioPalettes() {
if (!palettes) return;
size_t lastCustPalette = customPalettes.size();
if (int(lastCustPalette) >= palettes) lastCustPalette -= palettes;
for (int pal=0; pal<palettes; pal++) {
// Scan by name pointer identity to find the palettes we added, palIndex = 0/1/2... selects the getCRGBForBand variant, matching how the entries were created.
for (auto &ump : usermodPalettes) {
if (ump.name != _name) continue;
const int pal = ump.palIndex;
uint8_t tcp[16]; // Needs to be 4 times however many colors are being used.
// 3 colors = 12, 4 colors = 16, etc.

tcp[0] = 0; // anchor of first color - must be zero
tcp[1] = 0;
tcp[2] = 0;
tcp[3] = 0;

CRGB rgb = getCRGBForBand(1, pal);
tcp[4] = 1; // anchor of first color
tcp[5] = rgb.r;
tcp[6] = rgb.g;
tcp[7] = rgb.b;

rgb = getCRGBForBand(128, pal);
tcp[8] = 128;
tcp[9] = rgb.r;
tcp[10] = rgb.g;
tcp[11] = rgb.b;

rgb = getCRGBForBand(255, pal);
tcp[12] = 255; // anchor of last color - must be 255
tcp[13] = rgb.r;
tcp[14] = rgb.g;
tcp[15] = rgb.b;

customPalettes[lastCustPalette+pal].loadDynamicGradientPalette(tcp);
ump.palette.loadDynamicGradientPalette(tcp);
}
}

Expand All @@ -2282,7 +2279,10 @@ const char AudioReactive::_inputLvl[] PROGMEM = "inputLevel";
const char AudioReactive::_analogmic[] PROGMEM = "analogmic";
#endif
const char AudioReactive::_digitalmic[] PROGMEM = "digitalmic";
const char AudioReactive::_addPalettes[] PROGMEM = "add-palettes";
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::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

Expand Down
29 changes: 22 additions & 7 deletions wled00/FX_fcn.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -228,12 +228,19 @@ void Segment::resetIfRequired() {
void Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) {
// there is one randomly generated palette (1) followed by 4 palettes created from segment colors (2-5)
// those are followed by 7 fastled palettes (6-12) and 59 gradient palettes (13-71)
// then come the custom palettes (255,254,...) growing downwards from 255 (255 being 1st custom palette)
// 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 >= FIXED_PALETTE_COUNT && pal <= 255-customPalettes.size()) pal = 0; // out of bounds palette
//default palette. Differs depending on effect
if (pal == 0) pal = _default_palette; // _default_palette is set in setMode()
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) {
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;
}
}
Comment thread
DedeHai marked this conversation as resolved.
switch (pal) {
case 0: //default palette. Exceptions for specific effects above
targetPalette = PartyColors_gc22;
Expand Down Expand Up @@ -267,8 +274,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 = 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
targetPalette = *fastledPalettes[pal - DYNAMIC_PALETTE_COUNT];
} else {
Expand Down Expand Up @@ -585,7 +594,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)usermodPalettes.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)
Expand Down
9 changes: 9 additions & 0 deletions wled00/colors.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,15 @@ void loadCustomPalettes() {
}
}

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 - usermodPalettes.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;
Expand Down
13 changes: 12 additions & 1 deletion wled00/colors.h
Original file line number Diff line number Diff line change
Expand Up @@ -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).
// 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; if set, shown as "name: palName" (e.g. "AudioReactive: Audio Responsive Hue"), otherwise falls back to "name index"
};

void loadCustomPalettes();
size_t removeUsermodPalettes(const char *name); // remove all entries from usermodPalettes whose name pointer matches 'name'
extern std::vector<CRGBPalette16> customPalettes;
inline size_t getPaletteCount() { return FIXED_PALETTE_COUNT + customPalettes.size(); }
extern std::vector<UsermodPalette> usermodPalettes;
inline size_t getPaletteCount() { return FIXED_PALETTE_COUNT + usermodPalettes.size() + customPalettes.size(); }
Comment thread
coderabbitai[bot] marked this conversation as resolved.

void hsv2rgb_spectrum(const CHSV32& hsv, CRGBW& rgb);
void hsv2rgb_spectrum(const CHSV& hsv, CRGB& rgb);
Expand Down
10 changes: 9 additions & 1 deletion wled00/const.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 5 additions & 1 deletion wled00/data/cpal/cpal.htm
Original file line number Diff line number Diff line change
Expand Up @@ -460,7 +460,11 @@
rm.className = 'sml';
rm.title = 'Delete palette';
rm.innerHTML = '&#10006;';
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);
Expand Down
33 changes: 25 additions & 8 deletions wled00/data/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -989,23 +989,39 @@ 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<li.cpalcount; j++) {
const pd = palettesData[255-j];
if (pd && pd.length === 16 && pd.every(e => 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',
`<div class="lstIprev" style="${genPalPrevCss(255-j)}"></div>`
);
}
}
// 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',
`<div class="lstIprev" style="${genPalPrevCss(id)}"></div>`
);
}
}
Comment thread
DedeHai marked this conversation as resolved.
updateSelectedPalette(selectedPal); // update selection after adding usermod and custom palettes
}

function redrawPalPrev()
Expand Down Expand Up @@ -2819,7 +2835,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();
Expand All @@ -2831,7 +2847,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
Expand Down
37 changes: 30 additions & 7 deletions wled00/json.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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")] = 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 (usermodPalettes.size() > 0) {
JsonArray umpalnames = root.createNestedArray(F("umpalnames"));
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);
}
}

JsonArray ledmaps = root.createNestedArray(F("maps"));
for (size_t i=0; i<WLED_MAX_LEDMAPS; i++) {
Expand Down Expand Up @@ -946,19 +956,28 @@ void serializePalettes(JsonObject root, int page)
#endif

const int customPalettesCount = customPalettes.size();
const int umPalettesCount = usermodPalettes.size();
const int palettesCount = FIXED_PALETTE_COUNT; // palettesCount is number of palettes, not palette index

const int maxPage = (palettesCount + customPalettesCount) / itemPerPage;
const int maxPage = (palettesCount + umPalettesCount + customPalettesCount) / itemPerPage;
if (page > 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);
Expand Down Expand Up @@ -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, 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 {
memcpy_P(tcp, (byte*)pgm_read_dword(&(gGradientPalettes[i - (DYNAMIC_PALETTE_COUNT + FASTLED_PALETTE_COUNT)])), sizeof(tcp));
Expand Down
Loading
Loading