GCC Code Coverage Report


Directory: ./
File: overlays/wled/util.cpp
Date: 2026-10-29 08:11:58
Exec Total Coverage
Lines: 159 298 53.4%
Functions: 20 29 69.0%
Branches: 19 209 9.1%

Line Branch Exec Source
1 #include "wled.h"
2 #include "fcn_declare.h"
3 #include "const.h"
4
5
6 //helper to get int value at a position in string
7 ✗ int getNumVal(const String* req, uint16_t pos)
8 {
9 ✗ return std::stoi(req->substr(pos+3));
10 }
11
12
13 //helper to get int value with in/decrementing support via ~ syntax
14 ✗ void parseNumber(const char* str, byte* val, byte minv, byte maxv)
15 {
16 ✗ if (str == nullptr || str[0] == '\0') return;
17 ✗ if (str[0] == 'r') {*val = hw_random8(minv,maxv?maxv:255); return;} // maxv for random cannot be 0
18 ✗ bool wrap = false;
19 ✗ if (str[0] == 'w' && strlen(str) > 1) {str++; wrap = true;}
20 ✗ if (str[0] == '~') {
21 ✗ int out = atoi(str +1);
22 ✗ if (out == 0) {
23 ✗ if (str[1] == '0') return;
24 ✗ if (str[1] == '-') {
25 ✗ *val = (int)(*val -1) < (int)minv ? maxv : min((int)maxv,(*val -1)); //-1, wrap around
26 } else {
27 ✗ *val = (int)(*val +1) > (int)maxv ? minv : max((int)minv,(*val +1)); //+1, wrap around
28 }
29 } else {
30 ✗ if (wrap && *val == maxv && out > 0) out = minv;
31 ✗ else if (wrap && *val == minv && out < 0) out = maxv;
32 else {
33 ✗ out += *val;
34 ✗ if (out > maxv) out = maxv;
35 ✗ if (out < minv) out = minv;
36 }
37 ✗ *val = out;
38 }
39 ✗ return;
40 ✗ } else if (minv == maxv && minv == 0) { // limits "unset" i.e. both 0
41 ✗ byte p1 = atoi(str);
42 ✗ const char* str2 = strchr(str,'~'); // min/max range (for preset cycle, e.g. "1~5~")
43 ✗ if (str2) {
44 ✗ byte p2 = atoi(++str2); // skip ~
45 ✗ if (p2 > 0) {
46 ✗ while (isdigit(*(++str2))); // skip digits
47 ✗ parseNumber(str2, val, p1, p2);
48 ✗ return;
49 }
50 }
51 }
52 ✗ *val = atoi(str);
53 }
54 #ifndef WLED_DISABLE_FPP
55 //getVal supports inc/decrementing and random ("X~Y(r|~[w][-][Z])" form)
56 bool getVal(JsonVariant elem, byte* val, byte vmin, byte vmax) {
57 if (elem.is<int>()) {
58 if (elem < 0) return false; //ignore e.g. {"ps":-1}
59 *val = elem;
60 return true;
61 } else if (elem.is<const char*>()) {
62 const char* str = elem;
63 size_t len = strnlen(str, 14);
64 if (len == 0 || len > 12) return false;
65 // fix for #3605 & #4346
66 // ignore vmin and vmax and use as specified in API
67 if (len > 3 && (strchr(str,'r') || strchr(str,'~') != strrchr(str,'~'))) vmax = vmin = 0; // we have "X~Y(r|~[w][-][Z])" form
68 // end fix
69 parseNumber(str, val, vmin, vmax);
70 return true;
71 }
72 return false; //key does not exist
73 }
74
75
76 bool getBoolVal(const JsonVariant &elem, bool dflt) {
77 if (elem.is<const char*>() && elem.as<const char*>()[0] == 't') {
78 return !dflt;
79 } else {
80 return elem | dflt;
81 }
82 }
83
84 bool updateVal(const char* req, const char* key, byte* val, byte minv, byte maxv)
85 {
86 const char *v = strstr(req, key);
87 if (v) v += strlen(key);
88 else return false;
89 parseNumber(v, val, minv, maxv);
90 return true;
91 }
92
93 static size_t printSetFormInput(Print& settingsScript, const char* key, const char* selector, int value) {
94 return settingsScript.printf_P(PSTR("d.Sf.%s.%s=%d;"), key, selector, value);
95 }
96
97 size_t printSetFormCheckbox(Print& settingsScript, const char* key, int val) {
98 return printSetFormInput(settingsScript, key, PSTR("checked"), val);
99 }
100 size_t printSetFormValue(Print& settingsScript, const char* key, int val) {
101 return printSetFormInput(settingsScript, key, PSTR("value"), val);
102 }
103 size_t printSetFormIndex(Print& settingsScript, const char* key, int index) {
104 return printSetFormInput(settingsScript, key, PSTR("selectedIndex"), index);
105 }
106
107 size_t printSetFormValue(Print& settingsScript, const char* key, const char* val) {
108 return settingsScript.printf_P(PSTR("d.Sf.%s.value=\"%s\";"),key,val);
109 }
110
111 size_t printSetClassElementHTML(Print& settingsScript, const char* key, const int index, const char* val) {
112 return settingsScript.printf_P(PSTR("d.getElementsByClassName(\"%s\")[%d].innerHTML=\"%s\";"), key, index, val);
113 }
114
115
116
117 void prepareHostname(char* hostname)
118 {
119 sprintf_P(hostname, PSTR("wled-%*s"), 6, escapedMac.c_str() + 6);
120 const char *pC = serverDescription;
121 unsigned pos = 5; // keep "wled-"
122 while (*pC && pos < 24) { // while !null and not over length
123 if (isalnum(*pC)) { // if the current char is alpha-numeric append it to the hostname
124 hostname[pos] = *pC;
125 pos++;
126 } else if (*pC == ' ' || *pC == '_' || *pC == '-' || *pC == '+' || *pC == '!' || *pC == '?' || *pC == '*') {
127 hostname[pos] = '-';
128 pos++;
129 }
130 // else do nothing - no leading hyphens and do not include hyphens for all other characters.
131 pC++;
132 }
133 //last character must not be hyphen
134 if (pos > 5) {
135 while (pos > 4 && hostname[pos -1] == '-') pos--;
136 hostname[pos] = '\0'; // terminate string (leave at least "wled")
137 }
138 }
139
140
141 bool isAsterisksOnly(const char* str, byte maxLen)
142 {
143 for (unsigned i = 0; i < maxLen; i++) {
144 if (str[i] == 0) break;
145 if (str[i] != '*') return false;
146 }
147 //at this point the password contains asterisks only
148 return (str[0] != 0); //false on empty string
149 }
150
151
152 //threading/network callback details: https://github.com/wled-dev/WLED/pull/2336#discussion_r762276994
153 bool requestJSONBufferLock(uint8_t moduleID)
154 {
155 if (pDoc == nullptr) {
156 DEBUG_PRINTLN(F("ERROR: JSON buffer not allocated!"));
157 return false;
158 }
159
160 #if defined(ARDUINO_ARCH_ESP32)
161 // Use a recursive mutex type in case our task is the one holding the JSON buffer.
162 // This can happen during large JSON web transactions. In this case, we continue immediately
163 // and then will return out below if the lock is still held.
164 if (xSemaphoreTakeRecursive(jsonBufferLockMutex, 250) == pdFALSE) return false; // timed out waiting
165 #elif defined(ARDUINO_ARCH_ESP8266)
166 // If we're in system context, delay() won't return control to the user context, so there's
167 // no point in waiting.
168 if (can_yield()) {
169 unsigned long now = millis();
170 while (jsonBufferLock && (millis()-now < 250)) delay(1); // wait for fraction for buffer lock
171 }
172 #else
173 #error Unsupported task framework - fix requestJSONBufferLock
174 #endif
175 // If the lock is still held - by us, or by another task
176 if (jsonBufferLock) {
177 DEBUG_PRINTF_P(PSTR("ERROR: Locking JSON buffer (%d) failed! (still locked by %d)\n"), moduleID, jsonBufferLock);
178 #ifdef ARDUINO_ARCH_ESP32
179 xSemaphoreGiveRecursive(jsonBufferLockMutex);
180 #endif
181 return false;
182 }
183
184 jsonBufferLock = moduleID ? moduleID : 255;
185 DEBUG_PRINTF_P(PSTR("JSON buffer locked. (%d)\n"), jsonBufferLock);
186 pDoc->clear();
187 return true;
188 }
189
190
191 void releaseJSONBufferLock()
192 {
193 DEBUG_PRINTF_P(PSTR("JSON buffer released. (%d)\n"), jsonBufferLock);
194 jsonBufferLock = 0;
195 #ifdef ARDUINO_ARCH_ESP32
196 xSemaphoreGiveRecursive(jsonBufferLockMutex);
197 #endif
198 }
199 #else
200 3542 void releaseJSONBufferLock() {}
201 #endif
202
203 // extracts effect mode (or palette) name from names serialized string
204 // caller must provide large enough buffer for name (including SR extensions)!
205 ✗ uint8_t extractModeName(uint8_t mode, const char *src, char *dest, uint8_t maxLen)
206 {
207 ✗ if (src == JSON_mode_names || src == nullptr) {
208 ✗ if (mode < strip().getModeCount()) {
209 char lineBuffer[256];
210 //strcpy_P(lineBuffer, (const char*)pgm_read_dword(&(WS2812FX::_modeData[mode])));
211 ✗ strncpy_P(lineBuffer, strip().getModeData(mode), sizeof(lineBuffer)/sizeof(char)-1);
212 ✗ lineBuffer[sizeof(lineBuffer)/sizeof(char)-1] = '\0'; // terminate string
213 ✗ size_t len = strlen(lineBuffer);
214 ✗ size_t j = 0;
215 ✗ for (; j < maxLen && j < len; j++) {
216 ✗ if (lineBuffer[j] == '\0' || lineBuffer[j] == '@') break;
217 ✗ dest[j] = lineBuffer[j];
218 }
219 ✗ dest[j] = 0; // terminate string
220 ✗ return strlen(dest);
221 ✗ } else return 0;
222 }
223
224 ✗ if (src == JSON_palette_names && mode > (GRADIENT_PALETTE_COUNT + 13)) {
225 ✗ snprintf_P(dest, maxLen, PSTR("~ Custom %d ~"), 255-mode);
226 ✗ dest[maxLen-1] = '\0';
227 ✗ return strlen(dest);
228 }
229
230 ✗ unsigned qComma = 0;
231 ✗ bool insideQuotes = false;
232 ✗ unsigned printedChars = 0;
233 char singleJsonSymbol;
234 ✗ size_t len = strlen_P(src);
235
236 // Find the mode name in JSON
237 ✗ for (size_t i = 0; i < len; i++) {
238 ✗ singleJsonSymbol = pgm_read_byte_near((uint8_t*)(src + i));
239 ✗ if (singleJsonSymbol == '\0') break;
240 ✗ if (singleJsonSymbol == '@' && insideQuotes && qComma == mode) break; //stop when SR extension encountered
241 ✗ switch (singleJsonSymbol) {
242 ✗ case '"':
243 ✗ insideQuotes = !insideQuotes;
244 ✗ break;
245 ✗ case '[':
246 case ']':
247 ✗ break;
248 ✗ case ',':
249 ✗ if (!insideQuotes) qComma++;
250 default:
251 ✗ if (!insideQuotes || (qComma != mode)) break;
252 ✗ dest[printedChars++] = singleJsonSymbol;
253 }
254 ✗ if ((qComma > mode) || (printedChars >= maxLen)) break;
255 }
256 ✗ dest[printedChars] = '\0';
257 ✗ return strlen(dest);
258 }
259
260
261 // extracts effect slider data (1st group after @)
262 ✗ uint8_t extractModeSlider(uint8_t mode, uint8_t slider, char *dest, uint8_t maxLen, uint8_t *var)
263 {
264 ✗ dest[0] = '\0'; // start by clearing buffer
265
266 ✗ if (mode < strip().getModeCount()) {
267 ✗ String lineBuffer = strip().getModeData(mode);
268 ✗ if (lineBuffer.length() > 0) {
269 ✗ int start = lineBuffer.find('@'); // String::find() returns an int, not an unsigned; -1 means "not found"
270 ✗ int stop = lineBuffer.find(';', start);
271 ✗ if (start>0 && stop>0) {
272 ✗ String names = lineBuffer.substr(start, stop); // include @
273 ✗ int nameBegin = 1, nameEnd, nameDefault;
274 ✗ if (slider < 10) {
275 ✗ for (size_t i=0; i<=slider; i++) {
276 const char *tmpstr;
277 ✗ dest[0] = '\0'; //clear dest buffer
278 ✗ if (nameBegin <= 0) break; // there are no more names
279 ✗ nameEnd = names.find(',', nameBegin);
280 ✗ if (i == slider) {
281 ✗ nameDefault = names.find('=', nameBegin); // find default value
282 ✗ if (nameDefault > 0 && var && ((nameEnd>0 && nameDefault<nameEnd) || nameEnd<0)) {
283 ✗ *var = (uint8_t)atoi(names.substr(nameDefault+1).c_str());
284 }
285 ✗ if (names[nameBegin] == '!') {
286 ✗ switch (slider) {
287 ✗ case 0: tmpstr = PSTR("FX Speed"); break;
288 ✗ case 1: tmpstr = PSTR("FX Intensity"); break;
289 ✗ case 2: tmpstr = PSTR("FX Custom 1"); break;
290 ✗ case 3: tmpstr = PSTR("FX Custom 2"); break;
291 ✗ case 4: tmpstr = PSTR("FX Custom 3"); break;
292 ✗ default: tmpstr = PSTR("FX Custom"); break;
293 }
294 ✗ strncpy_P(dest, tmpstr, maxLen); // copy the name into buffer (replacing previous)
295 ✗ dest[maxLen-1] = '\0';
296 } else {
297 ✗ if (nameEnd<0) tmpstr = names.substr(nameBegin).c_str(); // did not find ",", last name?
298 ✗ else tmpstr = names.substr(nameBegin, nameEnd).c_str();
299 ✗ strncpy(dest, tmpstr, maxLen); // copy the name into buffer (replacing previous)
300 }
301 }
302 ✗ nameBegin = nameEnd+1; // next name (if "," is not found it will be 0)
303 } // next slider
304 ✗ } else if (slider == 255) {
305 // palette
306 ✗ strncpy(dest, "pal", maxLen);
307 ✗ names = lineBuffer.substr(stop+1); // stop has index of color slot names
308 ✗ nameBegin = names.find(';'); // look for palette
309 ✗ if (nameBegin >= 0) {
310 ✗ nameEnd = names.find(';', nameBegin+1);
311 ✗ if (!isdigit(names[nameBegin+1])) nameBegin = names.find('=', nameBegin+1); // look for default value
312 ✗ if (nameEnd >= 0 && nameBegin > nameEnd) nameBegin = -1;
313 ✗ if (nameBegin >= 0 && var) {
314 ✗ *var = (uint8_t)atoi(names.substr(nameBegin+1).c_str());
315 }
316 }
317 }
318 // we have slider name (including default value) in the dest buffer
319 ✗ for (size_t i=0; i<strlen(dest); i++) if (dest[i]=='=') { dest[i]='\0'; break; } // truncate default value
320
321 ✗ } else {
322 // defaults to just speed and intensity since there is no slider data
323 ✗ switch (slider) {
324 ✗ case 0: strncpy_P(dest, PSTR("FX Speed"), maxLen); break;
325 ✗ case 1: strncpy_P(dest, PSTR("FX Intensity"), maxLen); break;
326 }
327 ✗ dest[maxLen] = '\0'; // strncpy does not necessarily null terminate string
328 }
329 }
330 ✗ return strlen(dest);
331 ✗ }
332 ✗ return 0;
333 }
334
335
336 // extracts mode parameter defaults from last section of mode data (e.g. "Juggle@!,Trail;!,!,;!;012;sx=16,ix=240")
337 2109422 int16_t extractModeDefaults(uint8_t mode, const char *segVar)
338 {
339
1/2
✓ Branch 0 taken 2109422 times.
✗ Branch 1 not taken.
2109422 if (mode < strip().getModeCount()) {
340 char lineBuffer[256];
341
1/2
✓ Branch 0 taken 2109422 times.
✗ Branch 1 not taken.
2109422 strncpy_P(lineBuffer, strip().getModeData(mode), sizeof(lineBuffer)/sizeof(char)-1);
342 2109422 lineBuffer[sizeof(lineBuffer)/sizeof(char)-1] = '\0'; // terminate string
343
1/2
✓ Branch 0 taken 2109422 times.
✗ Branch 1 not taken.
2109422 if (lineBuffer[0] != 0) {
344 2109422 char* startPtr = strrchr(lineBuffer, ';'); // last ";" in FX data
345
2/2
✓ Branch 0 taken 80 times.
✓ Branch 1 taken 2109342 times.
4218764 if (!startPtr) return -1;
346
347 2109342 char* stopPtr = strstr(startPtr, segVar);
348
2/2
✓ Branch 0 taken 1492197 times.
✓ Branch 1 taken 617145 times.
2109342 if (!stopPtr) return -1;
349
350 617145 stopPtr += strlen(segVar) +1; // skip "="
351 617145 return atoi(stopPtr);
352 }
353 }
354 ✗ return -1;
355 }
356
357
358 ✗ void checkSettingsPIN(const char* pin) {
359 ✗ if (!pin) return;
360 ✗ if (!correctPIN && millis() - lastEditTime < PIN_RETRY_COOLDOWN) return; // guard against PIN brute force
361 ✗ bool correctBefore = correctPIN;
362 ✗ correctPIN = (strlen(settingsPIN) == 0 || strncmp(settingsPIN, pin, 4) == 0);
363 //if (correctBefore != correctPIN) createEditHandler(correctPIN);
364 ✗ lastEditTime = millis();
365 }
366
367
368 5 uint16_t crc16(const unsigned char* data_p, size_t length) {
369 uint8_t x;
370 5 uint16_t crc = 0xFFFF;
371
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 if (!length) return 0x1D0F;
372
2/2
✓ Branch 0 taken 6207 times.
✓ Branch 1 taken 5 times.
6212 while (length--) {
373 6207 x = crc >> 8 ^ *data_p++;
374 6207 x ^= x>>4;
375 6207 crc = (crc << 8) ^ ((uint16_t)(x << 12)) ^ ((uint16_t)(x <<5)) ^ ((uint16_t)x);
376 }
377 5 return crc;
378 }
379
380 // fastled beatsin: 1:1 replacements to remove the use of fastled sin16()
381 // Generates a 16-bit sine wave at a given BPM that oscillates within a given range. see fastled for details.
382 529 uint16_t beatsin88_t(accum88 beats_per_minute_88, uint16_t lowest, uint16_t highest, uint32_t timebase, uint16_t phase_offset)
383 {
384 529 uint16_t beat = beat88( beats_per_minute_88, timebase);
385 529 uint16_t beatsin (sin16_t( beat + phase_offset) + 32768);
386 529 uint16_t rangewidth = highest - lowest;
387 529 uint16_t scaledbeat = scale16( beatsin, rangewidth);
388 529 uint16_t result = lowest + scaledbeat;
389 529 return result;
390 }
391
392 // Generates a 16-bit sine wave at a given BPM that oscillates within a given range. see fastled for details.
393 22045 uint16_t beatsin16_t(accum88 beats_per_minute, uint16_t lowest, uint16_t highest, uint32_t timebase, uint16_t phase_offset)
394 {
395 22045 uint16_t beat = beat16( beats_per_minute, timebase);
396 22045 uint16_t beatsin = (sin16_t( beat + phase_offset) + 32768);
397 22045 uint16_t rangewidth = highest - lowest;
398 22045 uint16_t scaledbeat = scale16( beatsin, rangewidth);
399 22045 uint16_t result = lowest + scaledbeat;
400 22045 return result;
401 }
402
403 // Generates an 8-bit sine wave at a given BPM that oscillates within a given range. see fastled for details.
404 74197 uint8_t beatsin8_t(accum88 beats_per_minute, uint8_t lowest, uint8_t highest, uint32_t timebase, uint8_t phase_offset)
405 {
406 74197 uint8_t beat = beat8( beats_per_minute, timebase);
407 74197 uint8_t beatsin = sin8_t( beat + phase_offset);
408 74197 uint8_t rangewidth = highest - lowest;
409 74197 uint8_t scaledbeat = scale8( beatsin, rangewidth);
410 74197 uint8_t result = lowest + scaledbeat;
411 74197 return result;
412 }
413
414 #ifndef WLED_DISABLE_FPP
415 ///////////////////////////////////////////////////////////////////////////////
416 // Begin simulateSound (to enable audio enhanced effects to display something)
417 ///////////////////////////////////////////////////////////////////////////////
418 // Currently 4 types defined, to be fine tuned and new types added
419 // (only 2 used as stored in 1 bit in segment options, consider switching to a single global simulation type)
420 typedef enum UM_SoundSimulations {
421 UMS_BeatSin = 0,
422 UMS_WeWillRockYou,
423 UMS_10_13,
424 UMS_14_3
425 } um_soundSimulations_t;
426
427 um_data_t* simulateSound(uint8_t simulationId)
428 {
429 static uint8_t samplePeak;
430 static float FFT_MajorPeak;
431 static uint8_t maxVol;
432 static uint8_t binNum;
433
434 static float volumeSmth;
435 static uint16_t volumeRaw;
436 static float my_magnitude;
437
438 //arrays
439 uint8_t *fftResult;
440
441 static um_data_t* um_data = nullptr;
442
443 if (!um_data) {
444 //claim storage for arrays
445 fftResult = (uint8_t *)malloc(sizeof(uint8_t) * 16);
446
447 // initialize um_data pointer structure
448 // NOTE!!!
449 // This may change as AudioReactive usermod may change
450 um_data = new um_data_t;
451 um_data->u_size = 8;
452 um_data->u_type = new um_types_t[um_data->u_size];
453 um_data->u_data = new void*[um_data->u_size];
454 um_data->u_data[0] = &volumeSmth;
455 um_data->u_data[1] = &volumeRaw;
456 um_data->u_data[2] = fftResult;
457 um_data->u_data[3] = &samplePeak;
458 um_data->u_data[4] = &FFT_MajorPeak;
459 um_data->u_data[5] = &my_magnitude;
460 um_data->u_data[6] = &maxVol;
461 um_data->u_data[7] = &binNum;
462 } else {
463 // get arrays from um_data
464 fftResult = (uint8_t*)um_data->u_data[2];
465 }
466
467 uint32_t ms = millis();
468
469 switch (simulationId) {
470 default:
471 case UMS_BeatSin:
472 for (int i = 0; i<16; i++)
473 fftResult[i] = beatsin8_t(120 / (i+1), 0, 255);
474 // fftResult[i] = (beatsin8_t(120, 0, 255) + (256/16 * i)) % 256;
475 volumeSmth = fftResult[8];
476 break;
477 case UMS_WeWillRockYou:
478 if (ms%2000 < 200) {
479 volumeSmth = hw_random8();
480 for (int i = 0; i<5; i++)
481 fftResult[i] = hw_random8();
482 }
483 else if (ms%2000 < 400) {
484 volumeSmth = 0;
485 for (int i = 0; i<16; i++)
486 fftResult[i] = 0;
487 }
488 else if (ms%2000 < 600) {
489 volumeSmth = hw_random8();
490 for (int i = 5; i<11; i++)
491 fftResult[i] = hw_random8();
492 }
493 else if (ms%2000 < 800) {
494 volumeSmth = 0;
495 for (int i = 0; i<16; i++)
496 fftResult[i] = 0;
497 }
498 else if (ms%2000 < 1000) {
499 volumeSmth = hw_random8();
500 for (int i = 11; i<16; i++)
501 fftResult[i] = hw_random8();
502 }
503 else {
504 volumeSmth = 0;
505 for (int i = 0; i<16; i++)
506 fftResult[i] = 0;
507 }
508 break;
509 case UMS_10_13:
510 for (int i = 0; i<16; i++)
511 fftResult[i] = perlin8(beatsin8_t(90 / (i+1), 0, 200)*15 + (ms>>10), ms>>3);
512 volumeSmth = fftResult[8];
513 break;
514 case UMS_14_3:
515 for (int i = 0; i<16; i++)
516 fftResult[i] = perlin8(beatsin8_t(120 / (i+1), 10, 30)*10 + (ms>>14), ms>>3);
517 volumeSmth = fftResult[8];
518 break;
519 }
520
521 samplePeak = hw_random8() > 250;
522 FFT_MajorPeak = 21 + (volumeSmth*volumeSmth) / 8.0f; // walk thru full range of 21hz...8200hz
523 maxVol = 31; // this gets feedback fro UI
524 binNum = 8; // this gets feedback fro UI
525 volumeRaw = volumeSmth;
526 my_magnitude = 10000.0f / 8.0f; //no idea if 10000 is a good value for FFT_Magnitude ???
527 if (volumeSmth < 1 ) my_magnitude = 0.001f; // noise gate closed - mute
528
529 return um_data;
530 }
531 #endif
532
533 static const char s_ledmap_tmpl[] PROGMEM = "ledmap%d.json";
534 // enumerate all ledmapX.json files on FS and extract ledmap names if existing
535 3542 void enumerateLedmaps() {
536 #ifndef WLED_DISABLE_FPP
537
538 StaticJsonDocument<64> filter;
539 filter["n"] = true;
540 ledMaps = 1;
541 for (size_t i=1; i<WLED_MAX_LEDMAPS; i++) {
542 char fileName[33] = "/";
543 sprintf_P(fileName+1, s_ledmap_tmpl, i);
544 bool isFile = WLED_FS.exists(fileName);
545
546 #ifndef ESP8266
547 if (ledmapNames[i-1]) { //clear old name
548 free(ledmapNames[i-1]);
549 ledmapNames[i-1] = nullptr;
550 }
551 #endif
552
553 if (isFile) {
554 ledMaps |= 1 << i;
555
556 #ifndef ESP8266
557 if (requestJSONBufferLock(21)) {
558 if (readObjectFromFile(fileName, nullptr, pDoc, &filter)) {
559 size_t len = 0;
560 JsonObject root = pDoc->as<JsonObject>();
561 if (!root["n"].isNull()) {
562 // name field exists
563 const char *name = root["n"].as<const char*>();
564 if (name != nullptr) len = strlen(name);
565 if (len > 0 && len < 33) {
566 ledmapNames[i-1] = static_cast<char*>(malloc(len+1));
567 if (ledmapNames[i-1]) strlcpy(ledmapNames[i-1], name, 33);
568 }
569 }
570 if (!ledmapNames[i-1]) {
571 char tmp[33];
572 snprintf_P(tmp, 32, s_ledmap_tmpl, i);
573 len = strlen(tmp);
574 ledmapNames[i-1] = static_cast<char*>(malloc(len+1));
575 if (ledmapNames[i-1]) strlcpy(ledmapNames[i-1], tmp, 33);
576 }
577 }
578 releaseJSONBufferLock();
579 }
580 #endif
581 }
582
583 }
584 #endif
585 3542 }
586
587 /*
588 * Returns a new, random color wheel index with a minimum distance of 42 from pos.
589 */
590 25 uint8_t get_random_wheel_index(uint8_t pos) {
591 25 uint8_t r = 0, x = 0, y = 0, d = 0;
592
2/2
✓ Branch 0 taken 36 times.
✓ Branch 1 taken 25 times.
61 while (d < 42) {
593 36 r = hw_random8();
594 36 x = abs(pos - r);
595 36 y = 255 - x;
596
2/2
✓ Branch 0 taken 30 times.
✓ Branch 1 taken 6 times.
36 d = MIN(x, y);
597 }
598 25 return r;
599 }
600
601 // float version of map()
602 174 float mapf(float x, float in_min, float in_max, float out_min, float out_max) {
603 174 return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
604 }
605
606 ✗ uint32_t hashInt(uint32_t s) {
607 // borrowed from https://stackoverflow.com/questions/664014/what-integer-hash-function-are-good-that-accepts-an-integer-hash-key
608 ✗ s = ((s >> 16) ^ s) * 0x45d9f3b;
609 ✗ s = ((s >> 16) ^ s) * 0x45d9f3b;
610 ✗ return (s >> 16) ^ s;
611 }
612
613 // 32 bit random number generator, inlining uses more code, use hw_random16() if speed is critical (see fcn_declare.h)
614 1151 uint32_t hw_random(uint32_t upperlimit) {
615 1151 uint32_t rnd = hw_random();
616 1151 uint64_t scaled = uint64_t(rnd) * uint64_t(upperlimit);
617 1151 return scaled >> 32;
618 }
619
620 ✗ int32_t hw_random(int32_t lowerlimit, int32_t upperlimit) {
621 ✗ if(lowerlimit >= upperlimit) {
622 ✗ return lowerlimit;
623 }
624 ✗ uint32_t diff = upperlimit - lowerlimit;
625 ✗ return hw_random(diff) + lowerlimit;
626 }
627
628 /*
629 * Fixed point integer based Perlin noise functions by @dedehai
630 * Note: optimized for speed and to mimic fastled inoise functions, not for accuracy or best randomness
631 */
632 #define PERLIN_SHIFT 1
633
634 // calculate gradient for corner from hash value
635 static inline __attribute__((always_inline)) int32_t hashToGradient(uint32_t h) {
636 // using more steps yields more "detailed" perlin noise but looks less like the original fastled version (adjust PERLIN_SHIFT to compensate, also changes range and needs proper adustment)
637 // return (h & 0xFF) - 128; // use PERLIN_SHIFT 7
638 // return (h & 0x0F) - 8; // use PERLIN_SHIFT 3
639 // return (h & 0x07) - 4; // use PERLIN_SHIFT 2
640 2160926 return (h & 0x03) - 2; // use PERLIN_SHIFT 1 -> closest to original fastled version
641 }
642
643 // Gradient functions for 1D, 2D and 3D Perlin noise note: forcing inline produces smaller code and makes it 3x faster!
644 static inline __attribute__((always_inline)) int32_t gradient1D(uint32_t x0, int32_t dx) {
645 15062 uint32_t h = x0 * 0x27D4EB2D;
646 15062 h ^= h >> 15;
647 15062 h *= 0x92C3412B;
648 15062 h ^= h >> 13;
649 15062 h ^= h >> 7;
650 15062 return (hashToGradient(h) * dx) >> PERLIN_SHIFT;
651 }
652
653 static inline __attribute__((always_inline)) int32_t gradient2D(uint32_t x0, int32_t dx, uint32_t y0, int32_t dy) {
654 243540 uint32_t h = (x0 * 0x27D4EB2D) ^ (y0 * 0xB5297A4D);
655 243540 h ^= h >> 15;
656 243540 h *= 0x92C3412B;
657 243540 h ^= h >> 13;
658 487080 return (hashToGradient(h) * dx + hashToGradient(h>>PERLIN_SHIFT) * dy) >> (1 + PERLIN_SHIFT);
659 }
660
661 static inline __attribute__((always_inline)) int32_t gradient3D(uint32_t x0, int32_t dx, uint32_t y0, int32_t dy, uint32_t z0, int32_t dz) {
662 // fast and good entropy hash from corner coordinates
663 552928 uint32_t h = (x0 * 0x27D4EB2D) ^ (y0 * 0xB5297A4D) ^ (z0 * 0x1B56C4E9);
664 552928 h ^= h >> 15;
665 552928 h *= 0x92C3412B;
666 552928 h ^= h >> 13;
667 1658784 return ((hashToGradient(h) * dx + hashToGradient(h>>(1+PERLIN_SHIFT)) * dy + hashToGradient(h>>(1 + 2*PERLIN_SHIFT)) * dz) * 85) >> (8 + PERLIN_SHIFT); // scale to 16bit, x*85 >> 8 = x/3
668 }
669
670 // fast cubic smoothstep: t*(3 - 2t²), optimized for fixed point, scaled to avoid overflows
671 336649 static uint32_t smoothstep(const uint32_t t) {
672 336649 uint32_t t_squared = (t * t) >> 16;
673 336649 uint32_t factor = (3 << 16) - ((t << 1));
674 336649 return (t_squared * factor) >> 18; // scale to avoid overflows and give best resolution
675 }
676
677 // simple linear interpolation for fixed-point values, scaled for perlin noise use
678 673998 static inline int32_t lerpPerlin(int32_t a, int32_t b, int32_t t) {
679 673998 return a + (((b - a) * t) >> 14); // match scaling with smoothstep to yield 16.16bit values
680 }
681
682 // 1D Perlin noise function that returns a value in range of -24691 to 24689
683 7531 int32_t perlin1D_raw(uint32_t x, bool is16bit) {
684 // integer and fractional part coordinates
685 7531 int32_t x0 = x >> 16;
686 7531 int32_t x1 = x0 + 1;
687
1/2
✓ Branch 0 taken 7531 times.
✗ Branch 1 not taken.
7531 if(is16bit) x1 = x1 & 0xFF; // wrap back to zero at 0xFF instead of 0xFFFF
688
689 7531 int32_t dx0 = x & 0xFFFF;
690 7531 int32_t dx1 = dx0 - 0x10000;
691 // gradient values for the two corners
692 7531 int32_t g0 = gradient1D(x0, dx0);
693 7531 int32_t g1 = gradient1D(x1, dx1);
694 // interpolate and smooth function
695 7531 int32_t tx = smoothstep(dx0);
696 7531 int32_t noise = lerpPerlin(g0, g1, tx);
697 7531 return noise;
698 }
699
700 // 2D Perlin noise function that returns a value in range of -20633 to 20629
701 60885 int32_t perlin2D_raw(uint32_t x, uint32_t y, bool is16bit) {
702 60885 int32_t x0 = x >> 16;
703 60885 int32_t y0 = y >> 16;
704 60885 int32_t x1 = x0 + 1;
705 60885 int32_t y1 = y0 + 1;
706
707
2/2
✓ Branch 0 taken 25164 times.
✓ Branch 1 taken 35721 times.
60885 if(is16bit) {
708 25164 x1 = x1 & 0xFF; // wrap back to zero at 0xFF instead of 0xFFFF
709 25164 y1 = y1 & 0xFF;
710 }
711
712 60885 int32_t dx0 = x & 0xFFFF;
713 60885 int32_t dy0 = y & 0xFFFF;
714 60885 int32_t dx1 = dx0 - 0x10000;
715 60885 int32_t dy1 = dy0 - 0x10000;
716
717 60885 int32_t g00 = gradient2D(x0, dx0, y0, dy0);
718 60885 int32_t g10 = gradient2D(x1, dx1, y0, dy0);
719 60885 int32_t g01 = gradient2D(x0, dx0, y1, dy1);
720 60885 int32_t g11 = gradient2D(x1, dx1, y1, dy1);
721
722 60885 uint32_t tx = smoothstep(dx0);
723 60885 uint32_t ty = smoothstep(dy0);
724
725 60885 int32_t nx0 = lerpPerlin(g00, g10, tx);
726 60885 int32_t nx1 = lerpPerlin(g01, g11, tx);
727
728 60885 int32_t noise = lerpPerlin(nx0, nx1, ty);
729 60885 return noise;
730 }
731
732 // 3D Perlin noise function that returns a value in range of -16788 to 16381
733 69116 int32_t perlin3D_raw(uint32_t x, uint32_t y, uint32_t z, bool is16bit) {
734 69116 int32_t x0 = x >> 16;
735 69116 int32_t y0 = y >> 16;
736 69116 int32_t z0 = z >> 16;
737 69116 int32_t x1 = x0 + 1;
738 69116 int32_t y1 = y0 + 1;
739 69116 int32_t z1 = z0 + 1;
740
741
2/2
✓ Branch 0 taken 39822 times.
✓ Branch 1 taken 29294 times.
69116 if(is16bit) {
742 39822 x1 = x1 & 0xFF; // wrap back to zero at 0xFF instead of 0xFFFF
743 39822 y1 = y1 & 0xFF;
744 39822 z1 = z1 & 0xFF;
745 }
746
747 69116 int32_t dx0 = x & 0xFFFF;
748 69116 int32_t dy0 = y & 0xFFFF;
749 69116 int32_t dz0 = z & 0xFFFF;
750 69116 int32_t dx1 = dx0 - 0x10000;
751 69116 int32_t dy1 = dy0 - 0x10000;
752 69116 int32_t dz1 = dz0 - 0x10000;
753
754 69116 int32_t g000 = gradient3D(x0, dx0, y0, dy0, z0, dz0);
755 69116 int32_t g001 = gradient3D(x0, dx0, y0, dy0, z1, dz1);
756 69116 int32_t g010 = gradient3D(x0, dx0, y1, dy1, z0, dz0);
757 69116 int32_t g011 = gradient3D(x0, dx0, y1, dy1, z1, dz1);
758 69116 int32_t g100 = gradient3D(x1, dx1, y0, dy0, z0, dz0);
759 69116 int32_t g101 = gradient3D(x1, dx1, y0, dy0, z1, dz1);
760 69116 int32_t g110 = gradient3D(x1, dx1, y1, dy1, z0, dz0);
761 69116 int32_t g111 = gradient3D(x1, dx1, y1, dy1, z1, dz1);
762
763 69116 uint32_t tx = smoothstep(dx0);
764 69116 uint32_t ty = smoothstep(dy0);
765 69116 uint32_t tz = smoothstep(dz0);
766
767 69116 int32_t nx0 = lerpPerlin(g000, g100, tx);
768 69116 int32_t nx1 = lerpPerlin(g010, g110, tx);
769 69116 int32_t nx2 = lerpPerlin(g001, g101, tx);
770 69116 int32_t nx3 = lerpPerlin(g011, g111, tx);
771 69116 int32_t ny0 = lerpPerlin(nx0, nx1, ty);
772 69116 int32_t ny1 = lerpPerlin(nx2, nx3, ty);
773
774 69116 int32_t noise = lerpPerlin(ny0, ny1, tz);
775 69116 return noise;
776 }
777
778 // scaling functions for fastled replacement
779 ✗ uint16_t perlin16(uint32_t x) {
780 ✗ return ((perlin1D_raw(x) * 1159) >> 10) + 32803; //scale to 16bit and offset (fastled range: about 4838 to 60766)
781 }
782
783 35721 uint16_t perlin16(uint32_t x, uint32_t y) {
784 35721 return ((perlin2D_raw(x, y) * 1537) >> 10) + 32725; //scale to 16bit and offset (fastled range: about 1748 to 63697)
785 }
786
787 29294 uint16_t perlin16(uint32_t x, uint32_t y, uint32_t z) {
788 29294 return ((perlin3D_raw(x, y, z) * 1731) >> 10) + 33147; //scale to 16bit and offset (fastled range: about 4766 to 60840)
789 }
790
791 7531 uint8_t perlin8(uint16_t x) {
792 7531 return (((perlin1D_raw((uint32_t)x << 8, true) * 1353) >> 10) + 32769) >> 8; //scale to 16 bit, offset, then scale to 8bit
793 }
794
795 25164 uint8_t perlin8(uint16_t x, uint16_t y) {
796 25164 return (((perlin2D_raw((uint32_t)x << 8, (uint32_t)y << 8, true) * 1620) >> 10) + 32771) >> 8; //scale to 16 bit, offset, then scale to 8bit
797 }
798
799 39822 uint8_t perlin8(uint16_t x, uint16_t y, uint16_t z) {
800 39822 return (((perlin3D_raw((uint32_t)x << 8, (uint32_t)y << 8, (uint32_t)z << 8, true) * 2015) >> 10) + 33168) >> 8; //scale to 16 bit, offset, then scale to 8bit
801 }
802