From e16c4b868104a724faaec9245827f7a7b20db5e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Kristan?= Date: Sun, 29 Dec 2024 19:50:08 +0100 Subject: [PATCH 01/14] UI info --- wled00/data/index.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/wled00/data/index.js b/wled00/data/index.js index c9ee23ad5..388770cc6 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -773,8 +773,8 @@ function populateSegments(s) } let segp = `
`+ - ``+ - `
`+ + ``+ + `
`+ ``+ `
`+ `
`+ @@ -810,7 +810,7 @@ function populateSegments(s) cn += `
`+ ``+ `
`+ `&#x${inst.frz ? (li.live && li.liveseg==i?'e410':'e0e8') : 'e325'};`+ @@ -1659,13 +1659,17 @@ function setEffectParameters(idx) paOnOff[0] = paOnOff[0].substring(0,dPos); } if (paOnOff.length>0 && paOnOff[0] != "!") text = paOnOff[0]; + gId("adPal").classList.remove("hide"); + if (lastinfo.cpalcount>0) gId("rmPal").classList.remove("hide"); } else { // disable palette list text += ' not used'; palw.style.display = "none"; + gId("adPal").classList.add("hide"); + gId("rmPal").classList.add("hide"); // Close palette dialog if not available - if (gId("palw").lastElementChild.tagName == "DIALOG") { - gId("palw").lastElementChild.close(); + if (palw.lastElementChild.tagName == "DIALOG") { + palw.lastElementChild.close(); } } pall.innerHTML = icon + text; @@ -1879,7 +1883,7 @@ function makeSeg() function resetUtil(off=false) { gId('segutil').innerHTML = `
` - + '' + + '' + `
Add segment
` + '
' + `` From 249c1241764d6e4e72999f7a566b20a4b8b92173 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Kristan?= Date: Tue, 31 Dec 2024 17:31:34 +0100 Subject: [PATCH 02/14] Clear spaced segment - also wait for strip before updating segment --- wled00/FX.h | 1 + wled00/FX_fcn.cpp | 67 ++++++++++++++++++++++---------- wled00/json.cpp | 97 ++++++++++++++++++++++++----------------------- 3 files changed, 97 insertions(+), 68 deletions(-) diff --git a/wled00/FX.h b/wled00/FX.h index e36cad307..747be80a7 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -596,6 +596,7 @@ typedef struct Segment { [[gnu::hot]] uint32_t getPixelColor(int i) const; // 1D support functions (some implement 2D as well) void blur(uint8_t, bool smear = false); + void clear(); void fill(uint32_t c); void fade_out(uint8_t r); void fadeToBlackBy(uint8_t fadeBy); diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index d0b13d7b8..bd2787ad4 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -279,22 +279,27 @@ void Segment::startTransition(uint16_t dur) { _t->_briT = on ? opacity : 0; _t->_cctT = cct; #ifndef WLED_DISABLE_MODE_BLEND - if (modeBlending) { - swapSegenv(_t->_segT); - _t->_modeT = mode; - _t->_segT._dataLenT = 0; - _t->_segT._dataT = nullptr; - if (_dataLen > 0 && data) { - _t->_segT._dataT = (byte *)malloc(_dataLen); - if (_t->_segT._dataT) { - //DEBUG_PRINTF_P(PSTR("-- Allocated duplicate data (%d) for %p: %p\n"), _dataLen, this, _t->_segT._dataT); - memcpy(_t->_segT._dataT, data, _dataLen); - _t->_segT._dataLenT = _dataLen; - } + swapSegenv(_t->_segT); // copy runtime data to temporary + _t->_modeT = mode; + _t->_segT._dataLenT = 0; + _t->_segT._dataT = nullptr; + if (_dataLen > 0 && data) { + _t->_segT._dataT = (byte *)malloc(_dataLen); + if (_t->_segT._dataT) { + //DEBUG_PRINTF_P(PSTR("-- Allocated duplicate data (%d) for %p: %p\n"), _dataLen, this, _t->_segT._dataT); + memcpy(_t->_segT._dataT, data, _dataLen); + _t->_segT._dataLenT = _dataLen; } } else { for (size_t i=0; i_segT._colorT[i] = colors[i]; } + DEBUG_PRINTF_P(PSTR("-- pal: %d, bri: %d, C:[%08X,%08X,%08X], m: %d\n"), + (int)_t->_palTid, + (int)_t->_briT, + _t->_segT._colorT[0], + _t->_segT._colorT[1], + _t->_segT._colorT[2], + (int)_t->_modeT); #else for (size_t i=0; i_colorT[i] = colors[i]; #endif @@ -462,13 +467,16 @@ void Segment::setGeometry(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, ui #ifndef WLED_DISABLE_2D if (Segment::maxHeight>1) boundsUnchanged &= (startY == i1Y && stopY == i2Y); // 2D #endif + + if (stop && (spc > 0 || m12 != map1D2D)) clear(); +/* if (boundsUnchanged && (!grp || (grouping == grp && spacing == spc)) - && (ofs == UINT16_MAX || ofs == offset)) return; - + && (m12 == map1D2D) + ) return; +*/ stateChanged = true; // send UDP/WS broadcast - if (stop) fill(BLACK); // turn old segment range off (clears pixels if changing spacing) if (grp) { // prevent assignment of 0 grouping = grp; spacing = spc; @@ -478,10 +486,7 @@ void Segment::setGeometry(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, ui } if (ofs < UINT16_MAX) offset = ofs; - DEBUG_PRINT(F("setUp segment: ")); DEBUG_PRINT(i1); - DEBUG_PRINT(','); DEBUG_PRINT(i2); - DEBUG_PRINT(F(" -> ")); DEBUG_PRINT(i1Y); - DEBUG_PRINT(','); DEBUG_PRINTLN(i2Y); + DEBUG_PRINTF_P(PSTR("Segment geometry: %d,%d -> %d,%d\n"), (int)i1, (int)i2, (int)i1Y, (int)i2Y); markForReset(); if (boundsUnchanged) return; @@ -1040,6 +1045,26 @@ void Segment::refreshLightCapabilities() { _capabilities = capabilities; } +/* + * Fills segment with black + */ +void Segment::clear() { + if (!isActive()) return; // not active + unsigned oldVW = _vWidth; + unsigned oldVH = _vHeight; + unsigned oldVL = _vLength; + unsigned oldSB = _segBri; + _vWidth = virtualWidth(); + _vHeight = virtualHeight(); + _vLength = virtualLength(); + _segBri = currentBri(); + fill(BLACK); + _vWidth = oldVW; + _vHeight = oldVH; + _vLength = oldVL; + _segBri = oldSB; +} + /* * Fills segment with color */ @@ -1364,7 +1389,7 @@ void WS2812FX::service() { _segment_index = 0; for (segment &seg : _segments) { - if (_suspend) return; // immediately stop processing segments if suspend requested during service() + if (_suspend) break; // immediately stop processing segments if suspend requested during service() // process transition (mode changes in the middle of transition) seg.handleTransition(); @@ -1429,8 +1454,8 @@ void WS2812FX::service() { if (doShow) { yield(); Segment::handleRandomPalette(); // slowly transition random palette; move it into for loop when each segment has individual random palette - show(); _lastServiceShow = nowUp; // update timestamp, for precise FPS control + if (!_suspend) show(); } #ifdef WLED_DEBUG if ((_targetFps != FPS_UNLIMITED) && (millis() - nowUp > _frametime)) DEBUG_PRINTF_P(PSTR("Slow strip %u/%d.\n"), (unsigned)(millis()-nowUp), (int)_frametime); diff --git a/wled00/json.cpp b/wled00/json.cpp index 2f77f24ef..bb2ab3dca 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -91,16 +91,20 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId) } } - uint16_t grp = elem["grp"] | seg.grouping; - uint16_t spc = elem[F("spc")] | seg.spacing; - uint16_t of = seg.offset; - uint8_t soundSim = elem["si"] | seg.soundSim; - uint8_t map1D2D = elem["m12"] | seg.map1D2D; - - if ((spc>0 && spc!=seg.spacing) || seg.map1D2D!=map1D2D) seg.fill(BLACK); // clear spacing gaps - - seg.map1D2D = constrain(map1D2D, 0, 7); - seg.soundSim = constrain(soundSim, 0, 3); + uint16_t grp = elem["grp"] | seg.grouping; + uint16_t spc = elem[F("spc")] | seg.spacing; + uint16_t of = seg.offset; + uint8_t soundSim = elem["si"] | seg.soundSim; + uint8_t map1D2D = elem["m12"] | seg.map1D2D; + uint8_t set = elem[F("set")] | seg.set; + bool selected = getBoolVal(elem["sel"], seg.selected); + bool reverse = getBoolVal(elem["rev"], seg.reverse); + bool mirror = getBoolVal(elem["mi"] , seg.mirror); + #ifndef WLED_DISABLE_2D + bool reverse_y = getBoolVal(elem["rY"] , seg.reverse_y); + bool mirror_y = getBoolVal(elem["mY"] , seg.mirror_y); + bool transpose = getBoolVal(elem[F("tp")], seg.transpose); + #endif uint8_t set = elem[F("set")] | seg.set; seg.set = constrain(set, 0, 3); @@ -206,20 +210,16 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId) } #endif + //seg.map1D2D = constrain(map1D2D, 0, 7); // done in setGeometry() + seg.set = constrain(set, 0, 3); + seg.soundSim = constrain(soundSim, 0, 3); + seg.selected = selected; + seg.reverse = reverse; + seg.mirror = mirror; #ifndef WLED_DISABLE_2D - bool reverse = seg.reverse; - bool mirror = seg.mirror; - #endif - seg.selected = getBoolVal(elem["sel"], seg.selected); - seg.reverse = getBoolVal(elem["rev"], seg.reverse); - seg.mirror = getBoolVal(elem["mi"] , seg.mirror); - #ifndef WLED_DISABLE_2D - bool reverse_y = seg.reverse_y; - bool mirror_y = seg.mirror_y; - seg.reverse_y = getBoolVal(elem["rY"] , seg.reverse_y); - seg.mirror_y = getBoolVal(elem["mY"] , seg.mirror_y); - seg.transpose = getBoolVal(elem[F("tp")], seg.transpose); - if (seg.is2D() && seg.map1D2D == M12_pArc && (reverse != seg.reverse || reverse_y != seg.reverse_y || mirror != seg.mirror || mirror_y != seg.mirror_y)) seg.fill(BLACK); // clear entire segment (in case of Arc 1D to 2D expansion) + seg.reverse_y = reverse_y; + seg.mirror_y = mirror_y; + seg.transpose = transpose; #endif byte fx = seg.mode; @@ -393,35 +393,38 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId) int it = 0; JsonVariant segVar = root["seg"]; - if (!segVar.isNull()) strip.suspend(); - if (segVar.is()) - { - int id = segVar["id"] | -1; - //if "seg" is not an array and ID not specified, apply to all selected/checked segments - if (id < 0) { - //apply all selected segments - //bool didSet = false; - for (size_t s = 0; s < strip.getSegmentsNum(); s++) { - Segment &sg = strip.getSegment(s); - if (sg.isActive() && sg.isSelected()) { - deserializeSegment(segVar, s, presetId); - //didSet = true; + if (!segVar.isNull()) { + // we may be called during strip.service() so we must not modify segments while effects are executing + strip.suspend(); + const unsigned long start = millis(); + while (strip.isServicing() && millis() - start < strip.getFrameTime()) yield(); // wait until frame is over + #ifdef WLED_DEBUG + if (millis() - start > 0) DEBUG_PRINTLN(F("JSON: Waited for strip to finish servicing.")); + #endif + if (segVar.is()) { + int id = segVar["id"] | -1; + //if "seg" is not an array and ID not specified, apply to all selected/checked segments + if (id < 0) { + //apply all selected segments + for (size_t s = 0; s < strip.getSegmentsNum(); s++) { + Segment &sg = strip.getSegment(s); + if (sg.isActive() && sg.isSelected()) { + deserializeSegment(segVar, s, presetId); + } } + } else { + deserializeSegment(segVar, id, presetId); //apply only the segment with the specified ID } - //TODO: not sure if it is good idea to change first active but unselected segment - //if (!didSet) deserializeSegment(segVar, strip.getMainSegmentId(), presetId); } else { - deserializeSegment(segVar, id, presetId); //apply only the segment with the specified ID + size_t deleted = 0; + JsonArray segs = segVar.as(); + for (JsonObject elem : segs) { + if (deserializeSegment(elem, it++, presetId) && !elem["stop"].isNull() && elem["stop"]==0) deleted++; + } + if (strip.getSegmentsNum() > 3 && deleted >= strip.getSegmentsNum()/2U) strip.purgeSegments(); // batch deleting more than half segments } - } else { - size_t deleted = 0; - JsonArray segs = segVar.as(); - for (JsonObject elem : segs) { - if (deserializeSegment(elem, it++, presetId) && !elem["stop"].isNull() && elem["stop"]==0) deleted++; - } - if (strip.getSegmentsNum() > 3 && deleted >= strip.getSegmentsNum()/2U) strip.purgeSegments(); // batch deleting more than half segments + strip.resume(); } - strip.resume(); UsermodManager::readFromJsonState(root); From 47a9e4aa51a8b6f0afa507a18deb405a32ae3080 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Kristan?= Date: Tue, 7 Jan 2025 20:39:44 +0100 Subject: [PATCH 03/14] Shifting bugfix & size tuning in fade_out --- wled00/FX_fcn.cpp | 88 +++++++++++++++++++++-------------------------- 1 file changed, 39 insertions(+), 49 deletions(-) diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index bd2787ad4..0edc9333b 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -279,30 +279,31 @@ void Segment::startTransition(uint16_t dur) { _t->_briT = on ? opacity : 0; _t->_cctT = cct; #ifndef WLED_DISABLE_MODE_BLEND - swapSegenv(_t->_segT); // copy runtime data to temporary - _t->_modeT = mode; - _t->_segT._dataLenT = 0; - _t->_segT._dataT = nullptr; - if (_dataLen > 0 && data) { - _t->_segT._dataT = (byte *)malloc(_dataLen); - if (_t->_segT._dataT) { - //DEBUG_PRINTF_P(PSTR("-- Allocated duplicate data (%d) for %p: %p\n"), _dataLen, this, _t->_segT._dataT); - memcpy(_t->_segT._dataT, data, _dataLen); - _t->_segT._dataLenT = _dataLen; + if (modeBlending) { + swapSegenv(_t->_segT); // copy runtime data to temporary + _t->_modeT = mode; + _t->_segT._dataLenT = 0; + _t->_segT._dataT = nullptr; + if (_dataLen > 0 && data) { + _t->_segT._dataT = (byte *)malloc(_dataLen); + if (_t->_segT._dataT) { + //DEBUG_PRINTF_P(PSTR("-- Allocated duplicate data (%d) for %p: %p\n"), _dataLen, this, _t->_segT._dataT); + memcpy(_t->_segT._dataT, data, _dataLen); + _t->_segT._dataLenT = _dataLen; + } + } else { + for (size_t i=0; i_segT._colorT[i] = colors[i]; } - } else { - for (size_t i=0; i_segT._colorT[i] = colors[i]; - } - DEBUG_PRINTF_P(PSTR("-- pal: %d, bri: %d, C:[%08X,%08X,%08X], m: %d\n"), - (int)_t->_palTid, - (int)_t->_briT, - _t->_segT._colorT[0], - _t->_segT._colorT[1], - _t->_segT._colorT[2], - (int)_t->_modeT); -#else + DEBUG_PRINTF_P(PSTR("-- pal: %d, bri: %d, C:[%08X,%08X,%08X], m: %d\n"), + (int)_t->_palTid, + (int)_t->_briT, + _t->_segT._colorT[0], + _t->_segT._colorT[1], + _t->_segT._colorT[2], + (int)_t->_modeT); + } else +# endif for (size_t i=0; i_colorT[i] = colors[i]; -#endif } void Segment::stopTransition() { @@ -1086,36 +1087,25 @@ void Segment::fade_out(uint8_t rate) { const int cols = is2D() ? virtualWidth() : virtualLength(); const int rows = virtualHeight(); // will be 1 for 1D - rate = (255-rate) >> 1; - float mappedRate = 1.0f / (float(rate) + 1.1f); - - uint32_t color = colors[1]; // SEGCOLOR(1); // target color - int w2 = W(color); - int r2 = R(color); - int g2 = G(color); - int b2 = B(color); + rate = (256-rate) >> 1; + const int mappedRate = 256 / (rate + 1); for (int y = 0; y < rows; y++) for (int x = 0; x < cols; x++) { - color = is2D() ? getPixelColorXY(x, y) : getPixelColor(x); + uint32_t color = is2D() ? getPixelColorXY(x, y) : getPixelColor(x); if (color == colors[1]) continue; // already at target color - int w1 = W(color); - int r1 = R(color); - int g1 = G(color); - int b1 = B(color); - - int wdelta = (w2 - w1) * mappedRate; - int rdelta = (r2 - r1) * mappedRate; - int gdelta = (g2 - g1) * mappedRate; - int bdelta = (b2 - b1) * mappedRate; - - // if fade isn't complete, make sure delta is at least 1 (fixes rounding issues) - wdelta += (w2 == w1) ? 0 : (w2 > w1) ? 1 : -1; - rdelta += (r2 == r1) ? 0 : (r2 > r1) ? 1 : -1; - gdelta += (g2 == g1) ? 0 : (g2 > g1) ? 1 : -1; - bdelta += (b2 == b1) ? 0 : (b2 > b1) ? 1 : -1; - - if (is2D()) setPixelColorXY(x, y, r1 + rdelta, g1 + gdelta, b1 + bdelta, w1 + wdelta); - else setPixelColor(x, r1 + rdelta, g1 + gdelta, b1 + bdelta, w1 + wdelta); + for (int i = 0; i < 32; i += 8) { + uint8_t c2 = (colors[1]>>i); // get background channel + uint8_t c1 = (color>>i); // get foreground channel + // we can't use bitshift since we are using int + int delta = (c2 - c1) * mappedRate / 256; + // if fade isn't complete, make sure delta is at least 1 (fixes rounding issues) + if (delta == 0) delta += (c2 == c1) ? 0 : (c2 > c1) ? 1 : -1; + // stuff new value back into color + color &= ~(0xFF< Date: Wed, 8 Jan 2025 21:20:07 +0100 Subject: [PATCH 04/14] W Hex entry bugfix & optiisation --- wled00/data/index.js | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/wled00/data/index.js b/wled00/data/index.js index 388770cc6..b008ba602 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -2653,28 +2653,28 @@ function fromRgb() var g = gId('sliderG').value; var b = gId('sliderB').value; setPicker(`rgb(${r},${g},${b})`); - let cd = gId('csl').children; // color slots - cd[csel].dataset.r = r; - cd[csel].dataset.g = g; - cd[csel].dataset.b = b; - setCSL(cd[csel]); + let cd = gId('csl').children[csel]; // color slots + cd.dataset.r = r; + cd.dataset.g = g; + cd.dataset.b = b; + setCSL(cd); } function fromW() { let w = gId('sliderW'); - let cd = gId('csl').children; // color slots - cd[csel].dataset.w = w.value; - setCSL(cd[csel]); + let cd = gId('csl').children[csel]; // color slots + cd.dataset.w = w.value; + setCSL(cd); updateTrail(w); } // sr 0: from RGB sliders, 1: from picker, 2: from hex function setColor(sr) { - var cd = gId('csl').children; // color slots - let cdd = cd[csel].dataset; - let w = 0, r,g,b; + var cd = gId('csl').children[csel]; // color slots + let cdd = cd.dataset; + let w = parseInt(cdd.w), r = parseInt(cdd.r), g = parseInt(cdd.g), b = parseInt(cdd.b); if (sr == 1 && isRgbBlack(cdd)) cpick.color.setChannel('hsv', 'v', 100); if (sr != 2 && hasWhite) w = parseInt(gId('sliderW').value); var col = cpick.color.rgb; @@ -2682,7 +2682,7 @@ function setColor(sr) cdd.g = g = hasRGB ? col.g : w; cdd.b = b = hasRGB ? col.b : w; cdd.w = w; - setCSL(cd[csel]); + setCSL(cd); var obj = {"seg": {"col": [[],[],[]]}}; obj.seg.col[csel] = [r, g, b, w]; requestJson(obj); From 762679177c0d763c574753d0aeddbf0688ead925 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Kristan?= Date: Fri, 17 Jan 2025 16:05:26 +0100 Subject: [PATCH 05/14] Replace magic with cosntant --- wled00/wled.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/wled.cpp b/wled00/wled.cpp index b24e0c5e9..7b57b1dac 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -459,7 +459,7 @@ void WLED::setup() #endif // fill in unique mdns default - if (strcmp(cmDNS, "x") == 0) sprintf_P(cmDNS, PSTR("wled-%*s"), 6, escapedMac.c_str() + 6); + if (strcmp(cmDNS, DEFAULT_MDNS_NAME) == 0) sprintf_P(cmDNS, PSTR("wled-%*s"), 6, escapedMac.c_str() + 6); #ifndef WLED_DISABLE_MQTT if (mqttDeviceTopic[0] == 0) sprintf_P(mqttDeviceTopic, PSTR("wled/%*s"), 6, escapedMac.c_str() + 6); if (mqttClientID[0] == 0) sprintf_P(mqttClientID, PSTR("WLED-%*s"), 6, escapedMac.c_str() + 6); From a38d6075c7de4d695ef23bdba7bbb9a959cc10e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Kristan?= Date: Sun, 19 Jan 2025 21:30:17 +0100 Subject: [PATCH 06/14] Bugfix in settings --- wled00/data/settings_leds.htm | 21 ++++++++++++--------- wled00/set.cpp | 2 +- wled00/xml.cpp | 2 +- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index 5f9db0889..6c6811c2d 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -24,6 +24,7 @@ function is16b(t) { return !!(gT(t).c & 0x10); } // is digital 16 bit type function mustR(t) { return !!(gT(t).c & 0x20); } // Off refresh is mandatory function numPins(t){ return Math.max(gT(t).t.length, 1); } // type length determines number of GPIO pins + function chrID(x) { return String.fromCharCode((x<10?48:55)+x); } function S() { getLoc(); loadJS(getURL('/settings/s.js?p=2'), false, ()=>{ @@ -138,7 +139,7 @@ gId("ppldis").style.display = ppl ? 'inline' : 'none'; // set PPL minimum value and clear actual PPL limit if ABL is disabled d.Sf.querySelectorAll("#mLC input[name^=MA]").forEach((i,x)=>{ - var n = String.fromCharCode((x<10?48:55)+x); + var n = chrID(x); gId("PSU"+n).style.display = ppl ? "inline" : "none"; const t = parseInt(d.Sf["LT"+n].value); // LED type SELECT const c = parseInt(d.Sf["LC"+n].value); //get LED count @@ -169,7 +170,7 @@ // select appropriate LED current d.Sf.querySelectorAll("#mLC select[name^=LAsel]").forEach((sel,x)=>{ sel.value = 0; // set custom - var n = String.fromCharCode((x<10?48:55)+x); + var n = chrID(x); if (en) switch (parseInt(d.Sf["LA"+n].value)) { case 0: break; // disable ABL @@ -399,7 +400,7 @@ } function lastEnd(i) { if (i-- < 1) return 0; - var s = String.fromCharCode((i<10?48:55)+i); + var s = chrID(i); v = parseInt(d.getElementsByName("LS"+s)[0].value) + parseInt(d.getElementsByName("LC"+s)[0].value); var t = parseInt(d.getElementsByName("LT"+s)[0].value); if (isPWM(t)) v = 1; //PWM busses @@ -422,7 +423,7 @@ }); if ((n==1 && i>=maxB+maxV) || (n==-1 && i==0)) return; - var s = String.fromCharCode((i<10?48:55)+i); + var s = chrID(i); if (n==1) { // npm run build has trouble minimizing spaces inside string @@ -506,7 +507,7 @@ mA/LED:   @@ -560,7 +561,7 @@ Swap: `; c += `  function checkSi() { //on load, checks whether there are custom start fields var cs = false; for (var i=1; i < gEBCN("iST").length; i++) { - var v = parseInt(gId("ls"+(i-1)).value) + parseInt(gN("LC"+(i-1)).value); - if (v != parseInt(gId("ls"+i).value)) {cs = true; startsDirty[i] = true;} + var s = chrID(i); + var p = chrID(i-1); // cover edge case 'A' previous char being '9' + var v = parseInt(gId("ls"+p).value) + parseInt(gN("LC"+p).value); + if (v != parseInt(gId("ls"+s).value)) {cs = true; startsDirty[i] = true;} } if (gId("ls0") && parseInt(gId("ls0").value) != 0) {cs = true; startsDirty[0] = true;} gId("si").checked = cs; @@ -617,7 +620,7 @@ Swap: