ee7036f13d
Fix repeat segment button remaining hidden Fix third segment row (offset) alignment in 1D mode Keep disabled sound simulation modes as comment for reference New local var for 2D seg UI, improves code legibility
552 lines
16 KiB
C++
552 lines
16 KiB
C++
#include "wled.h"
|
|
#include "fcn_declare.h"
|
|
#include "const.h"
|
|
|
|
|
|
//helper to get int value at a position in string
|
|
int getNumVal(const String* req, uint16_t pos)
|
|
{
|
|
return req->substring(pos+3).toInt();
|
|
}
|
|
|
|
|
|
//helper to get int value with in/decrementing support via ~ syntax
|
|
void parseNumber(const char* str, byte* val, byte minv, byte maxv)
|
|
{
|
|
if (str == nullptr || str[0] == '\0') return;
|
|
if (str[0] == 'r') {*val = random8(minv,maxv?maxv:255); return;} // maxv for random cannot be 0
|
|
bool wrap = false;
|
|
if (str[0] == 'w' && strlen(str) > 1) {str++; wrap = true;}
|
|
if (str[0] == '~') {
|
|
int out = atoi(str +1);
|
|
if (out == 0) {
|
|
if (str[1] == '0') return;
|
|
if (str[1] == '-') {
|
|
*val = (int)(*val -1) < (int)minv ? maxv : min((int)maxv,(*val -1)); //-1, wrap around
|
|
} else {
|
|
*val = (int)(*val +1) > (int)maxv ? minv : max((int)minv,(*val +1)); //+1, wrap around
|
|
}
|
|
} else {
|
|
if (wrap && *val == maxv && out > 0) out = minv;
|
|
else if (wrap && *val == minv && out < 0) out = maxv;
|
|
else {
|
|
out += *val;
|
|
if (out > maxv) out = maxv;
|
|
if (out < minv) out = minv;
|
|
}
|
|
*val = out;
|
|
}
|
|
return;
|
|
} else if (minv == maxv && minv == 0) { // limits "unset" i.e. both 0
|
|
byte p1 = atoi(str);
|
|
const char* str2 = strchr(str,'~'); // min/max range (for preset cycle, e.g. "1~5~")
|
|
if (str2) {
|
|
byte p2 = atoi(++str2); // skip ~
|
|
if (p2 > 0) {
|
|
while (isdigit(*(++str2))); // skip digits
|
|
parseNumber(str2, val, p1, p2);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
*val = atoi(str);
|
|
}
|
|
|
|
|
|
bool getVal(JsonVariant elem, byte* val, byte vmin, byte vmax) {
|
|
if (elem.is<int>()) {
|
|
if (elem < 0) return false; //ignore e.g. {"ps":-1}
|
|
*val = elem;
|
|
return true;
|
|
} else if (elem.is<const char*>()) {
|
|
const char* str = elem;
|
|
size_t len = strnlen(str, 12);
|
|
if (len == 0 || len > 10) return false;
|
|
parseNumber(str, val, vmin, vmax);
|
|
return true;
|
|
}
|
|
return false; //key does not exist
|
|
}
|
|
|
|
|
|
bool updateVal(const char* req, const char* key, byte* val, byte minv, byte maxv)
|
|
{
|
|
const char *v = strstr(req, key);
|
|
if (v) v += strlen(key);
|
|
else return false;
|
|
parseNumber(v, val, minv, maxv);
|
|
return true;
|
|
}
|
|
|
|
|
|
//append a numeric setting to string buffer
|
|
void sappend(char stype, const char* key, int val)
|
|
{
|
|
char ds[] = "d.Sf.";
|
|
|
|
switch(stype)
|
|
{
|
|
case 'c': //checkbox
|
|
oappend(ds);
|
|
oappend(key);
|
|
oappend(".checked=");
|
|
oappendi(val);
|
|
oappend(";");
|
|
break;
|
|
case 'v': //numeric
|
|
oappend(ds);
|
|
oappend(key);
|
|
oappend(".value=");
|
|
oappendi(val);
|
|
oappend(";");
|
|
break;
|
|
case 'i': //selectedIndex
|
|
oappend(ds);
|
|
oappend(key);
|
|
oappend(SET_F(".selectedIndex="));
|
|
oappendi(val);
|
|
oappend(";");
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
//append a string setting to buffer
|
|
void sappends(char stype, const char* key, char* val)
|
|
{
|
|
switch(stype)
|
|
{
|
|
case 's': {//string (we can interpret val as char*)
|
|
String buf = val;
|
|
//convert "%" to "%%" to make EspAsyncWebServer happy
|
|
//buf.replace("%","%%");
|
|
oappend("d.Sf.");
|
|
oappend(key);
|
|
oappend(".value=\"");
|
|
oappend(buf.c_str());
|
|
oappend("\";");
|
|
break;}
|
|
case 'm': //message
|
|
oappend(SET_F("d.getElementsByClassName"));
|
|
oappend(key);
|
|
oappend(SET_F(".innerHTML=\""));
|
|
oappend(val);
|
|
oappend("\";");
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
bool oappendi(int i)
|
|
{
|
|
char s[11];
|
|
sprintf(s, "%d", i);
|
|
return oappend(s);
|
|
}
|
|
|
|
|
|
bool oappend(const char* txt)
|
|
{
|
|
uint16_t len = strlen(txt);
|
|
if (olen + len >= SETTINGS_STACK_BUF_SIZE)
|
|
return false; // buffer full
|
|
strcpy(obuf + olen, txt);
|
|
olen += len;
|
|
return true;
|
|
}
|
|
|
|
|
|
void prepareHostname(char* hostname)
|
|
{
|
|
sprintf_P(hostname, "wled-%*s", 6, escapedMac.c_str() + 6);
|
|
const char *pC = serverDescription;
|
|
uint8_t pos = 5; // keep "wled-"
|
|
while (*pC && pos < 24) { // while !null and not over length
|
|
if (isalnum(*pC)) { // if the current char is alpha-numeric append it to the hostname
|
|
hostname[pos] = *pC;
|
|
pos++;
|
|
} else if (*pC == ' ' || *pC == '_' || *pC == '-' || *pC == '+' || *pC == '!' || *pC == '?' || *pC == '*') {
|
|
hostname[pos] = '-';
|
|
pos++;
|
|
}
|
|
// else do nothing - no leading hyphens and do not include hyphens for all other characters.
|
|
pC++;
|
|
}
|
|
//last character must not be hyphen
|
|
if (pos > 5) {
|
|
while (pos > 4 && hostname[pos -1] == '-') pos--;
|
|
hostname[pos] = '\0'; // terminate string (leave at least "wled")
|
|
}
|
|
}
|
|
|
|
|
|
bool isAsterisksOnly(const char* str, byte maxLen)
|
|
{
|
|
for (byte i = 0; i < maxLen; i++) {
|
|
if (str[i] == 0) break;
|
|
if (str[i] != '*') return false;
|
|
}
|
|
//at this point the password contains asterisks only
|
|
return (str[0] != 0); //false on empty string
|
|
}
|
|
|
|
|
|
//threading/network callback details: https://github.com/Aircoookie/WLED/pull/2336#discussion_r762276994
|
|
bool requestJSONBufferLock(uint8_t module)
|
|
{
|
|
unsigned long now = millis();
|
|
|
|
while (jsonBufferLock && millis()-now < 1000) delay(1); // wait for a second for buffer lock
|
|
|
|
if (millis()-now >= 1000) {
|
|
DEBUG_PRINT(F("ERROR: Locking JSON buffer failed! ("));
|
|
DEBUG_PRINT(jsonBufferLock);
|
|
DEBUG_PRINTLN(")");
|
|
return false; // waiting time-outed
|
|
}
|
|
|
|
jsonBufferLock = module ? module : 255;
|
|
DEBUG_PRINT(F("JSON buffer locked. ("));
|
|
DEBUG_PRINT(jsonBufferLock);
|
|
DEBUG_PRINTLN(")");
|
|
fileDoc = &doc; // used for applying presets (presets.cpp)
|
|
doc.clear();
|
|
return true;
|
|
}
|
|
|
|
|
|
void releaseJSONBufferLock()
|
|
{
|
|
DEBUG_PRINT(F("JSON buffer released. ("));
|
|
DEBUG_PRINT(jsonBufferLock);
|
|
DEBUG_PRINTLN(")");
|
|
fileDoc = nullptr;
|
|
jsonBufferLock = 0;
|
|
}
|
|
|
|
|
|
// extracts effect mode (or palette) name from names serialized string
|
|
// caller must provide large enough buffer for name (incluing SR extensions)!
|
|
uint8_t extractModeName(uint8_t mode, const char *src, char *dest, uint8_t maxLen)
|
|
{
|
|
if (src == JSON_mode_names || src == nullptr) {
|
|
if (mode < strip.getModeCount()) {
|
|
char lineBuffer[256];
|
|
//strcpy_P(lineBuffer, (const char*)pgm_read_dword(&(WS2812FX::_modeData[mode])));
|
|
strcpy_P(lineBuffer, strip.getModeData(mode));
|
|
size_t len = strlen(lineBuffer);
|
|
size_t j = 0;
|
|
for (; j < maxLen && j < len; j++) {
|
|
if (lineBuffer[j] == '\0' || lineBuffer[j] == '@') break;
|
|
dest[j] = lineBuffer[j];
|
|
}
|
|
dest[j] = 0; // terminate string
|
|
return strlen(dest);
|
|
} else return 0;
|
|
}
|
|
|
|
uint8_t qComma = 0;
|
|
bool insideQuotes = false;
|
|
uint8_t printedChars = 0;
|
|
char singleJsonSymbol;
|
|
size_t len = strlen_P(src);
|
|
|
|
// Find the mode name in JSON
|
|
for (size_t i = 0; i < len; i++) {
|
|
singleJsonSymbol = pgm_read_byte_near(src + i);
|
|
if (singleJsonSymbol == '\0') break;
|
|
if (singleJsonSymbol == '@' && insideQuotes && qComma == mode) break; //stop when SR extension encountered
|
|
switch (singleJsonSymbol) {
|
|
case '"':
|
|
insideQuotes = !insideQuotes;
|
|
break;
|
|
case '[':
|
|
case ']':
|
|
break;
|
|
case ',':
|
|
if (!insideQuotes) qComma++;
|
|
default:
|
|
if (!insideQuotes || (qComma != mode)) break;
|
|
dest[printedChars++] = singleJsonSymbol;
|
|
}
|
|
if ((qComma > mode) || (printedChars >= maxLen)) break;
|
|
}
|
|
dest[printedChars] = '\0';
|
|
return strlen(dest);
|
|
}
|
|
|
|
|
|
// extracts effect slider data (1st group after @)
|
|
uint8_t extractModeSlider(uint8_t mode, uint8_t slider, char *dest, uint8_t maxLen, uint8_t *var)
|
|
{
|
|
dest[0] = '\0'; // start by clearing buffer
|
|
|
|
if (mode < strip.getModeCount()) {
|
|
String lineBuffer = FPSTR(strip.getModeData(mode));
|
|
if (lineBuffer.length() > 0) {
|
|
int16_t start = lineBuffer.indexOf('@');
|
|
int16_t stop = lineBuffer.indexOf(';', start);
|
|
if (start>0 && stop>0) {
|
|
String names = lineBuffer.substring(start, stop); // include @
|
|
int16_t nameBegin = 1, nameEnd, nameDefault;
|
|
if (slider < 10) {
|
|
for (size_t i=0; i<=slider; i++) {
|
|
const char *tmpstr;
|
|
dest[0] = '\0'; //clear dest buffer
|
|
if (nameBegin == 0) break; // there are no more names
|
|
nameEnd = names.indexOf(',', nameBegin);
|
|
if (i == slider) {
|
|
nameDefault = names.indexOf('=', nameBegin); // find default value
|
|
if (nameDefault > 0 && var && ((nameEnd>0 && nameDefault<nameEnd) || nameEnd<0)) {
|
|
*var = (uint8_t)atoi(names.substring(nameDefault+1).c_str());
|
|
}
|
|
if (names.charAt(nameBegin) == '!') {
|
|
switch (slider) {
|
|
case 0: tmpstr = PSTR("FX Speed"); break;
|
|
case 1: tmpstr = PSTR("FX Intensity"); break;
|
|
case 2: tmpstr = PSTR("FX Custom 1"); break;
|
|
case 3: tmpstr = PSTR("FX Custom 2"); break;
|
|
case 4: tmpstr = PSTR("FX Custom 3"); break;
|
|
default: tmpstr = PSTR("FX Custom"); break;
|
|
}
|
|
strncpy_P(dest, tmpstr, maxLen); // copy the name into buffer (replacing previous)
|
|
dest[maxLen-1] = '\0';
|
|
} else {
|
|
if (nameEnd<0) tmpstr = names.substring(nameBegin).c_str(); // did not find ",", last name?
|
|
else tmpstr = names.substring(nameBegin, nameEnd).c_str();
|
|
strlcpy(dest, tmpstr, maxLen); // copy the name into buffer (replacing previous)
|
|
}
|
|
}
|
|
nameBegin = nameEnd+1; // next name (if "," is not found it will be 0)
|
|
} // next slider
|
|
} else if (slider == 255) {
|
|
// palette
|
|
strlcpy(dest, "pal", maxLen);
|
|
names = lineBuffer.substring(stop+1); // stop has index of color slot names
|
|
nameBegin = names.indexOf(';'); // look for palette
|
|
if (nameBegin >= 0) {
|
|
nameEnd = names.indexOf(';', nameBegin+1);
|
|
if (!isdigit(names[nameBegin+1])) nameBegin = names.indexOf('=', nameBegin+1); // look for default value
|
|
if (nameEnd >= 0 && nameBegin > nameEnd) nameBegin = -1;
|
|
if (nameBegin >= 0 && var) {
|
|
*var = (uint8_t)atoi(names.substring(nameBegin+1).c_str());
|
|
}
|
|
}
|
|
}
|
|
// we have slider name (including default value) in the dest buffer
|
|
for (size_t i=0; i<strlen(dest); i++) if (dest[i]=='=') { dest[i]='\0'; break; } // truncate default value
|
|
|
|
} else {
|
|
// defaults to just speed and intensity since there is no slider data
|
|
switch (slider) {
|
|
case 0: strncpy_P(dest, PSTR("FX Speed"), maxLen); break;
|
|
case 1: strncpy_P(dest, PSTR("FX Intensity"), maxLen); break;
|
|
}
|
|
dest[maxLen] = '\0'; // strncpy does not necessarily null terminate string
|
|
}
|
|
}
|
|
return strlen(dest);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
// extracts mode parameter defaults from last section of mode data (e.g. "Juggle@!,Trail;!,!,;!;sx=16,ix=240,1d")
|
|
int16_t extractModeDefaults(uint8_t mode, const char *segVar)
|
|
{
|
|
if (mode < strip.getModeCount()) {
|
|
char lineBuffer[128] = "";
|
|
strncpy_P(lineBuffer, strip.getModeData(mode), 127);
|
|
lineBuffer[127] = '\0'; // terminate string
|
|
if (lineBuffer[0] != 0) {
|
|
char* startPtr = strrchr(lineBuffer, ';'); // last ";" in FX data
|
|
if (!startPtr) return -1;
|
|
|
|
char* stopPtr = strstr(startPtr, segVar);
|
|
if (!stopPtr) return -1;
|
|
|
|
stopPtr += strlen(segVar) +1; // skip "="
|
|
return atoi(stopPtr);
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
|
|
uint16_t crc16(const unsigned char* data_p, size_t length) {
|
|
uint8_t x;
|
|
uint16_t crc = 0xFFFF;
|
|
if (!length) return 0x1D0F;
|
|
while (length--) {
|
|
x = crc >> 8 ^ *data_p++;
|
|
x ^= x>>4;
|
|
crc = (crc << 8) ^ ((uint16_t)(x << 12)) ^ ((uint16_t)(x <<5)) ^ ((uint16_t)x);
|
|
}
|
|
return crc;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Begin simulateSound (to enable audio enhanced effects to display something)
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Currently 4 types defined, to be fine tuned and new types added
|
|
// (only 2 used as stored in 1 bit in segment options, consider switching to a single global simulation type)
|
|
typedef enum UM_SoundSimulations {
|
|
UMS_BeatSin = 0,
|
|
UMS_WeWillRockYou
|
|
//UMS_10_13,
|
|
//UMS_14_3
|
|
} um_soundSimulations_t;
|
|
|
|
um_data_t* simulateSound(uint8_t simulationId)
|
|
{
|
|
static uint8_t samplePeak;
|
|
static float FFT_MajorPeak;
|
|
static uint8_t maxVol;
|
|
static uint8_t binNum;
|
|
|
|
static float volumeSmth;
|
|
static uint16_t volumeRaw;
|
|
static float my_magnitude;
|
|
|
|
//arrays
|
|
uint8_t *fftResult;
|
|
|
|
static um_data_t* um_data = nullptr;
|
|
|
|
if (!um_data) {
|
|
//claim storage for arrays
|
|
fftResult = (uint8_t *)malloc(sizeof(uint8_t) * 16);
|
|
|
|
// initialize um_data pointer structure
|
|
// NOTE!!!
|
|
// This may change as AudioReactive usermod may change
|
|
um_data = new um_data_t;
|
|
um_data->u_size = 8;
|
|
um_data->u_type = new um_types_t[um_data->u_size];
|
|
um_data->u_data = new void*[um_data->u_size];
|
|
um_data->u_data[0] = &volumeSmth;
|
|
um_data->u_data[1] = &volumeRaw;
|
|
um_data->u_data[2] = fftResult;
|
|
um_data->u_data[3] = &samplePeak;
|
|
um_data->u_data[4] = &FFT_MajorPeak;
|
|
um_data->u_data[5] = &my_magnitude;
|
|
um_data->u_data[6] = &maxVol;
|
|
um_data->u_data[7] = &binNum;
|
|
} else {
|
|
// get arrays from um_data
|
|
fftResult = (uint8_t*)um_data->u_data[2];
|
|
}
|
|
|
|
uint32_t ms = millis();
|
|
|
|
switch (simulationId) {
|
|
default:
|
|
case UMS_BeatSin:
|
|
for (int i = 0; i<16; i++)
|
|
fftResult[i] = beatsin8(120 / (i+1), 0, 255);
|
|
// fftResult[i] = (beatsin8(120, 0, 255) + (256/16 * i)) % 256;
|
|
volumeSmth = fftResult[8];
|
|
break;
|
|
case UMS_WeWillRockYou:
|
|
if (ms%2000 < 200) {
|
|
volumeSmth = random8(255);
|
|
for (int i = 0; i<5; i++)
|
|
fftResult[i] = random8(255);
|
|
}
|
|
else if (ms%2000 < 400) {
|
|
volumeSmth = 0;
|
|
for (int i = 0; i<16; i++)
|
|
fftResult[i] = 0;
|
|
}
|
|
else if (ms%2000 < 600) {
|
|
volumeSmth = random8(255);
|
|
for (int i = 5; i<11; i++)
|
|
fftResult[i] = random8(255);
|
|
}
|
|
else if (ms%2000 < 800) {
|
|
volumeSmth = 0;
|
|
for (int i = 0; i<16; i++)
|
|
fftResult[i] = 0;
|
|
}
|
|
else if (ms%2000 < 1000) {
|
|
volumeSmth = random8(255);
|
|
for (int i = 11; i<16; i++)
|
|
fftResult[i] = random8(255);
|
|
}
|
|
else {
|
|
volumeSmth = 0;
|
|
for (int i = 0; i<16; i++)
|
|
fftResult[i] = 0;
|
|
}
|
|
break;
|
|
/*case UMS_10_3:
|
|
for (int i = 0; i<16; i++)
|
|
fftResult[i] = inoise8(beatsin8(90 / (i+1), 0, 200)*15 + (ms>>10), ms>>3);
|
|
volumeSmth = fftResult[8];
|
|
break;
|
|
case UMS_14_3:
|
|
for (int i = 0; i<16; i++)
|
|
fftResult[i] = inoise8(beatsin8(120 / (i+1), 10, 30)*10 + (ms>>14), ms>>3);
|
|
volumeSmth = fftResult[8];
|
|
break;*/
|
|
}
|
|
|
|
samplePeak = random8() > 250;
|
|
FFT_MajorPeak = volumeSmth;
|
|
maxVol = 10; // this gets feedback fro UI
|
|
binNum = 8; // this gets feedback fro UI
|
|
volumeRaw = volumeSmth;
|
|
my_magnitude = 10000.0 / 8.0f; //no idea if 10000 is a good value for FFT_Magnitude ???
|
|
if (volumeSmth < 1 ) my_magnitude = 0.001f; // noise gate closed - mute
|
|
|
|
return um_data;
|
|
}
|
|
|
|
|
|
// enumerate all ledmapX.json files on FS and extract ledmap names if existing
|
|
void enumerateLedmaps() {
|
|
ledMaps = 1;
|
|
for (size_t i=1; i<WLED_MAX_LEDMAPS; i++) {
|
|
char fileName[33];
|
|
sprintf_P(fileName, PSTR("/ledmap%d.json"), i);
|
|
bool isFile = WLED_FS.exists(fileName);
|
|
|
|
#ifndef ESP8266
|
|
if (ledmapNames[i-1]) { //clear old name
|
|
delete[] ledmapNames[i-1];
|
|
ledmapNames[i-1] = nullptr;
|
|
}
|
|
#endif
|
|
|
|
if (isFile) {
|
|
ledMaps |= 1 << i;
|
|
|
|
#ifndef ESP8266
|
|
if (requestJSONBufferLock(21)) {
|
|
if (readObjectFromFile(fileName, nullptr, &doc)) {
|
|
size_t len = 0;
|
|
if (!doc["n"].isNull()) {
|
|
// name field exists
|
|
const char *name = doc["n"].as<const char*>();
|
|
if (name != nullptr) len = strlen(name);
|
|
if (len > 0 && len < 33) {
|
|
ledmapNames[i-1] = new char[len+1];
|
|
if (ledmapNames[i-1]) strlcpy(ledmapNames[i-1], name, 33);
|
|
}
|
|
}
|
|
if (!ledmapNames[i-1]) {
|
|
char tmp[33];
|
|
snprintf_P(tmp, 32, PSTR("ledmap%d.json"), i);
|
|
len = strlen(tmp);
|
|
ledmapNames[i-1] = new char[len+1];
|
|
if (ledmapNames[i-1]) strlcpy(ledmapNames[i-1], tmp, 33);
|
|
}
|
|
}
|
|
releaseJSONBufferLock();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
}
|
|
} |