diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 8b1d22d2d..5274805ba 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,2 +1,5 @@ github: [Aircoookie] custom: ['https://paypal.me/Aircoookie'] + +github: [blazoncek] +custom: ['https://paypal.me/blazoncek'] diff --git a/.gitignore b/.gitignore index 02e648b88..bb02e36ef 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,6 @@ .pioenvs .piolibdeps .vscode -!.vscode/extensions.json /wled00/Release /wled00/extLibs /platformio_override.ini @@ -15,3 +14,7 @@ node_modules .idea .direnv +wled-update.sh +esp01-update.sh +/wled00/LittleFS +replace_fs.py \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index f9e1ea0fc..ce4caa8e7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "wled", - "version": "0.13.3", + "version": "0.14.0-b0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 494f82e95..1e31f689c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wled", - "version": "0.13.3", + "version": "0.14.0-b0", "description": "Tools for WLED project", "main": "tools/cdata.js", "directories": { diff --git a/platformio.ini b/platformio.ini index 60c1d9d19..f128ed6ae 100644 --- a/platformio.ini +++ b/platformio.ini @@ -56,14 +56,14 @@ extra_configs = arduino_core_2_6_3 = espressif8266@2.3.3 arduino_core_2_7_4 = espressif8266@2.6.2 arduino_core_3_0_0 = espressif8266@3.0.0 -arduino_core_3_0_2 = espressif8266@3.2.0 +arduino_core_3_2_0 = espressif8266@3.2.0 # Development platforms arduino_core_develop = https://github.com/platformio/platform-espressif8266#develop arduino_core_git = https://github.com/platformio/platform-espressif8266#feature/stage # Platform to use for ESP8266 -platform_wled_default = ${common.arduino_core_2_7_4} +platform_wled_default = ${common.arduino_core_3_2_0} # We use 2.7.4.7 for all, includes PWM flicker fix and Wstring optimization platform_packages = tasmota/framework-arduinoespressif8266 @ 3.20704.7 platformio/toolchain-xtensa @ ~2.40802.200502 @@ -163,11 +163,12 @@ lib_compat_mode = strict lib_deps = fastled/FastLED @ 3.5.0 IRremoteESP8266 @ 2.8.2 - https://github.com/Aircoookie/ESPAsyncWebServer.git @ ~2.0.4 + https://github.com/Aircoookie/ESPAsyncWebServer.git @ ~2.0.7 #For use of the TTGO T-Display ESP32 Module with integrated TFT display uncomment the following line #TFT_eSPI #For use SSD1306 OLED display uncomment following - #U8g2@~2.27.2 + #U8g2@~2.28.8 + #U8g2@~2.32.10 #For Dallas sensor uncomment following 2 lines #OneWire@~2.3.5 #milesburton/DallasTemperature@^3.9.0 @@ -184,8 +185,8 @@ build_flags = -DESP8266 -DFP_IN_IROM ;-Wno-deprecated-declarations - -Wno-register - -Wno-misleading-indentation + ;-Wno-register + ;-Wno-misleading-indentation ; NONOSDK22x_190703 = 2.2.2-dev(38a443e) -DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK22x_190703 ; lwIP 2 - Higher Bandwidth no Features @@ -251,6 +252,23 @@ lib_deps = makuna/NeoPixelBus @ 2.6.9 https://github.com/pbolduc/AsyncTCP.git @ 1.2.0 +[esp32s3] +;; generic definitions for all ESP32-S3 boards +build_flags = -g + -DESP32 + -DARDUINO_ARCH_ESP32 + -DARDUINO_ARCH_ESP32S3 + -DCONFIG_IDF_TARGET_ESP32S3 + -D CONFIG_ASYNC_TCP_USE_WDT=0 + -DCO + +lib_deps = + ${env.lib_deps} + ;; currently we need the latest NeoPixelBus dev version, because it contains important bugfixes for -S3 + https://github.com/Makuna/NeoPixelBus.git#master @ 2.7.0 + https://github.com/pbolduc/AsyncTCP.git @ 1.2.0 + + # ------------------------------------------------------------------------------ # WLED BUILDS # ------------------------------------------------------------------------------ @@ -263,6 +281,7 @@ board_build.ldscript = ${common.ldscript_4m1m} build_unflags = ${common.build_unflags} build_flags = ${common.build_flags_esp8266} -D WLED_RELEASE_NAME=ESP8266 lib_deps = ${esp8266.lib_deps} +monitor_filters = esp8266_exception_decoder [env:esp8266_2m] board = esp_wroom_02 @@ -364,15 +383,30 @@ build_unflags = ${common.build_unflags} lib_deps = ${esp32s2.lib_deps} [env:esp32c3] -board = esp32-c3-devkitm-1 -platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.2.2/platform-tasmota-espressif32-2.0.2.zip -platform_packages = +platform = espressif32@5.1.1 framework = arduino +board = esp32-c3-devkitm-1 board_build.partitions = tools/WLED_ESP32_4MB_1MB_FS.csv +build_flags = ${common.build_flags} ${esp32c3.build_flags} #-D WLED_RELEASE_NAME=ESP32-C3 + -D WLED_WATCHDOG_TIMEOUT=0 upload_speed = 460800 build_unflags = ${common.build_unflags} lib_deps = ${esp32c3.lib_deps} +[env:esp32s3dev_8MB] +;; ESP32-S3-DevKitC-1 development board, with 8MB FLASH, no PSRAM +board = esp32-s3-devkitc-1 +platform = espressif32@5.1.1 +platform_packages = platformio/framework-arduinoespressif32@3.20004.220825 +upload_speed = 921600 +build_unflags = ${common.build_unflags} +build_flags = ${common.build_flags} ${esp32s3.build_flags} -D CONFIG_LITTLEFS_FOR_IDF_3_2 -D WLED_WATCHDOG_TIMEOUT=0 -D ARDUINO_USB_MODE=1 -D ARDUINO_USB_CDC_ON_BOOT=0 -D ARDUINO_USB_MSC_ON_BOOT=0 +lib_deps = ${esp32s3.lib_deps} +board_build.partitions = tools/WLED_ESP32_8MB.csv +board_build.f_flash = 80000000L +board_build.flash_mode = qio +monitor_filters = esp32_exception_decoder + [env:esp8285_4CH_MagicHome] board = esp8285 platform = ${common.platform_wled_default} @@ -435,6 +469,29 @@ build_unflags = ${common.build_unflags} build_flags = ${common.build_flags_esp8266} -D LEDPIN=12 -D IRPIN=-1 -D RLYPIN=2 lib_deps = ${esp8266.lib_deps} +[env:lolin_s2_mini] +platform = espressif32@5.1.1 +board = lolin_s2_mini +board_build.partitions = tools/WLED_ESP32_4MB_1MB_FS.csv +build_unflags = ${common.build_unflags} +build_flags = ${common.build_flags} ${esp32s2.build_flags} #-D WLED_RELEASE_NAME=LolinS2 + -DBOARD_HAS_PSRAM + -D ARDUINO_USB_CDC_ON_BOOT + -D WLED_USE_PSRAM + -D WLED_WATCHDOG_TIMEOUT=0 + -D CONFIG_ASYNC_TCP_USE_WDT=0 + -D LEDPIN=16 + -D BTNPIN=18 + -D RLYPIN=9 + -D IRPIN=7 + -D HW_PIN_SCL=35 + -D HW_PIN_SDA=33 + -D HW_PIN_CLOCKSPI=7 + -D HW_PIN_DATASPI=11 + -D HW_PIN_MISOSPI=9 +; -D STATUSLED=15 +lib_deps = ${esp32s2.lib_deps} + # ------------------------------------------------------------------------------ # custom board configurations # ------------------------------------------------------------------------------ diff --git a/tools/WLED_ESP32-wrover_4MB.csv b/tools/WLED_ESP32-wrover_4MB.csv new file mode 100644 index 000000000..a179a89d0 --- /dev/null +++ b/tools/WLED_ESP32-wrover_4MB.csv @@ -0,0 +1,6 @@ +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x5000, +otadata, data, ota, 0xe000, 0x2000, +app0, app, ota_0, 0x10000, 0x180000, +app1, app, ota_1, 0x190000,0x180000, +spiffs, data, spiffs, 0x310000,0xF0000, diff --git a/tools/cdata.js b/tools/cdata.js index 15455a428..d01c3e35f 100644 --- a/tools/cdata.js +++ b/tools/cdata.js @@ -16,20 +16,31 @@ */ const fs = require("fs"); +const inliner = require("inliner"); +const zlib = require("zlib"); +const CleanCSS = require("clean-css"); +const MinifyHTML = require("html-minifier-terser").minify; const packageJson = require("../package.json"); /** * */ -function hexdump(buffer) { +function hexdump(buffer,isHex=false) { let lines = []; - for (let i = 0; i < buffer.length; i += 16) { - let block = buffer.slice(i, i + 16); // cut buffer into blocks of 16 + for (let i = 0; i < buffer.length; i +=(isHex?32:16)) { + var block; let hexArray = []; - - for (let value of block) { - hexArray.push("0x" + value.toString(16).padStart(2, "0")); + if (isHex) { + block = buffer.slice(i, i + 32) + for (let j = 0; j < block.length; j +=2 ) { + hexArray.push("0x" + block.slice(j,j+2)) + } + } else { + block = buffer.slice(i, i + 16); // cut buffer into blocks of 16 + for (let value of block) { + hexArray.push("0x" + value.toString(16).padStart(2, "0")); + } } let hexString = hexArray.join(", "); @@ -40,9 +51,6 @@ function hexdump(buffer) { return lines.join(",\n"); } -const inliner = require("inliner"); -const zlib = require("zlib"); - function strReplace(str, search, replacement) { return str.split(search).join(replacement); } @@ -56,16 +64,52 @@ function adoptVersionAndRepo(html) { html = strReplace(html, "https://github.com/atuline/WLED", repoUrl); html = strReplace(html, "https://github.com/Aircoookie/WLED", repoUrl); } - let version = packageJson.version; if (version) { html = strReplace(html, "##VERSION##", version); } - return html; } -function writeHtmlGzipped(sourceFile, resultFile) { +function filter(str, type) { + str = adoptVersionAndRepo(str); + if (type === undefined) { + return str; + } else if (type == "css-minify") { + return new CleanCSS({}).minify(str).styles; + } else if (type == "js-minify") { + return MinifyHTML('', { + collapseWhitespace: true, + minifyJS: true, + continueOnParseError: false, + removeComments: true, + }).replace(/<[\/]*script>/g,''); + } else if (type == "html-minify") { + return MinifyHTML(str, { + collapseWhitespace: true, + maxLineLength: 80, + minifyCSS: true, + minifyJS: true, + continueOnParseError: false, + removeComments: true, + }); + } else if (type == "html-minify-ui") { + return MinifyHTML(str, { + collapseWhitespace: true, + conservativeCollapse: true, + maxLineLength: 80, + minifyCSS: true, + minifyJS: true, + continueOnParseError: false, + removeComments: true, + }); + } else { + console.warn("Unknown filter: " + type); + return str; + } +} + +function writeHtmlGzipped(sourceFile, resultFile, page) { console.info("Reading " + sourceFile); new inliner(sourceFile, function (error, html) { console.info("Inlined " + html.length + " characters"); @@ -95,8 +139,8 @@ function writeHtmlGzipped(sourceFile, resultFile) { */ // Autogenerated from ${sourceFile}, do not edit!! -const uint16_t PAGE_index_L = ${result.length}; -const uint8_t PAGE_index[] PROGMEM = { +const uint16_t PAGE_${page}_L = ${result.length}; +const uint8_t PAGE_${page}[] PROGMEM = { ${array} }; `; @@ -106,41 +150,6 @@ ${array} }); } -const CleanCSS = require("clean-css"); -const MinifyHTML = require("html-minifier-terser").minify; - -function filter(str, type) { - str = adoptVersionAndRepo(str); - - if (type === undefined) { - return str; - } else if (type == "css-minify") { - return new CleanCSS({}).minify(str).styles; - } else if (type == "html-minify") { - return MinifyHTML(str, { - collapseWhitespace: true, - maxLineLength: 80, - minifyCSS: true, - minifyJS: true, - continueOnParseError: false, - removeComments: true, - }); - } else if (type == "html-minify-ui") { - return MinifyHTML(str, { - collapseWhitespace: true, - conservativeCollapse: true, - maxLineLength: 80, - minifyCSS: true, - minifyJS: true, - continueOnParseError: false, - removeComments: true, - }); - } else { - console.warn("Unknown filter: " + type); - return str; - } -} - function specToChunk(srcDir, s) { if (s.method == "plaintext") { const buf = fs.readFileSync(srcDir + "/" + s.file); @@ -153,6 +162,21 @@ const char ${s.name}[] PROGMEM = R"${s.prepend || ""}${filter(str, s.filter)}${ `; return s.mangle ? s.mangle(chunk) : chunk; + } else if (s.method == "gzip") { + const buf = fs.readFileSync(srcDir + "/" + s.file); + var str = buf.toString('utf-8'); + if (s.mangle) str = s.mangle(str); + const zip = zlib.gzipSync(filter(str, s.filter), { level: zlib.constants.Z_BEST_COMPRESSION }); + const result = hexdump(zip.toString('hex'), true); + const chunk = ` +// Autogenerated from ${srcDir}/${s.file}, do not edit!! +const uint16_t ${s.name}_length = ${zip.length}; +const uint8_t ${s.name}[] PROGMEM = { +${result} +}; + +`; + return chunk; } else if (s.method == "binary") { const buf = fs.readFileSync(srcDir + "/" + s.file); const result = hexdump(buf); @@ -164,7 +188,7 @@ ${result} }; `; - return s.mangle ? s.mangle(chunk) : chunk; + return chunk; } else { console.warn("Unknown method: " + s.method); return undefined; @@ -194,160 +218,111 @@ function writeChunks(srcDir, specs, resultFile) { fs.writeFileSync(resultFile, src); } -writeHtmlGzipped("wled00/data/index.htm", "wled00/html_ui.h"); - +writeHtmlGzipped("wled00/data/index.htm", "wled00/html_ui.h", 'index'); +writeHtmlGzipped("wled00/data/simple.htm", "wled00/html_simple.h", 'simple'); +/* +writeChunks( + "wled00/data", + [ + { + file: "simple.css", + name: "PAGE_simpleCss", + method: "gzip", + filter: "css-minify", + }, + { + file: "simple.js", + name: "PAGE_simpleJs", + method: "gzip", + filter: "js-minify", + }, + { + file: "simple.htm", + name: "PAGE_simple", + method: "gzip", + filter: "html-minify-ui", + } + ], + "wled00/html_simplex.h" +); +*/ writeChunks( "wled00/data", [ { file: "style.css", name: "PAGE_settingsCss", - prepend: "=====()=====", - method: "plaintext", + method: "gzip", filter: "css-minify", + mangle: (str) => + str + .replace("%%","%") }, { file: "settings.htm", name: "PAGE_settings", - prepend: "=====(", - append: ")=====", - method: "plaintext", + method: "gzip", filter: "html-minify", - mangle: (str) => - str - .replace("%", "%%") - .replace(/Usermods\<\/button\>\<\/form\>/gms, "Usermods\<\/button\>\<\/form\>%DMXMENU%"), }, { file: "settings_wifi.htm", name: "PAGE_settings_wifi", - prepend: "=====(", - append: ")=====", - method: "plaintext", + method: "gzip", filter: "html-minify", - mangle: (str) => - str - .replace(/\/gms, "") - .replace(/\.*\<\/style\>/gms, "%CSS%%SCSS%") - .replace( - /function GetV().*\<\/script\>/gms, - "function GetV() {var d=document;\n" - ), }, { file: "settings_leds.htm", name: "PAGE_settings_leds", - prepend: "=====(", - append: ")=====", - method: "plaintext", + method: "gzip", filter: "html-minify", - mangle: (str) => - str - .replace(/\/gms, "") - .replace(/\.*\<\/style\>/gms, "%CSS%%SCSS%") - .replace( - /function GetV().*\<\/script\>/gms, - "function GetV() {var d=document;\n" - ), }, { file: "settings_dmx.htm", name: "PAGE_settings_dmx", - prepend: "=====(", - append: ")=====", - method: "plaintext", + method: "gzip", filter: "html-minify", - mangle: (str) => { - const nocss = str - .replace(/\/gms, "") - .replace(/\.*\<\/style\>/gms, "%CSS%%SCSS%") - .replace( - /function GetV().*\<\/script\>/gms, - "function GetV() {var d=document;\n" - ); - return ` -#ifdef WLED_ENABLE_DMX -${nocss} -#else -const char PAGE_settings_dmx[] PROGMEM = R"=====()====="; -#endif -`; - }, }, { file: "settings_ui.htm", name: "PAGE_settings_ui", - prepend: "=====(", - append: ")=====", - method: "plaintext", + method: "gzip", filter: "html-minify", - mangle: (str) => - str - .replace(/\/gms, "") - .replace(/\.*\<\/style\>/gms, "%CSS%%SCSS%") - .replace( - /function GetV().*\<\/script\>/gms, - "function GetV() {var d=document;\n" - ), }, { file: "settings_sync.htm", name: "PAGE_settings_sync", - prepend: "=====(", - append: ")=====", - method: "plaintext", + method: "gzip", filter: "html-minify", - mangle: (str) => - str - .replace(/\/gms, "") - .replace(/\.*\<\/style\>/gms, "%CSS%%SCSS%") - .replace(/function GetV().*\<\/script\>/gms, "function GetV() {\n"), }, { file: "settings_time.htm", name: "PAGE_settings_time", - prepend: "=====(", - append: ")=====", - method: "plaintext", + method: "gzip", filter: "html-minify", - mangle: (str) => - str - .replace(/\/gms, "") - .replace(/\.*\<\/style\>/gms, "%CSS%%SCSS%") - .replace(/function GetV().*\<\/script\>/gms, "function GetV() {\n"), }, { file: "settings_sec.htm", name: "PAGE_settings_sec", - prepend: "=====(", - append: ")=====", - method: "plaintext", + method: "gzip", filter: "html-minify", - mangle: (str) => - str - .replace(/\/gms, "") - .replace(/\.*\<\/style\>/gms, "%CSS%%SCSS%") - .replace( - /function GetV().*\<\/script\>/gms, - "function GetV() {var d=document;\n" - ), }, { file: "settings_um.htm", name: "PAGE_settings_um", - prepend: "=====(", - append: ")=====", - method: "plaintext", + method: "gzip", filter: "html-minify", - mangle: (str) => - str - .replace(/\/gms, "") - .replace(/\.*\<\/style\>/gms, "%CSS%%SCSS%") - .replace( - /function GetV().*\<\/script\>/gms, - "function GetV() {var d=document;\n" - ), + }, + { + file: "settings_2D.htm", + name: "PAGE_settings_2D", + method: "gzip", + filter: "html-minify", + }, + { + file: "settings_pin.htm", + name: "PAGE_settings_pin", + method: "gzip", + filter: "html-minify" } ], "wled00/html_settings.h" @@ -359,9 +334,7 @@ writeChunks( { file: "usermod.htm", name: "PAGE_usermod", - prepend: "=====(", - append: ")=====", - method: "plaintext", + method: "gzip", filter: "html-minify", mangle: (str) => str.replace(/fetch\("http\:\/\/.*\/win/gms, 'fetch("/win'), @@ -393,41 +366,43 @@ const char PAGE_dmxmap[] PROGMEM = R"=====()====="; { file: "update.htm", name: "PAGE_update", - prepend: "=====(", - append: ")=====", - method: "plaintext", + method: "gzip", filter: "html-minify", + mangle: (str) => + str + .replace( + /function GetV().*\<\/script\>/gms, + "" + ) }, { file: "welcome.htm", name: "PAGE_welcome", - prepend: "=====(", - append: ")=====", - method: "plaintext", + method: "gzip", filter: "html-minify", }, { file: "liveview.htm", name: "PAGE_liveview", - prepend: "=====(", - append: ")=====", - method: "plaintext", + method: "gzip", filter: "html-minify", }, { file: "liveviewws.htm", name: "PAGE_liveviewws", - prepend: "=====(", - append: ")=====", - method: "plaintext", + method: "gzip", + filter: "html-minify", + }, + { + file: "liveviewws2D.htm", + name: "PAGE_liveviewws2D", + method: "gzip", filter: "html-minify", }, { file: "404.htm", name: "PAGE_404", - prepend: "=====(", - append: ")=====", - method: "plaintext", + method: "gzip", filter: "html-minify", }, { @@ -435,6 +410,16 @@ const char PAGE_dmxmap[] PROGMEM = R"=====()====="; name: "favicon", method: "binary", }, + { + file: "iro.js", + name: "iroJs", + method: "gzip" + }, + { + file: "rangetouch.js", + name: "rangetouchJs", + method: "gzip" + } ], "wled00/html_other.h" ); diff --git a/usermods/Animated_Staircase/Animated_Staircase.h b/usermods/Animated_Staircase/Animated_Staircase.h index 35ea0d7a4..427348d8e 100644 --- a/usermods/Animated_Staircase/Animated_Staircase.h +++ b/usermods/Animated_Staircase/Animated_Staircase.h @@ -103,25 +103,24 @@ class Animated_Staircase : public Usermod { void updateSegments() { mainSegmentId = strip.getMainSegmentId(); - WS2812FX::Segment* segments = strip.getSegments(); - for (int i = 0; i < MAX_NUM_SEGMENTS; i++, segments++) { - if (!segments->isActive()) { + for (int i = 0; i < strip.getSegmentsNum(); i++) { + Segment &seg = strip.getSegment(i); + if (!seg.isActive()) { maxSegmentId = i - 1; break; } - if (i >= onIndex && i < offIndex) { - segments->setOption(SEG_OPTION_ON, 1, i); + seg.setOption(SEG_OPTION_ON, true); // We may need to copy mode and colors from segment 0 to make sure // changes are propagated even when the config is changed during a wipe - // segments->mode = mainsegment.mode; - // segments->colors[0] = mainsegment.colors[0]; + // seg.setMode(mainsegment.mode); + // seg.setColor(0, mainsegment.colors[0]); } else { - segments->setOption(SEG_OPTION_ON, 0, i); + seg.setOption(SEG_OPTION_ON, false); } // Always mark segments as "transitional", we are animating the staircase - segments->setOption(SEG_OPTION_TRANSITIONAL, 1, i); + seg.setOption(SEG_OPTION_TRANSITIONAL, true); } colorUpdated(CALL_MODE_DIRECT_CHANGE); } @@ -290,13 +289,13 @@ class Animated_Staircase : public Usermod { } } else { // Restore segment options - WS2812FX::Segment* segments = strip.getSegments(); - for (int i = 0; i < MAX_NUM_SEGMENTS; i++, segments++) { - if (!segments->isActive()) { + for (int i = 0; i < strip.getSegmentsNum(); i++) { + Segment &seg = strip.getSegment(i); + if (!seg.isActive()) { maxSegmentId = i - 1; break; } - segments->setOption(SEG_OPTION_ON, 1, i); + seg.setOption(SEG_OPTION_ON, true); } colorUpdated(CALL_MODE_DIRECT_CHANGE); DEBUG_PRINTLN(F("Animated Staircase disabled.")); @@ -406,6 +405,14 @@ class Animated_Staircase : public Usermod { } } + void appendConfigData() { + //oappend(SET_F("dd=addDropdown('staircase','selectfield');")); + //oappend(SET_F("addOption(dd,'1st value',0);")); + //oappend(SET_F("addOption(dd,'2nd value',1);")); + //oappend(SET_F("addInfo('staircase:selectfield',1,'additional info');")); // 0 is field type, 1 is actual field + } + + /* * Writes the configuration to internal flash memory. */ @@ -500,22 +507,22 @@ class Animated_Staircase : public Usermod { * tab of the web-UI. */ void addToJsonInfo(JsonObject& root) { - JsonObject staircase = root["u"]; - if (staircase.isNull()) { - staircase = root.createNestedObject("u"); + JsonObject user = root["u"]; + if (user.isNull()) { + user = root.createNestedObject("u"); } - JsonArray usermodEnabled = staircase.createNestedArray(F("Staircase")); // name - String btn = F(""); - usermodEnabled.add(btn); // value + JsonArray infoArr = user.createNestedArray(FPSTR(_name)); // name + + String uiDomString = F(""); + infoArr.add(uiDomString); } }; diff --git a/usermods/BME280_v2/usermod_bme280.h b/usermods/BME280_v2/usermod_bme280.h index 742066f7b..6b42fc808 100644 --- a/usermods/BME280_v2/usermod_bme280.h +++ b/usermods/BME280_v2/usermod_bme280.h @@ -26,15 +26,10 @@ private: bool HomeAssistantDiscovery = false; // Publish Home Assistant Device Information // set the default pins based on the architecture, these get overridden by Usermod menu settings - #ifdef ARDUINO_ARCH_ESP32 // ESP32 boards - #define HW_PIN_SCL 22 - #define HW_PIN_SDA 21 - #else // ESP8266 boards - #define HW_PIN_SCL 5 - #define HW_PIN_SDA 4 + #ifdef ESP8266 //uint8_t RST_PIN = 16; // Uncoment for Heltec WiFi-Kit-8 #endif - int8_t ioPin[2] = {HW_PIN_SCL, HW_PIN_SDA}; // I2C pins: SCL, SDA...defaults to Arch hardware pins but overridden at setup() + int8_t ioPin[2] = {i2c_scl, i2c_sda}; // I2C pins: SCL, SDA...defaults to Arch hardware pins but overridden at setup() bool initDone = false; // BME280 sensor settings @@ -177,7 +172,7 @@ private: public: void setup() { - bool HW_Pins_Used = (ioPin[0]==HW_PIN_SCL && ioPin[1]==HW_PIN_SDA); // note whether architecture-based hardware SCL/SDA pins used + bool HW_Pins_Used = (ioPin[0]==i2c_scl && ioPin[1]==i2c_sda); // note whether architecture-based hardware SCL/SDA pins used PinOwner po = PinOwner::UM_BME280; // defaults to being pinowner for SCL/SDA pins PinManagerPinType pins[2] = { { ioPin[0], true }, { ioPin[1], true } }; // allocate pins if (HW_Pins_Used) po = PinOwner::HW_I2C; // allow multiple allocations of HW I2C bus pins @@ -444,7 +439,7 @@ public: for (byte i=0; i<2; i++) if (ioPin[i] != newPin[i]) { pinsChanged = true; break; } // check if any pins changed if (pinsChanged) { //if pins changed, deallocate old pins and allocate new ones PinOwner po = PinOwner::UM_BME280; - if (ioPin[0]==HW_PIN_SCL && ioPin[1]==HW_PIN_SDA) po = PinOwner::HW_I2C; // allow multiple allocations of HW I2C bus pins + if (ioPin[0]==i2c_scl && ioPin[1]==i2c_sda) po = PinOwner::HW_I2C; // allow multiple allocations of HW I2C bus pins pinManager.deallocateMultiplePins((const uint8_t *)ioPin, 2, po); // deallocate pins for (byte i=0; i<2; i++) ioPin[i] = newPin[i]; setup(); diff --git a/usermods/Cronixie/usermod_cronixie.h b/usermods/Cronixie/usermod_cronixie.h index 5e4255f43..5702d8fa8 100644 --- a/usermods/Cronixie/usermod_cronixie.h +++ b/usermods/Cronixie/usermod_cronixie.h @@ -249,7 +249,7 @@ class UsermodCronixie : public Usermod { if (backlight && _digitOut[i] <11) { - uint32_t col = strip.gamma32(strip.getSegment(0).colors[1]); + uint32_t col = gamma32(strip.getSegment(0).colors[1]); for (uint16_t j=o; j< o+10; j++) { if (j != excl) strip.setPixelColor(j, col); } diff --git a/usermods/EleksTube_IPS/TFTs.h b/usermods/EleksTube_IPS/TFTs.h index 4c6bd9cb8..e614704f5 100644 --- a/usermods/EleksTube_IPS/TFTs.h +++ b/usermods/EleksTube_IPS/TFTs.h @@ -75,7 +75,7 @@ private: uint8_t lineBuffer[w * 2]; - if (!realtimeMode || realtimeOverride) strip.service(); + if (!realtimeMode || realtimeOverride || (realtimeMode && useMainSegmentOnly)) strip.service(); // 0,0 coordinates are top left for (row = 0; row < h; row++) { @@ -169,7 +169,7 @@ private: uint32_t lineSize = ((bitDepth * w +31) >> 5) * 4; uint8_t lineBuffer[lineSize]; - uint8_t serviceStrip = (!realtimeMode || realtimeOverride) ? 7 : 0; + uint8_t serviceStrip = (!realtimeMode || realtimeOverride || (realtimeMode && useMainSegmentOnly)) ? 7 : 0; // row is decremented as the BMP image is drawn bottom up for (row = h-1; row >= 0; row--) { if ((row & 0b00000111) == serviceStrip) strip.service(); //still refresh backlight to mitigate stutter every few rows @@ -250,7 +250,7 @@ private: uint8_t lineBuffer[w * 2]; - if (!realtimeMode || realtimeOverride) strip.service(); + if (!realtimeMode || realtimeOverride || (realtimeMode && useMainSegmentOnly)) strip.service(); // 0,0 coordinates are top left for (row = 0; row < h; row++) { @@ -355,7 +355,7 @@ public: // Color in grayscale bitmaps if Segment 1 exists // TODO If secondary and tertiary are black, color all in primary, // else color first three from Seg 1 color slots and last three from Seg 2 color slots - WS2812FX::Segment& seg1 = strip.getSegment(tubeSegment); + Segment& seg1 = strip.getSegment(tubeSegment); if (seg1.isActive()) { digitColor = strip.getPixelColor(seg1.start + digit); dimming = seg1.opacity; diff --git a/usermods/EleksTube_IPS/usermod_elekstube_ips.h b/usermods/EleksTube_IPS/usermod_elekstube_ips.h index 06c6ecc89..0f7d92e7e 100644 --- a/usermods/EleksTube_IPS/usermod_elekstube_ips.h +++ b/usermods/EleksTube_IPS/usermod_elekstube_ips.h @@ -63,7 +63,7 @@ class ElekstubeIPSUsermod : public Usermod { if (!toki.isTick()) return; updateLocalTime(); - WS2812FX::Segment& seg1 = strip.getSegment(tfts.tubeSegment); + Segment& seg1 = strip.getSegment(tfts.tubeSegment); if (seg1.isActive()) { bool update = false; if (seg1.opacity != lastBri) update = true; diff --git a/usermods/Enclosure_with_OLED_temp_ESP07/usermod.cpp b/usermods/Enclosure_with_OLED_temp_ESP07/usermod.cpp index 36d322fa0..0a2662aac 100644 --- a/usermods/Enclosure_with_OLED_temp_ESP07/usermod.cpp +++ b/usermods/Enclosure_with_OLED_temp_ESP07/usermod.cpp @@ -148,58 +148,14 @@ void userLoop() { // Third row with mode name u8x8.setCursor(2, 2); - uint8_t qComma = 0; - bool insideQuotes = false; - uint8_t printedChars = 0; - char singleJsonSymbol; + char lineBuffer[17]; + extractModeName(knownMode, JSON_mode_names, lineBuffer, 16); + u8x8.print(lineBuffer); - // Find the mode name in JSON - for (size_t i = 0; i < strlen_P(JSON_mode_names); i++) { - singleJsonSymbol = pgm_read_byte_near(JSON_mode_names + i); - switch (singleJsonSymbol) { - case '"': - insideQuotes = !insideQuotes; - break; - case '[': - case ']': - break; - case ',': - qComma++; - default: - if (!insideQuotes || (qComma != knownMode)) - break; - u8x8.print(singleJsonSymbol); - printedChars++; - } - if ((qComma > knownMode) || (printedChars > u8x8.getCols() - 2)) - break; - } // Fourth row with palette name u8x8.setCursor(2, 3); - qComma = 0; - insideQuotes = false; - printedChars = 0; - // Looking for palette name in JSON. - for (size_t i = 0; i < strlen_P(JSON_palette_names); i++) { - singleJsonSymbol = pgm_read_byte_near(JSON_palette_names + i); - switch (singleJsonSymbol) { - case '"': - insideQuotes = !insideQuotes; - break; - case '[': - case ']': - break; - case ',': - qComma++; - default: - if (!insideQuotes || (qComma != knownPalette)) - break; - u8x8.print(singleJsonSymbol); - printedChars++; - } - if ((qComma > knownMode) || (printedChars > u8x8.getCols() - 2)) - break; - } + extractModeName(knownPalette, JSON_palette_names, lineBuffer, 16); + u8x8.print(lineBuffer); u8x8.setFont(u8x8_font_open_iconic_embedded_1x1); u8x8.drawGlyph(0, 0, 80); // wifi icon diff --git a/usermods/Enclosure_with_OLED_temp_ESP07/usermod_bme280.cpp b/usermods/Enclosure_with_OLED_temp_ESP07/usermod_bme280.cpp index 587312832..88d497905 100644 --- a/usermods/Enclosure_with_OLED_temp_ESP07/usermod_bme280.cpp +++ b/usermods/Enclosure_with_OLED_temp_ESP07/usermod_bme280.cpp @@ -191,58 +191,14 @@ void userLoop() { // Third row with mode name u8x8.setCursor(2, 2); - uint8_t qComma = 0; - bool insideQuotes = false; - uint8_t printedChars = 0; - char singleJsonSymbol; + char lineBuffer[17]; + extractModeName(knownMode, JSON_mode_names, lineBuffer, 16); + u8x8.print(lineBuffer); - // Find the mode name in JSON - for (size_t i = 0; i < strlen_P(JSON_mode_names); i++) { - singleJsonSymbol = pgm_read_byte_near(JSON_mode_names + i); - switch (singleJsonSymbol) { - case '"': - insideQuotes = !insideQuotes; - break; - case '[': - case ']': - break; - case ',': - qComma++; - default: - if (!insideQuotes || (qComma != knownMode)) - break; - u8x8.print(singleJsonSymbol); - printedChars++; - } - if ((qComma > knownMode) || (printedChars > u8x8.getCols() - 2)) - break; - } // Fourth row with palette name u8x8.setCursor(2, 3); - qComma = 0; - insideQuotes = false; - printedChars = 0; - // Looking for palette name in JSON. - for (size_t i = 0; i < strlen_P(JSON_palette_names); i++) { - singleJsonSymbol = pgm_read_byte_near(JSON_palette_names + i); - switch (singleJsonSymbol) { - case '"': - insideQuotes = !insideQuotes; - break; - case '[': - case ']': - break; - case ',': - qComma++; - default: - if (!insideQuotes || (qComma != knownPalette)) - break; - u8x8.print(singleJsonSymbol); - printedChars++; - } - if ((qComma > knownMode) || (printedChars > u8x8.getCols() - 2)) - break; - } + extractModeName(knownPalette, JSON_palette_names, lineBuffer, 16); + u8x8.print(lineBuffer); u8x8.setFont(u8x8_font_open_iconic_embedded_1x1); u8x8.drawGlyph(0, 0, 80); // wifi icon diff --git a/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h b/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h index 8085d79a9..d7abb8476 100644 --- a/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h +++ b/usermods/PIR_sensor_switch/usermod_PIR_sensor_switch.h @@ -58,7 +58,6 @@ private: byte prevPreset = 0; byte prevPlaylist = 0; - bool savedState = false; uint32_t offTimerStart = 0; // off timer start time byte NotifyUpdateMode = CALL_MODE_NO_NOTIFY; // notification mode for stateUpdated(): CALL_MODE_NO_NOTIFY or CALL_MODE_DIRECT_CHANGE @@ -77,6 +76,7 @@ private: bool m_mqttOnly = false; // flag to send MQTT message only (assuming it is enabled) // flag to enable triggering only if WLED is initially off (LEDs are not on, preventing running effect being overwritten by PIR) bool m_offOnly = false; + bool m_offMode = offMode; // strings to reduce flash memory usage (used more than twice) static const char _name[]; @@ -118,17 +118,20 @@ private: */ void switchStrip(bool switchOn) { - if (m_offOnly && bri && (switchOn || (!PIRtriggered && !switchOn))) return; + if (m_offOnly && bri && (switchOn || (!PIRtriggered && !switchOn))) return; //if lights on and off only, do nothing + if (PIRtriggered && switchOn) return; //if already on and triggered before, do nothing PIRtriggered = switchOn; if (switchOn) { if (m_onPreset) { - if (currentPlaylist>0) prevPlaylist = currentPlaylist; - else if (currentPreset>0) prevPreset = currentPreset; - else { + if (currentPlaylist>0 && !offMode) { + prevPlaylist = currentPlaylist; + unloadPlaylist(); + } else if (currentPreset>0 && !offMode) { + prevPreset = currentPreset; + } else { saveTemporaryPreset(); - savedState = true; prevPlaylist = 0; - prevPreset = 0; + prevPreset = 255; } applyPreset(m_onPreset, NotifyUpdateMode); return; @@ -140,20 +143,17 @@ private: } } else { if (m_offPreset) { - applyPreset(m_offPreset, NotifyUpdateMode); + if (currentPreset==m_onPreset || currentPlaylist==m_onPreset) applyPreset(m_offPreset, NotifyUpdateMode); return; } else if (prevPlaylist) { - applyPreset(prevPlaylist, NotifyUpdateMode); + if (currentPreset==m_onPreset || currentPlaylist==m_onPreset) applyPreset(prevPlaylist, NotifyUpdateMode); prevPlaylist = 0; return; } else if (prevPreset) { - applyPreset(prevPreset, NotifyUpdateMode); + if (prevPreset<255) { if (currentPreset==m_onPreset || currentPlaylist==m_onPreset) applyPreset(prevPreset, NotifyUpdateMode); } + else { if (currentPreset==m_onPreset || currentPlaylist==m_onPreset) applyTemporaryPreset(); } prevPreset = 0; return; - } else if (savedState) { - applyTemporaryPreset(); - savedState = false; - return; } // preset not assigned if (bri != 0) { @@ -188,10 +188,12 @@ private: if (sensorPinState == HIGH) { offTimerStart = 0; if (!m_mqttOnly && (!m_nightTimeOnly || (m_nightTimeOnly && !isDayTime()))) switchStrip(true); + else if (NotifyUpdateMode != CALL_MODE_NO_NOTIFY) updateInterfaces(CALL_MODE_WS_SEND); publishMqtt("on"); - } else /*if (bri != 0)*/ { + } else { // start switch off timer offTimerStart = millis(); + if (NotifyUpdateMode != CALL_MODE_NO_NOTIFY) updateInterfaces(CALL_MODE_WS_SEND); } return true; } @@ -203,14 +205,13 @@ private: */ bool handleOffTimer() { - if (offTimerStart > 0 && millis() - offTimerStart > m_switchOffDelay) - { - if (enabled == true) - { + if (offTimerStart > 0 && millis() - offTimerStart > m_switchOffDelay) { + offTimerStart = 0; + if (enabled == true) { if (!m_mqttOnly && (!m_nightTimeOnly || (m_nightTimeOnly && !isDayTime()))) switchStrip(false); + else if (NotifyUpdateMode != CALL_MODE_NO_NOTIFY) updateInterfaces(CALL_MODE_WS_SEND); publishMqtt("off"); } - offTimerStart = 0; return true; } return false; @@ -274,20 +275,9 @@ public: JsonObject user = root["u"]; if (user.isNull()) user = root.createNestedObject("u"); - String uiDomString = F(""); - JsonArray infoArr = user.createNestedArray(uiDomString); // timer value + JsonArray infoArr = user.createNestedArray(FPSTR(_name)); + String uiDomString; if (enabled) { if (offTimerStart > 0) { @@ -320,6 +310,24 @@ public: } else { infoArr.add(F("disabled")); } + + uiDomString = F(" "); + infoArr.add(uiDomString); + + JsonObject sensor = root[F("sensor")]; + if (sensor.isNull()) sensor = root.createNestedObject(F("sensor")); + sensor[F("motion")] = sensorPinState || offTimerStart>0 ? true : false; } /** diff --git a/usermods/PWM_fan/readme.md b/usermods/PWM_fan/readme.md index 976d6b24c..8cd04a91e 100644 --- a/usermods/PWM_fan/readme.md +++ b/usermods/PWM_fan/readme.md @@ -30,7 +30,16 @@ _NOTE:_ You may also need to tweak Dallas Temperature usermod sampling frequency No special requirements. +## Control PWM fan speed using JSON API + +You can use e.g. `{"PWM-fan":{"speed":30,"lock":true}}` to set fan speed to 30 percent of maximum speed (replace 30 with arbitrary value between 0 and 100) and lock the speed. +If you include `speed` property you can set fan speed in percent (%) of maximum speed. +If you include `lock` property you can lock (_true_) or unlock (_false_) fan speed. +If the fan speed is unlocked it will revert to temperature controlled speed on next update cycle. Once fan speed is locked it will remain so until it is unlocked by next API call. + ## Change Log 2021-10 * First public release +2022-05 +* Added JSON API call to allow changing of speed \ No newline at end of file diff --git a/usermods/PWM_fan/usermod_PWM_fan.h b/usermods/PWM_fan/usermod_PWM_fan.h index 943ba1ae6..153a5f633 100644 --- a/usermods/PWM_fan/usermod_PWM_fan.h +++ b/usermods/PWM_fan/usermod_PWM_fan.h @@ -38,6 +38,7 @@ class PWMFanUsermod : public Usermod { #ifdef ARDUINO_ARCH_ESP32 uint8_t pwmChannel = 255; #endif + bool lockFan = false; #ifdef USERMOD_DALLASTEMPERATURE UsermodTemperature* tempUM; @@ -47,9 +48,10 @@ class PWMFanUsermod : public Usermod { int8_t tachoPin = TACHO_PIN; int8_t pwmPin = PWM_PIN; uint8_t tachoUpdateSec = 30; - float targetTemperature = 25.0; - uint8_t minPWMValuePct = 50; + float targetTemperature = 35.0; + uint8_t minPWMValuePct = 0; uint8_t numberOfInterrupsInOneSingleRotation = 2; // Number of interrupts ESP32 sees on tacho signal on a single fan rotation. All the fans I've seen trigger two interrups. + uint8_t pwmValuePct = 0; // strings to reduce flash memory usage (used more than twice) static const char _name[]; @@ -60,6 +62,8 @@ class PWMFanUsermod : public Usermod { static const char _tachoUpdateSec[]; static const char _minPWMValuePct[]; static const char _IRQperRotation[]; + static const char _speed[]; + static const char _lock[]; void initTacho(void) { if (tachoPin < 0 || !pinManager.allocatePin(tachoPin, false, PinOwner::UM_Unspecified)){ @@ -80,6 +84,8 @@ class PWMFanUsermod : public Usermod { } void updateTacho(void) { + // store milliseconds when tacho was measured the last time + msLastTachoMeasurement = millis(); if (tachoPin < 0) return; // start of tacho measurement @@ -90,8 +96,6 @@ class PWMFanUsermod : public Usermod { last_rpm /= tachoUpdateSec; // reset counter counter_rpm = 0; - // store milliseconds when tacho was measured the last time - msLastTachoMeasurement = millis(); // attach interrupt again attachInterrupt(digitalPinToInterrupt(tachoPin), rpm_fan, FALLING); } @@ -99,6 +103,7 @@ class PWMFanUsermod : public Usermod { // https://randomnerdtutorials.com/esp32-pwm-arduino-ide/ void initPWMfan(void) { if (pwmPin < 0 || !pinManager.allocatePin(pwmPin, true, PinOwner::UM_Unspecified)) { + enabled = false; pwmPin = -1; return; } @@ -130,7 +135,7 @@ class PWMFanUsermod : public Usermod { } void updateFanSpeed(uint8_t pwmValue){ - if (pwmPin < 0) return; + if (!enabled || pwmPin < 0) return; #ifdef ESP8266 analogWrite(pwmPin, pwmValue); @@ -155,7 +160,7 @@ class PWMFanUsermod : public Usermod { int pwmStep = ((100 - minPWMValuePct) * newPWMvalue) / (7*100); int pwmMinimumValue = (minPWMValuePct * newPWMvalue) / 100; - if ((temp == NAN) || (temp <= 0.0)) { + if ((temp == NAN) || (temp <= -100.0)) { DEBUG_PRINTLN(F("WARNING: no temperature value available. Cannot do temperature control. Will set PWM fan to 255.")); } else if (difftemp <= 0.0) { // Temperature is below target temperature. Run fan at minimum speed. @@ -205,7 +210,7 @@ class PWMFanUsermod : public Usermod { if ((now - msLastTachoMeasurement) < (tachoUpdateSec * 1000)) return; updateTacho(); - setFanPWMbasedOnTemperature(); + if (!lockFan) setFanPWMbasedOnTemperature(); } /* @@ -214,12 +219,41 @@ class PWMFanUsermod : public Usermod { * Below it is shown how this could be used for e.g. a light sensor */ void addToJsonInfo(JsonObject& root) { - if (tachoPin < 0) return; JsonObject user = root["u"]; if (user.isNull()) user = root.createNestedObject("u"); - JsonArray data = user.createNestedArray(FPSTR(_name)); - data.add(last_rpm); - data.add(F("rpm")); + + JsonArray infoArr = user.createNestedArray(FPSTR(_name)); + String uiDomString = F(""); + infoArr.add(uiDomString); + + if (enabled) { + JsonArray infoArr = user.createNestedArray(F("Manual")); + String uiDomString = F("
"); // + infoArr.add(uiDomString); + + JsonArray data = user.createNestedArray(F("Speed")); + if (tachoPin >= 0) { + data.add(last_rpm); + data.add(F("rpm")); + } else { + if (lockFan) data.add(F("locked")); + else data.add(F("auto")); + } + } } /* @@ -233,9 +267,24 @@ class PWMFanUsermod : public Usermod { * readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object). * Values in the state object may be modified by connected clients */ - //void readFromJsonState(JsonObject& root) { - // if (!initDone) return; // prevent crash on boot applyPreset() - //} + void readFromJsonState(JsonObject& root) { + if (!initDone) return; // prevent crash on boot applyPreset() + JsonObject usermod = root[FPSTR(_name)]; + if (!usermod.isNull()) { + if (usermod[FPSTR(_enabled)].is()) { + enabled = usermod[FPSTR(_enabled)].as(); + if (!enabled) updateFanSpeed(0); + } + if (enabled && !usermod[FPSTR(_speed)].isNull() && usermod[FPSTR(_speed)].is()) { + pwmValuePct = usermod[FPSTR(_speed)].as(); + updateFanSpeed((constrain(pwmValuePct,0,100) * 255) / 100); + if (pwmValuePct) lockFan = true; + } + if (enabled && !usermod[FPSTR(_lock)].isNull() && usermod[FPSTR(_lock)].is()) { + lockFan = usermod[FPSTR(_lock)].as(); + } + } + } /* * addToConfig() can be used to add custom persistent settings to the cfg.json file in the "um" (usermod) object. @@ -337,3 +386,5 @@ const char PWMFanUsermod::_temperature[] PROGMEM = "target-temp-C"; const char PWMFanUsermod::_tachoUpdateSec[] PROGMEM = "tacho-update-s"; const char PWMFanUsermod::_minPWMValuePct[] PROGMEM = "min-PWM-percent"; const char PWMFanUsermod::_IRQperRotation[] PROGMEM = "IRQs-per-rotation"; +const char PWMFanUsermod::_speed[] PROGMEM = "speed"; +const char PWMFanUsermod::_lock[] PROGMEM = "lock"; diff --git a/usermods/QuinLED_Dig_Uno_Temp_MQTT/readme.md b/usermods/QuinLED_Dig_Uno_Temp_MQTT/readme.md deleted file mode 100644 index 60fc31f73..000000000 --- a/usermods/QuinLED_Dig_Uno_Temp_MQTT/readme.md +++ /dev/null @@ -1,34 +0,0 @@ -# QuinLED Dig Uno board - -These files allow WLED 0.9.1 to report the temp sensor on the Quinled board to MQTT. I use it to report the board temp to Home Assistant via MQTT, so it will send notifications if something happens and the board start to heat up. -This code uses Aircookie's WLED software. It has a premade file for user modifications. I use it to publish the temperature from the dallas temperature sensor on the Quinled board. The entries for the top of the WLED00 file, initializes the required libraries, and variables for the sensor. The .ino file waits for 60 seconds, and checks to see if the MQTT server is connected (thanks Aircoookie). It then poles the sensor, and published it using the MQTT service already running, using the main topic programmed in the WLED UI. - -Installation of file: Copy and replace file in wled00 directory - -## Project link - -* [QuinLED-Dig-Uno](https://quinled.info/2018/09/15/quinled-dig-uno/) - Project link - -### Platformio requirements - -Uncomment `DallasTemperature@~3.8.0`,`OneWire@~2.3.5 under` `[common]` section in `platformio.ini`: - -```ini -# platformio.ini -... -[platformio] -... -; default_envs = esp07 -default_envs = d1_mini -... -[common] -... -lib_deps_external = - ... - #For use SSD1306 OLED display uncomment following - U8g2@~2.27.3 - #For Dallas sensor uncomment following 2 lines - DallasTemperature@~3.8.0 - OneWire@~2.3.5 -... -``` diff --git a/usermods/QuinLED_Dig_Uno_Temp_MQTT/usermod.cpp b/usermods/QuinLED_Dig_Uno_Temp_MQTT/usermod.cpp deleted file mode 100644 index 5b4e2e5c7..000000000 --- a/usermods/QuinLED_Dig_Uno_Temp_MQTT/usermod.cpp +++ /dev/null @@ -1,54 +0,0 @@ -#include -#include "wled.h" -//Intiating code for QuinLED Dig-Uno temp sensor -//Uncomment Celsius if that is your prefered temperature scale -#include //Dallastemperature sensor -#ifdef ARDUINO_ARCH_ESP32 //ESP32 boards -OneWire oneWire(18); -#else //ESP8266 boards -OneWire oneWire(14); -#endif -DallasTemperature sensor(&oneWire); -long temptimer = millis(); -long lastMeasure = 0; -#define Celsius // Show temperature mesaurement in Celcius otherwise is in Fahrenheit -void userSetup() -{ -// Start the DS18B20 sensor - sensor.begin(); -} - -//gets called every time WiFi is (re-)connected. Initialize own network interfaces here -void userConnected() -{ - -} - -void userLoop() -{ - temptimer = millis(); - -// Timer to publishe new temperature every 60 seconds - if (temptimer - lastMeasure > 60000) { - lastMeasure = temptimer; - -//Check if MQTT Connected, otherwise it will crash the 8266 - if (mqtt != nullptr){ - sensor.requestTemperatures(); - -//Gets prefered temperature scale based on selection in definitions section - #ifdef Celsius - float board_temperature = sensor.getTempCByIndex(0); - #else - float board_temperature = sensors.getTempFByIndex(0); - #endif - -//Create character string populated with user defined device topic from the UI, and the read temperature. Then publish to MQTT server. - char subuf[38]; - strcpy(subuf, mqttDeviceTopic); - strcat(subuf, "/temperature"); - mqtt->publish(subuf, 0, true, String(board_temperature).c_str()); - return;} - return;} -return; -} diff --git a/usermods/RTC/usermod_rtc.h b/usermods/RTC/usermod_rtc.h index 8c174e6fa..fd9a40544 100644 --- a/usermods/RTC/usermod_rtc.h +++ b/usermods/RTC/usermod_rtc.h @@ -3,14 +3,6 @@ #include "src/dependencies/time/DS1307RTC.h" #include "wled.h" -#ifdef ARDUINO_ARCH_ESP32 - #define HW_PIN_SCL 22 - #define HW_PIN_SDA 21 -#else - #define HW_PIN_SCL 5 - #define HW_PIN_SDA 4 -#endif - //Connect DS1307 to standard I2C pins (ESP32: GPIO 21 (SDA)/GPIO 22 (SCL)) class RTCUsermod : public Usermod { @@ -20,8 +12,9 @@ class RTCUsermod : public Usermod { public: void setup() { - PinManagerPinType pins[2] = { { HW_PIN_SCL, true }, { HW_PIN_SDA, true } }; + PinManagerPinType pins[2] = { { i2c_scl, true }, { i2c_sda, true } }; if (!pinManager.allocateMultiplePins(pins, 2, PinOwner::HW_I2C)) { disabled = true; return; } + RTC.begin(); time_t rtcTime = RTC.get(); if (rtcTime) { toki.setTime(rtcTime,TOKI_NO_MS_ACCURACY,TOKI_TS_RTC); @@ -44,13 +37,13 @@ class RTCUsermod : public Usermod { * It will be called by WLED when settings are actually saved (for example, LED settings are saved) * I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings! */ - void addToConfig(JsonObject& root) - { - JsonObject top = root.createNestedObject("RTC"); - JsonArray pins = top.createNestedArray("pin"); - pins.add(HW_PIN_SCL); - pins.add(HW_PIN_SDA); - } +// void addToConfig(JsonObject& root) +// { +// JsonObject top = root.createNestedObject("RTC"); +// JsonArray pins = top.createNestedArray("pin"); +// pins.add(i2c_scl); +// pins.add(i2c_sda); +// } uint16_t getId() { diff --git a/usermods/ST7789_display/README.md b/usermods/ST7789_display/README.md index b98d5be00..930921860 100644 --- a/usermods/ST7789_display/README.md +++ b/usermods/ST7789_display/README.md @@ -2,11 +2,14 @@ This usermod allow to use 240x240 display to display following: +* current date and time; * Network SSID; * IP address; +* WiFi signal strength; * Brightness; * Chosen effect; * Chosen palette; +* effect speed and intensity; * Estimated current in mA; ## Hardware @@ -46,27 +49,29 @@ Add lines to section: default_envs = esp32dev build_flags = ${common.build_flags_esp32} -D USERMOD_ST7789_DISPLAY - + -DUSER_SETUP_LOADED=1 + -DST7789_DRIVER=1 + -DTFT_WIDTH=240 + -DTFT_HEIGHT=240 + -DCGRAM_OFFSET=1 + -DTFT_MOSI=21 + -DTFT_SCLK=22 + -DTFT_DC=27 + -DTFT_RST=26 + -DTFT_BL=14 + -DLOAD_GLCD=1 + ;optional for WROVER + ;-DCONFIG_SPIRAM_SUPPORT=1 ``` Save the `platformio.ini` file. Once this is saved, the required library files should be automatically downloaded for modifications in a later step. ### TFT_eSPI Library Adjustments -We need to modify a file in the `TFT_eSPI` library. If you followed the directions to modify and save the `platformio.ini` file above, the `User_Setup_Select.h` file can be found in the `/.pio/libdeps/esp32dev/TFT_eSPI` folder. +If you are not using PlatformIO you need to modify a file in the `TFT_eSPI` library. If you followed the directions to modify and save the `platformio.ini` file above, the `Setup24_ST7789.h` file can be found in the `/.pio/libdeps/esp32dev/TFT_eSPI/User_Setups/` folder. -Modify the `User_Setup_Select.h` file as follows: +Edit `Setup_ST7789.h` file and uncomment nad changep GPIO pin numbers in lines containing `TFT_MOSI`, `TFT_SCLK`, `TFT_RST`, `TFT_DC`. -* Comment out the following line (which is the 'default' setup file): +Modify the `User_Setup_Select.h` by uncommentig the line containing `#include ` and commenting out line containing `#include `. -```ini -//#include // Default setup is root library folder -``` - -* Add following line: - -```ini -#include // Setup file for ESP32 ST7789V SPI bus TFT -``` - -* Copy file `"Setup_ST7789_Display.h"` from usermod folder to `/.pio/libdeps/esp32dev/TFT_eSPI/User_Setups` +If your display includes backlight enable pin, #define TFT_BL with backlight enable GPIO number. \ No newline at end of file diff --git a/usermods/ST7789_display/ST7789_display.h b/usermods/ST7789_display/ST7789_display.h index 19ad5790a..93700c918 100644 --- a/usermods/ST7789_display/ST7789_display.h +++ b/usermods/ST7789_display/ST7789_display.h @@ -7,33 +7,55 @@ #include #include -#define USERMOD_ST7789_DISPLAY 97 - -#ifndef TFT_DISPOFF -#define TFT_DISPOFF 0x28 +#ifndef USER_SETUP_LOADED + #ifndef ST7789_DRIVER + #error Please define ST7789_DRIVER + #endif + #ifndef TFT_WIDTH + #error Please define TFT_WIDTH + #endif + #ifndef TFT_HEIGHT + #error Please define TFT_HEIGHT + #endif + #ifndef TFT_MOSI + #error Please define TFT_MOSI + #endif + #ifndef TFT_SCLK + #error Please define TFT_SCLK + #endif + #ifndef TFT_DC + #error Please define TFT_DC + #endif + #ifndef TFT_RST + #error Please define TFT_RST + #endif + #ifndef LOAD_GLCD + #error Please define LOAD_GLCD + #endif +#endif +#ifndef TFT_BL + #define TFT_BL -1 #endif -#ifndef TFT_SLPIN -#define TFT_SLPIN 0x10 -#endif +#define USERMOD_ID_ST7789_DISPLAY 97 -#define TFT_MOSI 21 -#define TFT_SCLK 22 -#define TFT_DC 18 -#define TFT_RST 5 -#define TFT_BL 26 // Display backlight control pin +TFT_eSPI tft = TFT_eSPI(TFT_WIDTH, TFT_HEIGHT); // Invoke custom library -TFT_eSPI tft = TFT_eSPI(240, 240); // Invoke custom library +// Extra char (+1) for null +#define LINE_BUFFER_SIZE 20 // How often we are redrawing screen #define USER_LOOP_REFRESH_RATE_MS 1000 +extern int getSignalQuality(int rssi); + //class name. Use something descriptive and leave the ": public Usermod" part :) class St7789DisplayUsermod : public Usermod { private: //Private class members. You can declare variables and functions only accessible to your usermod here unsigned long lastTime = 0; + bool enabled = true; bool displayTurnedOff = false; long lastRedraw = 0; @@ -45,9 +67,70 @@ class St7789DisplayUsermod : public Usermod { uint8_t knownBrightness = 0; uint8_t knownMode = 0; uint8_t knownPalette = 0; - uint8_t tftcharwidth = 19; // Number of chars that fit on screen with text size set to 2 + uint8_t knownEffectSpeed = 0; + uint8_t knownEffectIntensity = 0; + uint8_t knownMinute = 99; + uint8_t knownHour = 99; + + const uint8_t tftcharwidth = 19; // Number of chars that fit on screen with text size set to 2 long lastUpdate = 0; + void center(String &line, uint8_t width) { + int len = line.length(); + if (len0; i--) line = ' ' + line; + for (byte i=line.length(); i 12) { + showHour -= 12; + isAM = false; + } else { + isAM = true; + } + } + + sprintf_P(lineBuffer, PSTR("%2d:%02d"), (useAMPM ? showHour : hourCurrent), minuteCurrent); + tft.setTextColor(TFT_WHITE); + tft.setTextSize(4); + tft.setCursor(60, 24); + tft.print(lineBuffer); + + tft.setTextSize(2); + tft.setCursor(186, 24); + //sprintf_P(lineBuffer, PSTR("%02d"), secondCurrent); + if (useAMPM) tft.print(isAM ? "AM" : "PM"); + //else tft.print(lineBuffer); + } + public: //Functions called by WLED @@ -57,6 +140,9 @@ class St7789DisplayUsermod : public Usermod { */ void setup() { + PinManagerPinType pins[] = { { TFT_MOSI, true }, { TFT_MISO, false}, { TFT_SCLK, true }, { TFT_CS, true}, { TFT_DC, true}, { TFT_RST, true }, { TFT_BL, true } }; + if (!pinManager.allocateMultiplePins(pins, 7, PinOwner::UM_FourLineDisplay)) { enabled = false; return; } + tft.init(); tft.setRotation(0); //Rotation here is set up for the text to be readable with the port on the left. Use 1 to flip. tft.fillScreen(TFT_BLACK); @@ -65,10 +151,10 @@ class St7789DisplayUsermod : public Usermod { tft.setTextDatum(MC_DATUM); tft.setTextSize(2); tft.print("Loading..."); - if (TFT_BL > 0) - { // TFT_BL has been set in the TFT_eSPI library - pinMode(TFT_BL, OUTPUT); // Set backlight pin to output mode - digitalWrite(TFT_BL, HIGH); // Turn backlight on. + if (TFT_BL >= 0) + { + pinMode(TFT_BL, OUTPUT); // Set backlight pin to output mode + digitalWrite(TFT_BL, HIGH); // Turn backlight on. } } @@ -91,192 +177,153 @@ class St7789DisplayUsermod : public Usermod { * Instead, use a timer check as shown here. */ void loop() { -// Check if we time interval for redrawing passes. - if (millis() - lastUpdate < USER_LOOP_REFRESH_RATE_MS) + char buff[LINE_BUFFER_SIZE]; + + // Check if we time interval for redrawing passes. + if (millis() - lastUpdate < USER_LOOP_REFRESH_RATE_MS) { return; } - lastUpdate = millis(); + lastUpdate = millis(); -// Turn off display after 5 minutes with no change. - if(!displayTurnedOff && millis() - lastRedraw > 5*60*1000) + // Turn off display after 5 minutes with no change. + if (!displayTurnedOff && millis() - lastRedraw > 5*60*1000) { - digitalWrite(TFT_BL, LOW); // Turn backlight off. + if (TFT_BL >= 0) digitalWrite(TFT_BL, LOW); // Turn backlight off. displayTurnedOff = true; } -// Check if values which are shown on display changed from the last time. - if (((apActive) ? String(apSSID) : WiFi.SSID()) != knownSsid) - { - needRedraw = true; - } - else if (knownIp != (apActive ? IPAddress(4, 3, 2, 1) : WiFi.localIP())) - { - needRedraw = true; - } - else if (knownBrightness != bri) - { - needRedraw = true; - } - else if (knownMode != strip.getMainSegment().mode) - { - needRedraw = true; - } - else if (knownPalette != strip.getMainSegment().palette) - { - needRedraw = true; - } - - if (!needRedraw) - { - return; - } - needRedraw = false; - - if (displayTurnedOff) - { - digitalWrite(TFT_BL, TFT_BACKLIGHT_ON); // Turn backlight on. - displayTurnedOff = false; - } - lastRedraw = millis(); - -// Update last known values. - #if defined(ESP8266) - knownSsid = apActive ? WiFi.softAPSSID() : WiFi.SSID(); - #else - knownSsid = WiFi.SSID(); - #endif - knownIp = apActive ? IPAddress(4, 3, 2, 1) : WiFi.localIP(); - knownBrightness = bri; - knownMode = strip.getMainSegment().mode; - knownPalette = strip.getMainSegment().palette; - - tft.fillScreen(TFT_BLACK); - tft.setTextSize(2); -// First row with Wifi name - tft.setTextColor(TFT_SILVER); - tft.setCursor(3, 40); - tft.print(knownSsid.substring(0, tftcharwidth > 1 ? tftcharwidth - 1 : 0)); -// Print `~` char to indicate that SSID is longer, than our dicplay - if (knownSsid.length() > tftcharwidth) - tft.print("~"); - -// Second row with AP IP and Password or IP - tft.setTextColor(TFT_GREEN); - tft.setTextSize(2); - tft.setCursor(3, 64); -// Print AP IP and password in AP mode or knownIP if AP not active. - - if (apActive) - { - tft.setTextColor(TFT_YELLOW); - tft.print("AP IP: "); - tft.print(knownIp); - tft.setCursor(3,86); - tft.setTextColor(TFT_YELLOW); - tft.print("AP Pass:"); - tft.print(apPass); - } - else - { - tft.setTextColor(TFT_GREEN); - tft.print("IP: "); - tft.print(knownIp); - tft.setCursor(3,86); - //tft.print("Signal Strength: "); - //tft.print(i.wifi.signal); - tft.setTextColor(TFT_WHITE); - tft.print("Bri: "); - tft.print(((float(bri)/255)*100),0); - tft.print("%"); - } - -// Third row with mode name - tft.setCursor(3, 108); - uint8_t qComma = 0; - bool insideQuotes = false; - uint8_t printedChars = 0; - char singleJsonSymbol; -// Find the mode name in JSON - for (size_t i = 0; i < strlen_P(JSON_mode_names); i++) - { - singleJsonSymbol = pgm_read_byte_near(JSON_mode_names + i); - switch (singleJsonSymbol) + // Check if values which are shown on display changed from the last time. + if ((((apActive) ? String(apSSID) : WiFi.SSID()) != knownSsid) || + (knownIp != (apActive ? IPAddress(4, 3, 2, 1) : Network.localIP())) || + (knownBrightness != bri) || + (knownEffectSpeed != strip.getMainSegment().speed) || + (knownEffectIntensity != strip.getMainSegment().intensity) || + (knownMode != strip.getMainSegment().mode) || + (knownPalette != strip.getMainSegment().palette)) { - case '"': - insideQuotes = !insideQuotes; - break; - case '[': - case ']': - break; - case ',': - qComma++; - default: - if (!insideQuotes || (qComma != knownMode)) - break; - tft.setTextColor(TFT_MAGENTA); - tft.print(singleJsonSymbol); - printedChars++; + needRedraw = true; } - if ((qComma > knownMode) || (printedChars > tftcharwidth - 1)) - break; - } -// Fourth row with palette name - tft.setTextColor(TFT_YELLOW); - tft.setCursor(3, 130); - qComma = 0; - insideQuotes = false; - printedChars = 0; -// Looking for palette name in JSON. - for (size_t i = 0; i < strlen_P(JSON_palette_names); i++) - { - singleJsonSymbol = pgm_read_byte_near(JSON_palette_names + i); - switch (singleJsonSymbol) + + if (!needRedraw) { - case '"': - insideQuotes = !insideQuotes; - break; - case '[': - case ']': - break; - case ',': - qComma++; - default: - if (!insideQuotes || (qComma != knownPalette)) - break; - tft.print(singleJsonSymbol); - printedChars++; + return; } -// The following is modified from the code from the u8g2/u8g8 based code (knownPalette was knownMode) - if ((qComma > knownPalette) || (printedChars > tftcharwidth - 1)) - break; - } -// Fifth row with estimated mA usage - tft.setTextColor(TFT_SILVER); - tft.setCursor(3, 152); -// Print estimated milliamp usage (must specify the LED type in LED prefs for this to be a reasonable estimate). - tft.print("Current: "); - tft.print(strip.currentMilliamps); - tft.print("mA"); + needRedraw = false; + + if (displayTurnedOff) + { + digitalWrite(TFT_BL, HIGH); // Turn backlight on. + displayTurnedOff = false; + } + lastRedraw = millis(); + + // Update last known values. + #if defined(ESP8266) + knownSsid = apActive ? WiFi.softAPSSID() : WiFi.SSID(); + #else + knownSsid = WiFi.SSID(); + #endif + knownIp = apActive ? IPAddress(4, 3, 2, 1) : WiFi.localIP(); + knownBrightness = bri; + knownMode = strip.getMainSegment().mode; + knownPalette = strip.getMainSegment().palette; + knownEffectSpeed = strip.getMainSegment().speed; + knownEffectIntensity = strip.getMainSegment().intensity; + + tft.fillScreen(TFT_BLACK); + + showTime(); + + tft.setTextSize(2); + + // Wifi name + tft.setTextColor(TFT_GREEN); + tft.setCursor(0, 60); + String line = knownSsid.substring(0, tftcharwidth-1); + // Print `~` char to indicate that SSID is longer, than our display + if (knownSsid.length() > tftcharwidth) line = line.substring(0, tftcharwidth-1) + '~'; + center(line, tftcharwidth); + tft.print(line.c_str()); + + // Print AP IP and password in AP mode or knownIP if AP not active. + if (apActive) + { + tft.setCursor(0, 84); + tft.print("AP IP: "); + tft.print(knownIp); + tft.setCursor(0,108); + tft.print("AP Pass:"); + tft.print(apPass); + } + else + { + tft.setCursor(0, 84); + line = knownIp.toString(); + center(line, tftcharwidth); + tft.print(line.c_str()); + // percent brightness + tft.setCursor(0, 120); + tft.setTextColor(TFT_WHITE); + tft.print("Bri: "); + tft.print((((int)bri*100)/255)); + tft.print("%"); + // signal quality + tft.setCursor(124,120); + tft.print("Sig: "); + if (getSignalQuality(WiFi.RSSI()) < 10) { + tft.setTextColor(TFT_RED); + } else if (getSignalQuality(WiFi.RSSI()) < 25) { + tft.setTextColor(TFT_ORANGE); + } else { + tft.setTextColor(TFT_GREEN); + } + tft.print(getSignalQuality(WiFi.RSSI())); + tft.setTextColor(TFT_WHITE); + tft.print("%"); + } + + // mode name + tft.setTextColor(TFT_CYAN); + tft.setCursor(0, 144); + char lineBuffer[tftcharwidth+1]; + extractModeName(knownMode, JSON_mode_names, lineBuffer, tftcharwidth); + tft.print(lineBuffer); + + // palette name + tft.setTextColor(TFT_YELLOW); + tft.setCursor(0, 168); + extractModeName(knownPalette, JSON_palette_names, lineBuffer, tftcharwidth); + tft.print(lineBuffer); + + tft.setCursor(0, 192); + tft.setTextColor(TFT_SILVER); + sprintf_P(buff, PSTR("FX Spd:%3d Int:%3d"), effectSpeed, effectIntensity); + tft.print(buff); + + // Fifth row with estimated mA usage + tft.setTextColor(TFT_SILVER); + tft.setCursor(0, 216); + // Print estimated milliamp usage (must specify the LED type in LED prefs for this to be a reasonable estimate). + tft.print("Current: "); + tft.setTextColor(TFT_ORANGE); + tft.print(strip.currentMilliamps); + tft.print("mA"); } + /* * addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API. * Creating an "u" object allows you to add custom key/value pairs to the Info section of the WLED web UI. * Below it is shown how this could be used for e.g. a light sensor */ - /* void addToJsonInfo(JsonObject& root) { - int reading = 20; - //this code adds "u":{"Light":[20," lux"]} to the info object JsonObject user = root["u"]; if (user.isNull()) user = root.createNestedObject("u"); - JsonArray lightArr = user.createNestedArray("Light"); //name - lightArr.add(reading); //value - lightArr.add(" lux"); //unit + JsonArray lightArr = user.createNestedArray("ST7789"); //name + lightArr.add(enabled?F("installed"):F("disabled")); //unit } - */ /* @@ -295,7 +342,7 @@ class St7789DisplayUsermod : public Usermod { */ void readFromJsonState(JsonObject& root) { - userVar0 = root["user0"] | userVar0; //if "user0" key exists in JSON, update, else keep old value + //userVar0 = root["user0"] | userVar0; //if "user0" key exists in JSON, update, else keep old value //if (root["bri"] == 255) Serial.println(F("Don't burn down your garage!")); } @@ -316,8 +363,16 @@ class St7789DisplayUsermod : public Usermod { */ void addToConfig(JsonObject& root) { - JsonObject top = root.createNestedObject("exampleUsermod"); - top["great"] = userVar0; //save this var persistently whenever settings are saved + JsonObject top = root.createNestedObject("ST7789"); + JsonArray pins = top.createNestedArray("pin"); + pins.add(TFT_MOSI); + pins.add(TFT_MISO); + pins.add(TFT_SCLK); + pins.add(TFT_CS); + pins.add(TFT_DC); + pins.add(TFT_RST); + pins.add(TFT_BL); + //top["great"] = userVar0; //save this var persistently whenever settings are saved } @@ -329,10 +384,11 @@ class St7789DisplayUsermod : public Usermod { * but also that if you want to write persistent values to a dynamic buffer, you'd need to allocate it here instead of in setup. * If you don't know what that is, don't fret. It most likely doesn't affect your use case :) */ - void readFromConfig(JsonObject& root) + bool readFromConfig(JsonObject& root) { - JsonObject top = root["top"]; - userVar0 = top["great"] | 42; //The value right of the pipe "|" is the default value in case your setting was not present in cfg.json (e.g. first boot) + //JsonObject top = root["top"]; + //userVar0 = top["great"] | 42; //The value right of the pipe "|" is the default value in case your setting was not present in cfg.json (e.g. first boot) + return true; } @@ -342,7 +398,7 @@ class St7789DisplayUsermod : public Usermod { */ uint16_t getId() { - return USERMOD_ST7789_DISPLAY; + return USERMOD_ID_ST7789_DISPLAY; } //More methods can be added in the future, this example will then be extended. diff --git a/usermods/ST7789_display/Setup_ST7789_Display.h b/usermods/ST7789_display/Setup_ST7789_Display.h deleted file mode 100644 index 26d5c17ff..000000000 --- a/usermods/ST7789_display/Setup_ST7789_Display.h +++ /dev/null @@ -1,39 +0,0 @@ -// Setup for the ESP32 board with 1.5" 240x240 display - -// See SetupX_Template.h for all options available - -#define ST7789_DRIVER -#define TFT_SDA_READ // Display has a bidirectionsl SDA pin - -#define TFT_WIDTH 240 -#define TFT_HEIGHT 240 - -#define CGRAM_OFFSET // Library will add offsets required - -//#define TFT_MISO -1 - -#define TFT_MOSI 21 -#define TFT_SCLK 22 -//#define TFT_CS 5 -#define TFT_DC 18 -#define TFT_RST 5 - -#define TFT_BL 26 // Display backlight control pin - -#define TFT_BACKLIGHT_ON HIGH // HIGH or LOW are options - -#define LOAD_GLCD -#define LOAD_FONT2 -#define LOAD_FONT4 -#define LOAD_FONT6 -#define LOAD_FONT7 -#define LOAD_FONT8 -#define LOAD_GFXFF - -//#define SMOOTH_FONT - -//#define SPI_FREQUENCY 27000000 - #define SPI_FREQUENCY 40000000 // Maximum for ILI9341 - - -#define SPI_READ_FREQUENCY 6000000 // 6 MHz is the maximum SPI read speed for the ST7789V \ No newline at end of file diff --git a/usermods/TTGO-T-Display/usermod.cpp b/usermods/TTGO-T-Display/usermod.cpp index 9e08a001a..b126d40a0 100644 --- a/usermods/TTGO-T-Display/usermod.cpp +++ b/usermods/TTGO-T-Display/usermod.cpp @@ -177,58 +177,15 @@ void userLoop() { // Third row with mode name tft.setCursor(1, 68); - uint8_t qComma = 0; - bool insideQuotes = false; - uint8_t printedChars = 0; - char singleJsonSymbol; - // Find the mode name in JSON - for (size_t i = 0; i < strlen_P(JSON_mode_names); i++) { - singleJsonSymbol = pgm_read_byte_near(JSON_mode_names + i); - switch (singleJsonSymbol) { - case '"': - insideQuotes = !insideQuotes; - break; - case '[': - case ']': - break; - case ',': - qComma++; - default: - if (!insideQuotes || (qComma != knownMode)) - break; - tft.print(singleJsonSymbol); - printedChars++; - } - if ((qComma > knownMode) || (printedChars > tftcharwidth - 1)) - break; - } + char lineBuffer[tftcharwidth+1]; + extractModeName(knownMode, JSON_mode_names, lineBuffer, tftcharwidth); + tft.print(lineBuffer); + // Fourth row with palette name tft.setCursor(1, 90); - qComma = 0; - insideQuotes = false; - printedChars = 0; - // Looking for palette name in JSON. - for (size_t i = 0; i < strlen_P(JSON_palette_names); i++) { - singleJsonSymbol = pgm_read_byte_near(JSON_palette_names + i); - switch (singleJsonSymbol) { - case '"': - insideQuotes = !insideQuotes; - break; - case '[': - case ']': - break; - case ',': - qComma++; - default: - if (!insideQuotes || (qComma != knownPalette)) - break; - tft.print(singleJsonSymbol); - printedChars++; - } - // The following is modified from the code from the u8g2/u8g8 based code (knownPalette was knownMode) - if ((qComma > knownPalette) || (printedChars > tftcharwidth - 1)) - break; - } + extractModeName(knownPalette, JSON_palette_names, lineBuffer, tftcharwidth); + tft.print(lineBuffer); + // Fifth row with estimated mA usage tft.setCursor(1, 112); // Print estimated milliamp usage (must specify the LED type in LED prefs for this to be a reasonable estimate). diff --git a/usermods/Temperature/usermod_temperature.h b/usermods/Temperature/usermod_temperature.h index 40df0e533..a666639fe 100644 --- a/usermods/Temperature/usermod_temperature.h +++ b/usermods/Temperature/usermod_temperature.h @@ -46,6 +46,8 @@ class UsermodTemperature : public Usermod { bool enabled = true; + bool HApublished = false; + // strings to reduce flash memory usage (used more than twice) static const char _name[]; static const char _enabled[]; @@ -132,6 +134,28 @@ class UsermodTemperature : public Usermod { return false; } + void publishHomeAssistantAutodiscovery() { + if (!WLED_MQTT_CONNECTED) return; + + char json_str[1024], buf[128]; + size_t payload_size; + StaticJsonDocument<1024> json; + + sprintf_P(buf, PSTR("%s Temperature"), serverDescription); + json[F("name")] = buf; + strcpy(buf, mqttDeviceTopic); + strcat_P(buf, PSTR("/temperature")); + json[F("state_topic")] = buf; + json[F("device_class")] = F("temperature"); + json[F("unique_id")] = escapedMac.c_str(); + json[F("unit_of_measurement")] = F("°C"); + payload_size = serializeJson(json, json_str); + + sprintf_P(buf, PSTR("homeassistant/sensor/%s/config"), escapedMac.c_str()); + mqtt->publish(buf, 0, true, json_str, payload_size); + HApublished = true; + } + public: void setup() { @@ -206,6 +230,23 @@ class UsermodTemperature : public Usermod { } } + /** + * connected() is called every time the WiFi is (re)connected + * Use it to initialize network interfaces + */ + //void connected() {} + + /** + * subscribe to MQTT topic if needed + */ + void onMqttConnect(bool sessionPresent) { + //(re)subscribe to required topics + //char subuf[64]; + if (mqttDeviceTopic[0] != 0) { + publishHomeAssistantAutodiscovery(); + } + } + /* * API calls te enable data exchange between WLED modules */ @@ -229,7 +270,6 @@ class UsermodTemperature : public Usermod { if (user.isNull()) user = root.createNestedObject("u"); JsonArray temp = user.createNestedArray(FPSTR(_name)); - //temp.add(F("Loaded.")); if (temperature <= -100.0f) { temp.add(0); @@ -238,8 +278,13 @@ class UsermodTemperature : public Usermod { } temp.add(degC ? getTemperatureC() : getTemperatureF()); - if (degC) temp.add(F("°C")); - else temp.add(F("°F")); + temp.add(degC ? F("°C") : F("°F")); + + JsonObject sensor = root[F("sensor")]; + if (sensor.isNull()) sensor = root.createNestedObject(F("sensor")); + temp = sensor.createNestedArray(F("temp")); + temp.add(degC ? temperature : (float)temperature * 1.8f + 32); + temp.add(degC ? F("°C") : F("°F")); } /** diff --git a/usermods/UserModv2_SunRiseAndSet/README.md b/usermods/UserModv2_SunRiseAndSet/README.md deleted file mode 100644 index e989f0890..000000000 --- a/usermods/UserModv2_SunRiseAndSet/README.md +++ /dev/null @@ -1,15 +0,0 @@ -WLED v2 UserMod for running macros at sunrise and sunset. - -At the time of this text, this user mod requires code to be changed to set certain variables: - 1. To reflect the user's graphical location (latitude/longitude) used for calculating apparent sunrise/sunset - 2. To specify which macros will be run at sunrise and/or sunset. (defaults to 15 at sunrise and 16 at sunset) - 3. To optionally provide an offset from sunrise/sunset, in minutes (max of +/- 2 hours), when the macro will be run. - -In addition, WLED must be configured to get time from NTP (and the time must be retrieved via NTP.) - -Please open the UserMod_SunRiseAndSet.h file for instructions on what needs to be changed, where to copy files, etc. - -If this usermod proves useful enough, the code might eventually be updated to allow prompting for the required information -via the web interface and to store settings in EEPROM instead of hard-coding in the .h file. - -This usermod has only been tested on the esp32dev platform, but there's no reason it wouldn't work on other platforms. diff --git a/usermods/UserModv2_SunRiseAndSet/UserMod_SunRiseAndSet.h b/usermods/UserModv2_SunRiseAndSet/UserMod_SunRiseAndSet.h deleted file mode 100644 index ef1bb37ec..000000000 --- a/usermods/UserModv2_SunRiseAndSet/UserMod_SunRiseAndSet.h +++ /dev/null @@ -1,166 +0,0 @@ -#pragma once - -#include "wled.h" -#include - -/* - * - * REQUIREMENTS: - * The Dusk2Dawn library must be installed. This can be found at https://github.com/dmkishi/Dusk2Dawn. The 1.0.1 version of this library found via - * Arduino or platformio library managers is buggy and won't compile. The latest version from github should be used. - * - * NTP must be enabled and functional. It simply makes no sense to have events on sunrise/sunset when an accurate time isn't available. - * - * The user's geographical latitude and longitude must be configured (in decimal, not degrees/minutes/etc) using m_fLatitude and m_fLongitude - * - * if desired, an offset of up to +/- 2 hours can be specified for each of sunrise/sunset using m_sunriseOffset and m_sunsetOffset (defaults to 0) - * - * The specific macro to run at sunrise and/or sunset can be changed using m_sunriseMacro and m_sunsetMacro. (defaults to 15 and 16) - * - * From the Dusk2Dawn library: - * HINT: An easy way to find the longitude and latitude for any location is - * to find the spot in Google Maps, right click the place on the map, and - * select "What's here?". At the bottom, you’ll see a card with the - * coordinates. - * - * Once configured, copy UserMod_SunRiseAndSet.h to the sketch file (the same folder as wled00.ino exists), - * and then edit "usermods_list.cpp": - * Add '#include "UserMod_SunRiseAndSet.h"' in the 'includes' area - * Add 'usermods.add(new UserMod_SunRiseAndSet());' in the registerUsermods() area - * - */ - -class UserMod_SunRiseAndSet : public Usermod -{ -private: - - /**** USER SETTINGS ****/ - - float m_fLatitude = 40.6; // latitude where sunrise/set are calculated - float m_fLongitude = -79.80; // longitude where sunrise/set are calculated - int8_t m_sunriseOffset = 0; // offset from sunrise, in minutes, when macro should be run (negative for before sunrise, positive for after sunrise) - int8_t m_sunsetOffset = 0; // offset from sunset, in minutes, when macro should be run (negative for before sunset, positive for after sunset) - uint8_t m_sunriseMacro = 15; // macro number to run at sunrise - uint8_t m_sunsetMacro = 16; // macro number to run at sunset - - /**** END OF USER SETTINGS. DO NOT EDIT BELOW THIS LINE! ****/ - - - Dusk2Dawn *m_pD2D = NULL; // this must be dynamically allocated in order for parameters to be loaded from EEPROM - - int m_nUserSunrise = -1; // time, in minutes from midnight, of sunrise - int m_nUserSunset = -1; // time, in minutes from midnight, of sunset - - byte m_nLastRunMinute = -1; // indicates what minute the userloop was last run - used so that the code only runs once per minute - -public: - - virtual void setup(void) - { - /* TODO: From EEPROM, load the following variables: - * - * int16_t latitude16 = 4060; // user provided latitude, multiplied by 100 and rounded - * int16_t longitude16 = -7980; // user provided longitude, multiplied by 100 and rounded. - * int8_t sunrise_offset = 0; // number of minutes to offset the sunrise macro trigger (positive for minutes after sunrise, negative for minutes before) - * int8_t sunset_offset = 0; // number of minutes to offset the sunset macro trigger (positive for minutes after sunset, negative for minutes before) - * - * then: - * m_fLatitude = (float)latitude / 100.0; - * m_fLongitude = (float)longitude / 100.0; - * m_sunriseOffset = sunrise_offset; - * m_sunsetOffset = sunset_offset; - */ - - if ((0.0 != m_fLatitude) || (0.0 != m_fLongitude)) - { - m_pD2D = new Dusk2Dawn (m_fLatitude, m_fLongitude, 0 /* UTC */); - // can't really check for failures. if the alloc fails, the mod just doesn't work. - } - } - - void loop(void) - { - // without NTP, or a configured lat/long, none of this stuff is going to work... - // As an alternative, need to figure out how to determine if the user has manually set the clock or not. - if (m_pD2D && (999000000L != ntpLastSyncTime)) - { - // to prevent needing to import all the timezone stuff from other modules, work completely in UTC - time_t timeUTC = toki.second(); - tmElements_t tmNow; - breakTime(timeUTC, tmNow); - int nCurMinute = tmNow.Minute; - - if (m_nLastRunMinute != nCurMinute) //only check once a new minute begins - { - m_nLastRunMinute = nCurMinute; - int numMinutes = (60 * tmNow.Hour) + m_nLastRunMinute; // how many minutes into the day are we? - - // check to see if sunrise/sunset should be re-determined. Only do this if neither sunrise nor sunset - // are set. That happens when the device has just stated, and after both sunrise/sunset have already run. - if ((-1 == m_nUserSunrise) && (-1 == m_nUserSunset)) - { - m_nUserSunrise = m_pD2D->sunrise(tmNow.Year + 1970, tmNow.Month, tmNow.Day, false) % 1440; - m_nUserSunset = m_pD2D->sunset(tmNow.Year + 1970, tmNow.Month, tmNow.Day, false) % 1440; - if (m_nUserSunrise > numMinutes) // has sunrise already passed? if so, recompute for tomorrow - { - breakTime(timeUTC + (60*60*24), tmNow); - m_nUserSunrise = m_pD2D->sunrise(tmNow.Year + 1970, tmNow.Month, tmNow.Day, false) % 1440; - if (m_nUserSunset > numMinutes) // if sunset has also passed, recompute that as well - { - m_nUserSunset = m_pD2D->sunset(tmNow.Year + 1970, tmNow.Month, tmNow.Day, false) % 1440; - } - } - // offset by user provided values. becuase the offsets are signed bytes, the max offset is just over 2 hours. - m_nUserSunrise += m_sunriseOffset; - m_nUserSunset += m_sunsetOffset; - } - - if (numMinutes == m_nUserSunrise) // Good Morning! - { - if (m_sunriseMacro) - applyMacro(m_sunriseMacro); // run macro 15 - m_nUserSunrise = -1; - } - else if (numMinutes == m_nUserSunset) // Good Night! - { - if (m_sunsetMacro) - applyMacro(m_sunsetMacro); // run macro 16 - m_nUserSunset = -1; - } - } // if (m_nLastRunMinute != nCurMinute) - } // if (m_pD2D && (999000000L != ntpLastSyncTime)) - } - - void addToJsonState(JsonObject& root) - { - JsonObject user = root["SunRiseAndSet"]; - if (user.isNull()) user = root.createNestedObject("SunRiseAndSet"); - - char buf[10]; - if (-1 != m_nUserSunrise) - { - snprintf(buf, 10, "%02d:%02d UTC", m_nUserSunrise / 60, m_nUserSunrise % 60); - user["rise"] = buf; - } - if (-1 != m_nUserSunset) - { - snprintf(buf, 10, "%02d:%02d UTC", m_nUserSunset / 60, m_nUserSunset % 60); - user["set"] = buf; - } - JsonObject vars = user.createNestedObject("vars"); - vars["lat"] = m_fLatitude; - vars["long"] = m_fLongitude; - vars["rise_mac"] = m_sunriseMacro; - vars["set_mac"] = m_sunsetMacro; - vars["rise_off"] = m_sunriseOffset; - vars["set_off"] = m_sunsetOffset; - } - - ~UserMod_SunRiseAndSet(void) - { - if (m_pD2D) delete m_pD2D; - } -}; - - - diff --git a/usermods/VL53L0X_gestures/usermod_vl53l0x_gestures.h b/usermods/VL53L0X_gestures/usermod_vl53l0x_gestures.h index 210ec3f58..91766b9be 100644 --- a/usermods/VL53L0X_gestures/usermod_vl53l0x_gestures.h +++ b/usermods/VL53L0X_gestures/usermod_vl53l0x_gestures.h @@ -21,14 +21,6 @@ #include #include -#ifdef ARDUINO_ARCH_ESP32 - #define HW_PIN_SCL 22 - #define HW_PIN_SDA 21 -#else - #define HW_PIN_SCL 5 - #define HW_PIN_SDA 4 -#endif - #ifndef VL53L0X_MAX_RANGE_MM #define VL53L0X_MAX_RANGE_MM 230 // max height in millimiters to react for motions #endif @@ -59,7 +51,7 @@ class UsermodVL53L0XGestures : public Usermod { public: void setup() { - PinManagerPinType pins[2] = { { HW_PIN_SCL, true }, { HW_PIN_SDA, true } }; + PinManagerPinType pins[2] = { { i2c_scl, true }, { i2c_sda, true } }; if (!pinManager.allocateMultiplePins(pins, 2, PinOwner::HW_I2C)) { enabled = false; return; } Wire.begin(); @@ -127,13 +119,13 @@ class UsermodVL53L0XGestures : public Usermod { * It will be called by WLED when settings are actually saved (for example, LED settings are saved) * I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings! */ - void addToConfig(JsonObject& root) - { - JsonObject top = root.createNestedObject("VL53L0x"); - JsonArray pins = top.createNestedArray("pin"); - pins.add(HW_PIN_SCL); - pins.add(HW_PIN_SDA); - } +// void addToConfig(JsonObject& root) +// { +// JsonObject top = root.createNestedObject("VL53L0x"); +// JsonArray pins = top.createNestedArray("pin"); +// pins.add(i2c_scl); +// pins.add(i2c_sda); +// } /* * getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!). diff --git a/usermods/Wemos_D1_mini+Wemos32_mini_shield/usermod.cpp b/usermods/Wemos_D1_mini+Wemos32_mini_shield/usermod.cpp index 79241b5e2..c7eb8ee09 100644 --- a/usermods/Wemos_D1_mini+Wemos32_mini_shield/usermod.cpp +++ b/usermods/Wemos_D1_mini+Wemos32_mini_shield/usermod.cpp @@ -185,58 +185,14 @@ void userLoop() { // Third row with mode name u8x8.setCursor(2, 2); - uint8_t qComma = 0; - bool insideQuotes = false; - uint8_t printedChars = 0; - char singleJsonSymbol; + char lineBuffer[17]; + extractModeName(knownMode, JSON_mode_names, lineBuffer, 16); + u8x8.print(lineBuffer); - // Find the mode name in JSON - for (size_t i = 0; i < strlen_P(JSON_mode_names); i++) { - singleJsonSymbol = pgm_read_byte_near(JSON_mode_names + i); - switch (singleJsonSymbol) { - case '"': - insideQuotes = !insideQuotes; - break; - case '[': - case ']': - break; - case ',': - qComma++; - default: - if (!insideQuotes || (qComma != knownMode)) - break; - u8x8.print(singleJsonSymbol); - printedChars++; - } - if ((qComma > knownMode) || (printedChars > u8x8.getCols() - 2)) - break; - } // Fourth row with palette name u8x8.setCursor(2, 3); - qComma = 0; - insideQuotes = false; - printedChars = 0; - // Looking for palette name in JSON. - for (size_t i = 0; i < strlen_P(JSON_palette_names); i++) { - singleJsonSymbol = pgm_read_byte_near(JSON_palette_names + i); - switch (singleJsonSymbol) { - case '"': - insideQuotes = !insideQuotes; - break; - case '[': - case ']': - break; - case ',': - qComma++; - default: - if (!insideQuotes || (qComma != knownPalette)) - break; - u8x8.print(singleJsonSymbol); - printedChars++; - } - if ((qComma > knownMode) || (printedChars > u8x8.getCols() - 2)) - break; - } + extractModeName(knownPalette, JSON_palette_names, lineBuffer, 16); + u8x8.print(lineBuffer); u8x8.setFont(u8x8_font_open_iconic_embedded_1x1); u8x8.drawGlyph(0, 0, 80); // wifi icon diff --git a/usermods/Wemos_D1_mini+Wemos32_mini_shield/usermod_bme280.cpp b/usermods/Wemos_D1_mini+Wemos32_mini_shield/usermod_bme280.cpp index fe53a4628..05d4e77a4 100644 --- a/usermods/Wemos_D1_mini+Wemos32_mini_shield/usermod_bme280.cpp +++ b/usermods/Wemos_D1_mini+Wemos32_mini_shield/usermod_bme280.cpp @@ -191,58 +191,14 @@ void userLoop() { // Third row with mode name u8x8.setCursor(2, 2); - uint8_t qComma = 0; - bool insideQuotes = false; - uint8_t printedChars = 0; - char singleJsonSymbol; + char lineBuffer[17]; + extractModeName(knownMode, JSON_mode_names, lineBuffer, 16); + u8x8.print(lineBuffer); - // Find the mode name in JSON - for (size_t i = 0; i < strlen_P(JSON_mode_names); i++) { - singleJsonSymbol = pgm_read_byte_near(JSON_mode_names + i); - switch (singleJsonSymbol) { - case '"': - insideQuotes = !insideQuotes; - break; - case '[': - case ']': - break; - case ',': - qComma++; - default: - if (!insideQuotes || (qComma != knownMode)) - break; - u8x8.print(singleJsonSymbol); - printedChars++; - } - if ((qComma > knownMode) || (printedChars > u8x8.getCols() - 2)) - break; - } // Fourth row with palette name u8x8.setCursor(2, 3); - qComma = 0; - insideQuotes = false; - printedChars = 0; - // Looking for palette name in JSON. - for (size_t i = 0; i < strlen_P(JSON_palette_names); i++) { - singleJsonSymbol = pgm_read_byte_near(JSON_palette_names + i); - switch (singleJsonSymbol) { - case '"': - insideQuotes = !insideQuotes; - break; - case '[': - case ']': - break; - case ',': - qComma++; - default: - if (!insideQuotes || (qComma != knownPalette)) - break; - u8x8.print(singleJsonSymbol); - printedChars++; - } - if ((qComma > knownMode) || (printedChars > u8x8.getCols() - 2)) - break; - } + extractModeName(knownPalette, JSON_palette_names, lineBuffer, 16); + u8x8.print(lineBuffer); u8x8.setFont(u8x8_font_open_iconic_embedded_1x1); u8x8.drawGlyph(0, 0, 80); // wifi icon diff --git a/usermods/audioreactive/audio_reactive.h b/usermods/audioreactive/audio_reactive.h new file mode 100644 index 000000000..cc721cea1 --- /dev/null +++ b/usermods/audioreactive/audio_reactive.h @@ -0,0 +1,1640 @@ +#pragma once + +#include "wled.h" +#include +#include + +#ifndef ESP32 + #error This audio reactive usermod does not support the ESP8266. +#endif + +#if defined(WLED_DEBUG) || defined(SR_DEBUG) +#include +#endif + +/* + * Usermods allow you to add own functionality to WLED more easily + * See: https://github.com/Aircoookie/WLED/wiki/Add-own-functionality + * + * This is an audioreactive v2 usermod. + * .... + */ + +// Comment/Uncomment to toggle usb serial debugging +// #define MIC_LOGGER // MIC sampling & sound input debugging (serial plotter) +// #define FFT_SAMPLING_LOG // FFT result debugging +// #define SR_DEBUG // generic SR DEBUG messages + + +#ifdef SR_DEBUG + #define DEBUGSR_PRINT(x) Serial.print(x) + #define DEBUGSR_PRINTLN(x) Serial.println(x) + #define DEBUGSR_PRINTF(x...) Serial.printf(x) +#else + #define DEBUGSR_PRINT(x) + #define DEBUGSR_PRINTLN(x) + #define DEBUGSR_PRINTF(x...) +#endif + + +#include "audio_source.h" + +constexpr i2s_port_t I2S_PORT = I2S_NUM_0; +constexpr int BLOCK_SIZE = 128; +constexpr SRate_t SAMPLE_RATE = 22050; // Base sample rate in Hz - 22Khz is a standard rate. Physical sample time -> 23ms +//constexpr SRate_t SAMPLE_RATE = 16000; // 16kHz - use if FFTtask takes more than 20ms. Physical sample time -> 32ms +//constexpr SRate_t SAMPLE_RATE = 20480; // Base sample rate in Hz - 20Khz is experimental. Physical sample time -> 25ms +//constexpr SRate_t SAMPLE_RATE = 10240; // Base sample rate in Hz - previous default. Physical sample time -> 50ms + +#define FFT_MIN_CYCLE 21 // minimum time before FFT task is repeated. Use with 22Khz sampling +//#define FFT_MIN_CYCLE 30 // Use with 16Khz sampling +//#define FFT_MIN_CYCLE 23 // minimum time before FFT task is repeated. Use with 20Khz sampling +//#define FFT_MIN_CYCLE 46 // minimum time before FFT task is repeated. Use with 10Khz sampling + +// globals +static uint8_t inputLevel = 128; // UI slider value +static uint8_t soundSquelch = 10; // squelch value for volume reactive routines (config value) +static uint8_t sampleGain = 60; // sample gain (config value) +static uint8_t soundAgc = 0; // Automagic gain control: 0 - none, 1 - normal, 2 - vivid, 3 - lazy (config value) +static uint8_t audioSyncEnabled = 0; // bit field: bit 0 - send, bit 1 - receive (config value) + +// user settable parameters for limitSoundDynamics() +static bool limiterOn = true; // bool: enable / disable dynamics limiter +static uint16_t attackTime = 80; // int: attack time in milliseconds. Default 0.08sec +static uint16_t decayTime = 1400; // int: decay time in milliseconds. Default 1.40sec +// user settable options for FFTResult scaling +static uint8_t FFTScalingMode = 3; // 0 none; 1 optimized logarithmic; 2 optimized linear; 3 optimized sqare root + +// +// AGC presets +// Note: in C++, "const" implies "static" - no need to explicitly declare everything as "static const" +// +#define AGC_NUM_PRESETS 3 // AGC presets: normal, vivid, lazy +const double agcSampleDecay[AGC_NUM_PRESETS] = { 0.9994f, 0.9985f, 0.9997f}; // decay factor for sampleMax, in case the current sample is below sampleMax +const float agcZoneLow[AGC_NUM_PRESETS] = { 32, 28, 36}; // low volume emergency zone +const float agcZoneHigh[AGC_NUM_PRESETS] = { 240, 240, 248}; // high volume emergency zone +const float agcZoneStop[AGC_NUM_PRESETS] = { 336, 448, 304}; // disable AGC integrator if we get above this level +const float agcTarget0[AGC_NUM_PRESETS] = { 112, 144, 164}; // first AGC setPoint -> between 40% and 65% +const float agcTarget0Up[AGC_NUM_PRESETS] = { 88, 64, 116}; // setpoint switching value (a poor man's bang-bang) +const float agcTarget1[AGC_NUM_PRESETS] = { 220, 224, 216}; // second AGC setPoint -> around 85% +const double agcFollowFast[AGC_NUM_PRESETS] = { 1/192.f, 1/128.f, 1/256.f}; // quickly follow setpoint - ~0.15 sec +const double agcFollowSlow[AGC_NUM_PRESETS] = {1/6144.f,1/4096.f,1/8192.f}; // slowly follow setpoint - ~2-15 secs +const double agcControlKp[AGC_NUM_PRESETS] = { 0.6f, 1.5f, 0.65f}; // AGC - PI control, proportional gain parameter +const double agcControlKi[AGC_NUM_PRESETS] = { 1.7f, 1.85f, 1.2f}; // AGC - PI control, integral gain parameter +const float agcSampleSmooth[AGC_NUM_PRESETS] = { 1/12.f, 1/6.f, 1/16.f}; // smoothing factor for sampleAgc (use rawSampleAgc if you want the non-smoothed value) +// AGC presets end + +static AudioSource *audioSource = nullptr; +static volatile bool disableSoundProcessing = false; // if true, sound processing (FFT, filters, AGC) will be suspended. "volatile" as its shared between tasks. + +// audioreactive variables shared with FFT task +static float micDataReal = 0.0f; // MicIn data with full 24bit resolution - lowest 8bit after decimal point +static float multAgc = 1.0f; // sample * multAgc = sampleAgc. Our AGC multiplier +static float sampleAvg = 0.0f; // Smoothed Average sample - sampleAvg < 1 means "quiet" (simple noise gate) + +// peak detection +static bool samplePeak = false; // Boolean flag for peak - used in effects. Responding routine may reset this flag. Auto-reset after strip.getMinShowDelay() +static uint8_t maxVol = 10; // Reasonable value for constant volume for 'peak detector', as it won't always trigger (deprecated) +static uint8_t binNum = 8; // Used to select the bin for FFT based beat detection (deprecated) +static bool udpSamplePeak = false; // Boolean flag for peak. Set at the same tiem as samplePeak, but reset by transmitAudioData +static unsigned long timeOfPeak = 0; // time of last sample peak detection. +static void detectSamplePeak(void); // peak detection function (needs scaled FFT reasults in vReal[]) +static void autoResetPeak(void); // peak auto-reset function + + +//////////////////// +// Begin FFT Code // +//////////////////// + +#ifdef UM_AUDIOREACTIVE_USE_NEW_FFT +// lib_deps += https://github.com/kosme/arduinoFFT#develop @ 1.9.2 +#define FFT_SPEED_OVER_PRECISION // enables use of reciprocals (1/x etc), and an a few other speedups +#define FFT_SQRT_APPROXIMATION // enables "quake3" style inverse sqrt +#define sqrt(x) sqrtf(x) // little hack that reduces FFT time by 50% on ESP32 (as alternative to FFT_SQRT_APPROXIMATION) +#else +// lib_deps += https://github.com/blazoncek/arduinoFFT.git +#endif +#include + +// FFT Output variables shared with animations +#define NUM_GEQ_CHANNELS 16 // number of frequency channels. Don't change !! +static float FFT_MajorPeak = 1.0f; // FFT: strongest (peak) frequency +static float FFT_Magnitude = 0.0f; // FFT: volume (magnitude) of peak frequency +static uint8_t fftResult[NUM_GEQ_CHANNELS]= {0};// Our calculated freq. channel result table to be used by effects + +// FFT Constants +constexpr uint16_t samplesFFT = 512; // Samples in an FFT batch - This value MUST ALWAYS be a power of 2 +constexpr uint16_t samplesFFT_2 = 256; // meaningfull part of FFT results - only the "lower half" contains useful information. + +// These are the input and output vectors. Input vectors receive computed results from FFT. +static float vReal[samplesFFT] = {0.0f}; // FFT sample inputs / freq output - these are our raw result bins +static float vImag[samplesFFT] = {0.0f}; // imaginary parts + +// the following are observed values, supported by a bit of "educated guessing" +//#define FFT_DOWNSCALE 0.65f // 20kHz - downscaling factor for FFT results - "Flat-Top" window @20Khz, old freq channels +#define FFT_DOWNSCALE 0.46f // downscaling factor for FFT results - for "Flat-Top" window @22Khz, new freq channels +#define LOG_256 5.54517744 + +#ifdef UM_AUDIOREACTIVE_USE_NEW_FFT +static float windowWeighingFactors[samplesFFT] = {0.0f}; +#endif + +// Try and normalize fftBin values to a max of 4096, so that 4096/16 = 256. +static float fftCalc[NUM_GEQ_CHANNELS] = {0.0f}; +static float fftAvg[NUM_GEQ_CHANNELS] = {0.0f}; // Calculated frequency channel results, with smoothing (used if dynamics limiter is ON) +#ifdef SR_DEBUG +static float fftResultMax[NUM_GEQ_CHANNELS] = {0.0f}; // A table used for testing to determine how our post-processing is working. +#endif + +#if defined(WLED_DEBUG) || defined(SR_DEBUG) +static uint64_t fftTime = 0; +static uint64_t sampleTime = 0; +#endif + +// Table of multiplication factors so that we can even out the frequency response. +static float fftResultPink[NUM_GEQ_CHANNELS] = { 1.70f, 1.71f, 1.73f, 1.78f, 1.68f, 1.56f, 1.55f, 1.63f, 1.79f, 1.62f, 1.80f, 2.06f, 2.47f, 3.35f, 6.83f, 9.55f }; + +// Create FFT object +#ifdef UM_AUDIOREACTIVE_USE_NEW_FFT +static ArduinoFFT FFT = ArduinoFFT( vReal, vImag, samplesFFT, SAMPLE_RATE, windowWeighingFactors); +#else +static arduinoFFT FFT = arduinoFFT(vReal, vImag, samplesFFT, SAMPLE_RATE); +#endif + +static TaskHandle_t FFT_Task = nullptr; + +// float version of map() +static float mapf(float x, float in_min, float in_max, float out_min, float out_max){ + return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; +} + +static float fftAddAvg(int from, int to) { + float result = 0.0f; + for (int i = from; i <= to; i++) { + result += vReal[i]; + } + return result / float(to - from + 1); +} + +// FFT main task +void FFTcode(void * parameter) +{ + DEBUGSR_PRINT("FFT started on core: "); DEBUGSR_PRINTLN(xPortGetCoreID()); + + // see https://www.freertos.org/vtaskdelayuntil.html + const TickType_t xFrequency = FFT_MIN_CYCLE * portTICK_PERIOD_MS; + + TickType_t xLastWakeTime = xTaskGetTickCount(); + for(;;) { + delay(1); // DO NOT DELETE THIS LINE! It is needed to give the IDLE(0) task enough time and to keep the watchdog happy. + // taskYIELD(), yield(), vTaskDelay() and esp_task_wdt_feed() didn't seem to work. + + // Don't run FFT computing code if we're in Receive mode or in realtime mode + if (disableSoundProcessing || (audioSyncEnabled & 0x02)) { + vTaskDelayUntil( &xLastWakeTime, xFrequency); // release CPU, and let I2S fill its buffers + continue; + } + +#if defined(WLED_DEBUG) || defined(SR_DEBUG) + uint64_t start = esp_timer_get_time(); +#endif + + // get a fresh batch of samples from I2S + if (audioSource) audioSource->getSamples(vReal, samplesFFT); + +#if defined(WLED_DEBUG) || defined(SR_DEBUG) + if (start < esp_timer_get_time()) { // filter out overflows + unsigned long sampleTimeInMillis = (esp_timer_get_time() - start +5ULL) / 10ULL; // "+5" to ensure proper rounding + sampleTime = (sampleTimeInMillis*3 + sampleTime*7)/10; // smooth + } +#endif + + xLastWakeTime = xTaskGetTickCount(); // update "last unblocked time" for vTaskDelay + + // find highest sample in the batch + float maxSample = 0.0f; // max sample from FFT batch + for (int i=0; i < samplesFFT; i++) { + // set imaginary parts to 0 + vImag[i] = 0; + // pick our our current mic sample - we take the max value from all samples that go into FFT + if ((vReal[i] <= (INT16_MAX - 1024)) && (vReal[i] >= (INT16_MIN + 1024))) //skip extreme values - normally these are artefacts + if (fabsf((float)vReal[i]) > maxSample) maxSample = fabsf((float)vReal[i]); + } + // release highest sample to volume reactive effects early - not strictly necessary here - could also be done at the end of the function + // early release allows the filters (getSample() and agcAvg()) to work with fresh values - we will have matching gain and noise gate values when we want to process the FFT results. micDataReal = maxSample; + micDataReal = maxSample; + +#ifdef SR_DEBUG + if (true) { // this allows measure FFT runtimes, as it disables the "only when needed" optimization +#else + if (sampleAvg > 1) { // noise gate open means that FFT results will be used. Don't run FFT if results are not needed. +#endif + + // run FFT (takes 3-5ms on ESP32, ~12ms on ESP32-S2) +#ifdef UM_AUDIOREACTIVE_USE_NEW_FFT + FFT.dcRemoval(); // remove DC offset + FFT.windowing( FFTWindow::Flat_top, FFTDirection::Forward); // Weigh data using "Flat Top" function - better amplitude accuracy + //FFT.windowing(FFTWindow::Blackman_Harris, FFTDirection::Forward); // Weigh data using "Blackman- Harris" window - sharp peaks due to excellent sideband rejection + FFT.compute( FFTDirection::Forward ); // Compute FFT + FFT.complexToMagnitude(); // Compute magnitudes +#else + FFT.DCRemoval(); // let FFT lib remove DC component, so we don't need to care about this in getSamples() + + //FFT.Windowing( FFT_WIN_TYP_HAMMING, FFT_FORWARD ); // Weigh data - standard Hamming window + //FFT.Windowing( FFT_WIN_TYP_BLACKMAN, FFT_FORWARD ); // Blackman window - better side freq rejection + //FFT.Windowing( FFT_WIN_TYP_BLACKMAN_HARRIS, FFT_FORWARD );// Blackman-Harris - excellent sideband rejection + FFT.Windowing( FFT_WIN_TYP_FLT_TOP, FFT_FORWARD ); // Flat Top Window - better amplitude accuracy + FFT.Compute( FFT_FORWARD ); // Compute FFT + FFT.ComplexToMagnitude(); // Compute magnitudes +#endif + +#ifdef UM_AUDIOREACTIVE_USE_NEW_FFT + FFT.majorPeak(FFT_MajorPeak, FFT_Magnitude); // let the effects know which freq was most dominant +#else + FFT.MajorPeak(&FFT_MajorPeak, &FFT_Magnitude); // let the effects know which freq was most dominant +#endif + FFT_MajorPeak = constrain(FFT_MajorPeak, 1.0f, 11025.0f); // restrict value to range expected by effects + + } else { // noise gate closed - only clear results as FFT was skipped. MIC samples are still valid when we do this. + memset(vReal, 0, sizeof(vReal)); + FFT_MajorPeak = 1; + FFT_Magnitude = 0.001; + } + + for (int i = 0; i < samplesFFT; i++) { + float t = fabsf(vReal[i]); // just to be sure - values in fft bins should be positive any way + vReal[i] = t / 16.0f; // Reduce magnitude. Want end result to be scaled linear and ~4096 max. + } // for() + + // mapping of FFT result bins to frequency channels + if (sampleAvg > 1) { // noise gate open +#if 0 + /* This FFT post processing is a DIY endeavour. What we really need is someone with sound engineering expertise to do a great job here AND most importantly, that the animations look GREAT as a result. + * + * Andrew's updated mapping of 256 bins down to the 16 result bins with Sample Freq = 10240, samplesFFT = 512 and some overlap. + * Based on testing, the lowest/Start frequency is 60 Hz (with bin 3) and a highest/End frequency of 5120 Hz in bin 255. + * Now, Take the 60Hz and multiply by 1.320367784 to get the next frequency and so on until the end. Then detetermine the bins. + * End frequency = Start frequency * multiplier ^ 16 + * Multiplier = (End frequency/ Start frequency) ^ 1/16 + * Multiplier = 1.320367784 + */ // Range + fftCalc[ 0] = fftAddAvg(2,4); // 60 - 100 + fftCalc[ 1] = fftAddAvg(4,5); // 80 - 120 + fftCalc[ 2] = fftAddAvg(5,7); // 100 - 160 + fftCalc[ 3] = fftAddAvg(7,9); // 140 - 200 + fftCalc[ 4] = fftAddAvg(9,12); // 180 - 260 + fftCalc[ 5] = fftAddAvg(12,16); // 240 - 340 + fftCalc[ 6] = fftAddAvg(16,21); // 320 - 440 + fftCalc[ 7] = fftAddAvg(21,29); // 420 - 600 + fftCalc[ 8] = fftAddAvg(29,37); // 580 - 760 + fftCalc[ 9] = fftAddAvg(37,48); // 740 - 980 + fftCalc[10] = fftAddAvg(48,64); // 960 - 1300 + fftCalc[11] = fftAddAvg(64,84); // 1280 - 1700 + fftCalc[12] = fftAddAvg(84,111); // 1680 - 2240 + fftCalc[13] = fftAddAvg(111,147); // 2220 - 2960 + fftCalc[14] = fftAddAvg(147,194); // 2940 - 3900 + fftCalc[15] = fftAddAvg(194,250); // 3880 - 5000 // avoid the last 5 bins, which are usually inaccurate +#else + /* new mapping, optimized for 22050 Hz by softhack007 */ + // bins frequency range + fftCalc[ 0] = fftAddAvg(1,2); // 1 43 - 86 sub-bass + fftCalc[ 1] = fftAddAvg(2,3); // 1 86 - 129 bass + fftCalc[ 2] = fftAddAvg(3,5); // 2 129 - 216 bass + fftCalc[ 3] = fftAddAvg(5,7); // 2 216 - 301 bass + midrange + fftCalc[ 4] = fftAddAvg(7,10); // 3 301 - 430 midrange + fftCalc[ 5] = fftAddAvg(10,13); // 3 430 - 560 midrange + fftCalc[ 6] = fftAddAvg(13,19); // 5 560 - 818 midrange + fftCalc[ 7] = fftAddAvg(19,26); // 7 818 - 1120 midrange -- 1Khz should always be the center ! + fftCalc[ 8] = fftAddAvg(26,33); // 7 1120 - 1421 midrange + fftCalc[ 9] = fftAddAvg(33,44); // 9 1421 - 1895 midrange + fftCalc[10] = fftAddAvg(44,56); // 12 1895 - 2412 midrange + high mid + fftCalc[11] = fftAddAvg(56,70); // 14 2412 - 3015 high mid + fftCalc[12] = fftAddAvg(70,86); // 16 3015 - 3704 high mid + fftCalc[13] = fftAddAvg(86,104); // 18 3704 - 4479 high mid + fftCalc[14] = fftAddAvg(104,165) * 0.88f; // 61 4479 - 7106 high mid + high -- with slight damping + fftCalc[15] = fftAddAvg(165,215) * 0.70f; // 50 7106 - 9259 high -- with some damping + // don't use the last bins from 216 to 255. They are usually contaminated by aliasing (aka noise) +#endif + } else { // noise gate closed - just decay old values + for (int i=0; i < NUM_GEQ_CHANNELS; i++) { + fftCalc[i] *= 0.85f; // decay to zero + if (fftCalc[i] < 4.0f) fftCalc[i] = 0.0f; + } + } + + // post-processing of frequency channels (pink noise adjustment, AGC, smooting, scaling) + for (int i=0; i < NUM_GEQ_CHANNELS; i++) { + + if (sampleAvg > 1) { // noise gate open + // Adjustment for frequency curves. + fftCalc[i] *= fftResultPink[i]; + if (FFTScalingMode > 0) fftCalc[i] *= FFT_DOWNSCALE; // adjustment related to FFT windowing function + // Manual linear adjustment of gain using sampleGain adjustment for different input types. + fftCalc[i] *= soundAgc ? multAgc : ((float)sampleGain/40.0f * (float)inputLevel/128.0f + 1.0f/16.0f); //apply gain, with inputLevel adjustment + if(fftCalc[i] < 0) fftCalc[i] = 0; + } + + // smooth results - rise fast, fall slower + if(fftCalc[i] > fftAvg[i]) // rise fast + fftAvg[i] = fftCalc[i] *0.75f + 0.25f*fftAvg[i]; // will need approx 2 cycles (50ms) for converging against fftCalc[i] + else { // fall slow + if (decayTime < 1000) fftAvg[i] = fftCalc[i]*0.22f + 0.78f*fftAvg[i]; // approx 5 cycles (225ms) for falling to zero + else if (decayTime < 2000) fftAvg[i] = fftCalc[i]*0.17f + 0.83f*fftAvg[i]; // default - approx 9 cycles (225ms) for falling to zero + else if (decayTime < 3000) fftAvg[i] = fftCalc[i]*0.14f + 0.86f*fftAvg[i]; // approx 14 cycles (350ms) for falling to zero + else fftAvg[i] = fftCalc[i]*0.1f + 0.9f*fftAvg[i]; // approx 20 cycles (500ms) for falling to zero + } + // constrain internal vars - just to be sure + fftCalc[i] = constrain(fftCalc[i], 0.0f, 1023.0f); + fftAvg[i] = constrain(fftAvg[i], 0.0f, 1023.0f); + + float currentResult; + if(limiterOn == true) + currentResult = fftAvg[i]; + else + currentResult = fftCalc[i]; + + switch (FFTScalingMode) { + case 1: + // Logarithmic scaling + currentResult *= 0.42; // 42 is the answer ;-) + currentResult -= 8.0; // this skips the lowest row, giving some room for peaks + if (currentResult > 1.0) currentResult = logf(currentResult); // log to base "e", which is the fastest log() function + else currentResult = 0.0; // special handling, because log(1) = 0; log(0) = undefined + currentResult *= 0.85f + (float(i)/18.0f); // extra up-scaling for high frequencies + currentResult = mapf(currentResult, 0, LOG_256, 0, 255); // map [log(1) ... log(255)] to [0 ... 255] + break; + case 2: + // Linear scaling + currentResult *= 0.30f; // needs a bit more damping, get stay below 255 + currentResult -= 4.0; // giving a bit more room for peaks + if (currentResult < 1.0f) currentResult = 0.0f; + currentResult *= 0.85f + (float(i)/1.8f); // extra up-scaling for high frequencies + break; + case 3: + // square root scaling + currentResult *= 0.38f; + currentResult -= 6.0f; + if (currentResult > 1.0) currentResult = sqrtf(currentResult); + else currentResult = 0.0; // special handling, because sqrt(0) = undefined + currentResult *= 0.85f + (float(i)/4.5f); // extra up-scaling for high frequencies + currentResult = mapf(currentResult, 0.0, 16.0, 0.0, 255.0); // map [sqrt(1) ... sqrt(256)] to [0 ... 255] + break; + + case 0: + default: + // no scaling - leave freq bins as-is + currentResult -= 4; // just a bit more room for peaks + break; + } + + // Now, let's dump it all into fftResult. Need to do this, otherwise other routines might grab fftResult values prematurely. + if (soundAgc > 0) { // apply extra "GEQ Gain" if set by user + float post_gain = (float)inputLevel/128.0f; + if (post_gain < 1.0f) post_gain = ((post_gain -1.0f) * 0.8f) +1.0f; + currentResult *= post_gain; + } + fftResult[i] = constrain((int)currentResult, 0, 255); + } + +#if defined(WLED_DEBUG) || defined(SR_DEBUG) + if (start < esp_timer_get_time()) { // filter out overflows + unsigned long fftTimeInMillis = ((esp_timer_get_time() - start) +5ULL) / 10ULL; // "+5" to ensure proper rounding + fftTime = (fftTimeInMillis*3 + fftTime*7)/10; // smooth + } +#endif + // run peak detection + autoResetPeak(); + detectSamplePeak(); + + vTaskDelayUntil( &xLastWakeTime, xFrequency); // release CPU, and let I2S fill its buffers + + } // for(;;)ever +} // FFTcode() task end + + +//////////////////// +// Peak detection // +//////////////////// + +// peak detection is called from FFT task when vReal[] contains valid FFT results +static void detectSamplePeak(void) { + // Poor man's beat detection by seeing if sample > Average + some value. + if ((sampleAvg > 1) && (maxVol > 0) && (binNum > 1) && (vReal[binNum] > maxVol) && ((millis() - timeOfPeak) > 100)) { + // This goes through ALL of the 255 bins - but ignores stupid settings + // Then we got a peak, else we don't. The peak has to time out on its own in order to support UDP sound sync. + samplePeak = true; + timeOfPeak = millis(); + udpSamplePeak = true; + } +} + +static void autoResetPeak(void) { + uint16_t MinShowDelay = MAX(50, strip.getMinShowDelay()); // Fixes private class variable compiler error. Unsure if this is the correct way of fixing the root problem. -THATDONFC + if (millis() - timeOfPeak > MinShowDelay) { // Auto-reset of samplePeak after a complete frame has passed. + samplePeak = false; + if (audioSyncEnabled == 0) udpSamplePeak = false; // this is normally reset by transmitAudioData + } +} + + +//////////////////// +// usermod class // +//////////////////// + +//class name. Use something descriptive and leave the ": public Usermod" part :) +class AudioReactive : public Usermod { + + private: + #ifndef AUDIOPIN + int8_t audioPin = -1; + #else + int8_t audioPin = AUDIOPIN; + #endif + #ifndef DMTYPE // I2S mic type + uint8_t dmType = 1; // 0=none/disabled/analog; 1=generic I2S + #else + uint8_t dmType = DMTYPE; + #endif + #ifndef I2S_SDPIN // aka DOUT + int8_t i2ssdPin = 32; + #else + int8_t i2ssdPin = I2S_SDPIN; + #endif + #ifndef I2S_WSPIN // aka LRCL + int8_t i2swsPin = 15; + #else + int8_t i2swsPin = I2S_WSPIN; + #endif + #ifndef I2S_CKPIN // aka BCLK + int8_t i2sckPin = 14; /*PDM: set to I2S_PIN_NO_CHANGE*/ + #else + int8_t i2sckPin = I2S_CKPIN; + #endif + #ifndef ES7243_SDAPIN + int8_t sdaPin = -1; + #else + int8_t sdaPin = ES7243_SDAPIN; + #endif + #ifndef ES7243_SCLPIN + int8_t sclPin = -1; + #else + int8_t sclPin = ES7243_SCLPIN; + #endif + #ifndef MCLK_PIN + int8_t mclkPin = I2S_PIN_NO_CHANGE; /* ESP32: only -1, 0, 1, 3 allowed*/ + #else + int8_t mclkPin = MCLK_PIN; + #endif + + // new "V2" audiosync struct - 40 Bytes + struct audioSyncPacket { + char header[6]; // 06 Bytes + float sampleRaw; // 04 Bytes - either "sampleRaw" or "rawSampleAgc" depending on soundAgc setting + float sampleSmth; // 04 Bytes - either "sampleAvg" or "sampleAgc" depending on soundAgc setting + uint8_t samplePeak; // 01 Bytes - 0 no peak; >=1 peak detected. In future, this will also provide peak Magnitude + uint8_t reserved1; // 01 Bytes - for future extensions - not used yet + uint8_t fftResult[16]; // 16 Bytes + float FFT_Magnitude; // 04 Bytes + float FFT_MajorPeak; // 04 Bytes + }; + + // old "V1" audiosync struct - 83 Bytes - for backwards compatibility + struct audioSyncPacket_v1 { + char header[6]; // 06 Bytes + uint8_t myVals[32]; // 32 Bytes + int sampleAgc; // 04 Bytes + int sampleRaw; // 04 Bytes + float sampleAvg; // 04 Bytes + bool samplePeak; // 01 Bytes + uint8_t fftResult[16]; // 16 Bytes + double FFT_Magnitude; // 08 Bytes + double FFT_MajorPeak; // 08 Bytes + }; + + // set your config variables to their boot default value (this can also be done in readFromConfig() or a constructor if you prefer) + bool enabled = false; + bool initDone = false; + + // variables for UDP sound sync + WiFiUDP fftUdp; // UDP object for sound sync (from WiFi UDP, not Async UDP!) + bool udpSyncConnected = false;// UDP connection status -> true if connected to multicast group + unsigned long lastTime = 0; // last time of running UDP Microphone Sync + const uint16_t delayMs = 10; // I don't want to sample too often and overload WLED + uint16_t audioSyncPort= 11988;// default port for UDP sound sync + + // used for AGC + int last_soundAgc = -1; // used to detect AGC mode change (for resetting AGC internal error buffers) + double control_integrated = 0.0; // persistent across calls to agcAvg(); "integrator control" = accumulated error + + // variables used by getSample() and agcAvg() + int16_t micIn = 0; // Current sample starts with negative values and large values, which is why it's 16 bit signed + double sampleMax = 0.0; // Max sample over a few seconds. Needed for AGC controler. + float micLev = 0.0f; // Used to convert returned value to have '0' as minimum. A leveller + float expAdjF = 0.0f; // Used for exponential filter. + float sampleReal = 0.0f; // "sampleRaw" as float, to provide bits that are lost otherwise (before amplification by sampleGain or inputLevel). Needed for AGC. + int16_t sampleRaw = 0; // Current sample. Must only be updated ONCE!!! (amplified mic value by sampleGain and inputLevel) + int16_t rawSampleAgc = 0; // not smoothed AGC sample + float sampleAgc = 0.0f; // Smoothed AGC sample + + // variables used in effects + float volumeSmth = 0.0f; // either sampleAvg or sampleAgc depending on soundAgc; smoothed sample + int16_t volumeRaw = 0; // either sampleRaw or rawSampleAgc depending on soundAgc + float my_magnitude =0.0f; // FFT_Magnitude, scaled by multAgc + + // used to feed "Info" Page + unsigned long last_UDPTime = 0; // time of last valid UDP sound sync datapacket + float maxSample5sec = 0.0f; // max sample (after AGC) in last 5 seconds + unsigned long sampleMaxTimer = 0; // last time maxSample5sec was reset + #define CYCLE_SAMPLEMAX 3500 // time window for merasuring + + // strings to reduce flash memory usage (used more than twice) + static const char _name[]; + static const char _enabled[]; + static const char _inputLvl[]; + static const char _analogmic[]; + static const char _digitalmic[]; + static const char UDP_SYNC_HEADER[]; + static const char UDP_SYNC_HEADER_v1[]; + + // private methods + + //////////////////// + // Debug support // + //////////////////// + void logAudio() + { + #ifdef MIC_LOGGER + // Debugging functions for audio input and sound processing. Comment out the values you want to see + Serial.print("micReal:"); Serial.print(micDataReal); Serial.print("\t"); + //Serial.print("micIn:"); Serial.print(micIn); Serial.print("\t"); + //Serial.print("micLev:"); Serial.print(micLev); Serial.print("\t"); + //Serial.print("sampleReal:"); Serial.print(sampleReal); Serial.print("\t"); + //Serial.print("sample:"); Serial.print(sample); Serial.print("\t"); + //Serial.print("sampleAvg:"); Serial.print(sampleAvg); Serial.print("\t"); + //Serial.print("sampleMax:"); Serial.print(sampleMax); Serial.print("\t"); + //Serial.print("samplePeak:"); Serial.print((samplePeak!=0) ? 128:0); Serial.print("\t"); + //Serial.print("multAgc:"); Serial.print(multAgc, 4); Serial.print("\t"); + Serial.print("sampleAgc:"); Serial.print(sampleAgc); Serial.print("\t"); + //Serial.print("volumeRaw:"); Serial.print(volumeRaw); Serial.print("\t"); + //Serial.print("volumeSmth:"); Serial.print(volumeSmth); Serial.print("\t"); + + Serial.println(); + #endif + + #ifdef FFT_SAMPLING_LOG + #if 0 + for(int i=0; i maxVal) maxVal = fftResult[i]; + if(fftResult[i] < minVal) minVal = fftResult[i]; + } + for(int i = 0; i < NUM_GEQ_CHANNELS; i++) { + Serial.print(i); Serial.print(":"); + Serial.printf("%04ld ", map(fftResult[i], 0, (scaleValuesFromCurrentMaxVal ? maxVal : defaultScalingFromHighValue), (mapValuesToPlotterSpace*i*scalingToHighValue)+0, (mapValuesToPlotterSpace*i*scalingToHighValue)+scalingToHighValue-1)); + } + if(printMaxVal) { + Serial.printf("maxVal:%04d ", maxVal + (mapValuesToPlotterSpace ? 16*256 : 0)); + } + if(printMinVal) { + Serial.printf("%04d:minVal ", minVal); // printed with value first, then label, so negative values can be seen in Serial Monitor but don't throw off y axis in Serial Plotter + } + if(mapValuesToPlotterSpace) + Serial.printf("max:%04d ", (printMaxVal ? 17 : 16)*256); // print line above the maximum value we expect to see on the plotter to avoid autoscaling y axis + else + Serial.printf("max:%04d ", 256); + Serial.println(); + #endif // FFT_SAMPLING_LOG + } // logAudio() + + + ////////////////////// + // Audio Processing // + ////////////////////// + + /* + * A "PI controller" multiplier to automatically adjust sound sensitivity. + * + * A few tricks are implemented so that sampleAgc does't only utilize 0% and 100%: + * 0. don't amplify anything below squelch (but keep previous gain) + * 1. gain input = maximum signal observed in the last 5-10 seconds + * 2. we use two setpoints, one at ~60%, and one at ~80% of the maximum signal + * 3. the amplification depends on signal level: + * a) normal zone - very slow adjustment + * b) emergency zome (<10% or >90%) - very fast adjustment + */ + void agcAvg(unsigned long the_time) + { + const int AGC_preset = (soundAgc > 0)? (soundAgc-1): 0; // make sure the _compiler_ knows this value will not change while we are inside the function + + float lastMultAgc = multAgc; // last muliplier used + float multAgcTemp = multAgc; // new multiplier + float tmpAgc = sampleReal * multAgc; // what-if amplified signal + + float control_error; // "control error" input for PI control + + if (last_soundAgc != soundAgc) + control_integrated = 0.0; // new preset - reset integrator + + // For PI controller, we need to have a constant "frequency" + // so let's make sure that the control loop is not running at insane speed + static unsigned long last_time = 0; + unsigned long time_now = millis(); + if ((the_time > 0) && (the_time < time_now)) time_now = the_time; // allow caller to override my clock + + if (time_now - last_time > 2) { + last_time = time_now; + + if((fabs(sampleReal) < 2.0f) || (sampleMax < 1.0f)) { + // MIC signal is "squelched" - deliver silence + tmpAgc = 0; + // we need to "spin down" the intgrated error buffer + if (fabs(control_integrated) < 0.01) control_integrated = 0.0; + else control_integrated *= 0.91; + } else { + // compute new setpoint + if (tmpAgc <= agcTarget0Up[AGC_preset]) + multAgcTemp = agcTarget0[AGC_preset] / sampleMax; // Make the multiplier so that sampleMax * multiplier = first setpoint + else + multAgcTemp = agcTarget1[AGC_preset] / sampleMax; // Make the multiplier so that sampleMax * multiplier = second setpoint + } + // limit amplification + if (multAgcTemp > 32.0f) multAgcTemp = 32.0f; + if (multAgcTemp < 1.0f/64.0f) multAgcTemp = 1.0f/64.0f; + + // compute error terms + control_error = multAgcTemp - lastMultAgc; + + if (((multAgcTemp > 0.085f) && (multAgcTemp < 6.5f)) //integrator anti-windup by clamping + && (multAgc*sampleMax < agcZoneStop[AGC_preset])) //integrator ceiling (>140% of max) + control_integrated += control_error * 0.002 * 0.25; // 2ms = intgration time; 0.25 for damping + else + control_integrated *= 0.9; // spin down that beasty integrator + + // apply PI Control + tmpAgc = sampleReal * lastMultAgc; // check "zone" of the signal using previous gain + if ((tmpAgc > agcZoneHigh[AGC_preset]) || (tmpAgc < soundSquelch + agcZoneLow[AGC_preset])) { // upper/lower emergy zone + multAgcTemp = lastMultAgc + agcFollowFast[AGC_preset] * agcControlKp[AGC_preset] * control_error; + multAgcTemp += agcFollowFast[AGC_preset] * agcControlKi[AGC_preset] * control_integrated; + } else { // "normal zone" + multAgcTemp = lastMultAgc + agcFollowSlow[AGC_preset] * agcControlKp[AGC_preset] * control_error; + multAgcTemp += agcFollowSlow[AGC_preset] * agcControlKi[AGC_preset] * control_integrated; + } + + // limit amplification again - PI controler sometimes "overshoots" + //multAgcTemp = constrain(multAgcTemp, 0.015625f, 32.0f); // 1/64 < multAgcTemp < 32 + if (multAgcTemp > 32.0f) multAgcTemp = 32.0f; + if (multAgcTemp < 1.0f/64.0f) multAgcTemp = 1.0f/64.0f; + } + + // NOW finally amplify the signal + tmpAgc = sampleReal * multAgcTemp; // apply gain to signal + if (fabsf(sampleReal) < 2.0f) tmpAgc = 0.0f; // apply squelch threshold + //tmpAgc = constrain(tmpAgc, 0, 255); + if (tmpAgc > 255) tmpAgc = 255.0f; // limit to 8bit + if (tmpAgc < 1) tmpAgc = 0.0f; // just to be sure + + // update global vars ONCE - multAgc, sampleAGC, rawSampleAgc + multAgc = multAgcTemp; + rawSampleAgc = 0.8f * tmpAgc + 0.2f * (float)rawSampleAgc; + // update smoothed AGC sample + if (fabsf(tmpAgc) < 1.0f) + sampleAgc = 0.5f * tmpAgc + 0.5f * sampleAgc; // fast path to zero + else + sampleAgc += agcSampleSmooth[AGC_preset] * (tmpAgc - sampleAgc); // smooth path + + last_soundAgc = soundAgc; + } // agcAvg() + + // post-processing and filtering of MIC sample (micDataReal) from FFTcode() + void getSample() + { + float sampleAdj; // Gain adjusted sample value + float tmpSample; // An interim sample variable used for calculatioins. + const float weighting = 0.2f; // Exponential filter weighting. Will be adjustable in a future release. + const int AGC_preset = (soundAgc > 0)? (soundAgc-1): 0; // make sure the _compiler_ knows this value will not change while we are inside the function + + #ifdef WLED_DISABLE_SOUND + micIn = inoise8(millis(), millis()); // Simulated analog read + micDataReal = micIn; + #else + #ifdef ESP32 + micIn = int(micDataReal); // micDataSm = ((micData * 3) + micData)/4; + #else + // this is the minimal code for reading analog mic input on 8266. + // warning!! Absolutely experimental code. Audio on 8266 is still not working. Expects a million follow-on problems. + static unsigned long lastAnalogTime = 0; + static float lastAnalogValue = 0.0f; + if (millis() - lastAnalogTime > 20) { + micDataReal = analogRead(A0); // read one sample with 10bit resolution. This is a dirty hack, supporting volumereactive effects only. + lastAnalogTime = millis(); + lastAnalogValue = micDataReal; + yield(); + } else micDataReal = lastAnalogValue; + micIn = int(micDataReal); + #endif + #endif + + micLev = ((micLev * 8191.0f) + micDataReal) / 8192.0f; // takes a few seconds to "catch up" with the Mic Input + if(micIn < micLev) micLev = ((micLev * 31.0f) + micDataReal) / 32.0f; // align MicLev to lowest input signal + + micIn -= micLev; // Let's center it to 0 now + // Using an exponential filter to smooth out the signal. We'll add controls for this in a future release. + float micInNoDC = fabs(micDataReal - micLev); + expAdjF = (weighting * micInNoDC + (1.0-weighting) * expAdjF); + expAdjF = (expAdjF <= soundSquelch) ? 0: expAdjF; // simple noise gate + if ((soundSquelch == 0) && (expAdjF < 0.25f)) expAdjF = 0; // do something meaningfull when "squelch = 0" + + expAdjF = fabsf(expAdjF); // Now (!) take the absolute value + tmpSample = expAdjF; + micIn = abs(micIn); // And get the absolute value of each sample + + sampleAdj = tmpSample * sampleGain / 40.0f * inputLevel/128.0f + tmpSample / 16.0f; // Adjust the gain. with inputLevel adjustment + sampleReal = tmpSample; + + sampleAdj = fmax(fmin(sampleAdj, 255), 0); // Question: why are we limiting the value to 8 bits ??? + sampleRaw = (int16_t)sampleAdj; // ONLY update sample ONCE!!!! + + // keep "peak" sample, but decay value if current sample is below peak + if ((sampleMax < sampleReal) && (sampleReal > 0.5f)) { + sampleMax = sampleMax + 0.5f * (sampleReal - sampleMax); // new peak - with some filtering + } else { + if ((multAgc*sampleMax > agcZoneStop[AGC_preset]) && (soundAgc > 0)) + sampleMax += 0.5f * (sampleReal - sampleMax); // over AGC Zone - get back quickly + else + sampleMax *= agcSampleDecay[AGC_preset]; // signal to zero --> 5-8sec + } + if (sampleMax < 0.5f) sampleMax = 0.0f; + + sampleAvg = ((sampleAvg * 15.0f) + sampleAdj) / 16.0f; // Smooth it out over the last 16 samples. + } // getSample() + + + /* Limits the dynamics of volumeSmth (= sampleAvg or sampleAgc). + * does not affect FFTResult[] or volumeRaw ( = sample or rawSampleAgc) + */ + // effects: Gravimeter, Gravcenter, Gravcentric, Noisefire, Plasmoid, Freqpixels, Freqwave, Gravfreq, (2D Swirl, 2D Waverly) + void limitSampleDynamics(void) { + const float bigChange = 196; // just a representative number - a large, expected sample value + static unsigned long last_time = 0; + static float last_volumeSmth = 0.0f; + + if (limiterOn == false) return; + + long delta_time = millis() - last_time; + delta_time = constrain(delta_time , 1, 1000); // below 1ms -> 1ms; above 1sec -> sily lil hick-up + float deltaSample = volumeSmth - last_volumeSmth; + + if (attackTime > 0) { // user has defined attack time > 0 + float maxAttack = bigChange * float(delta_time) / float(attackTime); + if (deltaSample > maxAttack) deltaSample = maxAttack; + } + if (decayTime > 0) { // user has defined decay time > 0 + float maxDecay = - bigChange * float(delta_time) / float(decayTime); + if (deltaSample < maxDecay) deltaSample = maxDecay; + } + + volumeSmth = last_volumeSmth + deltaSample; + + last_volumeSmth = volumeSmth; + last_time = millis(); + } + + + ////////////////////// + // UDP Sound Sync // + ////////////////////// + + // try to establish UDP sound sync connection + void connectUDPSoundSync(void) { + // This function tries to establish a UDP sync connection if needed + // necessary as we also want to transmit in "AP Mode", but the standard "connected()" callback only reacts on STA connection + static unsigned long last_connection_attempt = 0; + + if ((audioSyncPort <= 0) || ((audioSyncEnabled & 0x03) == 0)) return; // Sound Sync not enabled + if (udpSyncConnected) return; // already connected + if (!(apActive || interfacesInited)) return; // neither AP nor other connections availeable + if (millis() - last_connection_attempt < 15000) return; // only try once in 15 seconds + + // if we arrive here, we need a UDP connection but don't have one + last_connection_attempt = millis(); + connected(); // try to start UDP + } + + void transmitAudioData() + { + if (!udpSyncConnected) return; + //DEBUGSR_PRINTLN("Transmitting UDP Mic Packet"); + + audioSyncPacket transmitData; + strncpy_P(transmitData.header, PSTR(UDP_SYNC_HEADER), 6); + // transmit samples that were not modified by limitSampleDynamics() + transmitData.sampleRaw = (soundAgc) ? rawSampleAgc: sampleRaw; + transmitData.sampleSmth = (soundAgc) ? sampleAgc : sampleAvg; + transmitData.samplePeak = udpSamplePeak ? 1:0; + udpSamplePeak = false; // Reset udpSamplePeak after we've transmitted it + transmitData.reserved1 = 0; + + for (int i = 0; i < NUM_GEQ_CHANNELS; i++) { + transmitData.fftResult[i] = (uint8_t)constrain(fftResult[i], 0, 254); + } + + transmitData.FFT_Magnitude = my_magnitude; + transmitData.FFT_MajorPeak = FFT_MajorPeak; + + fftUdp.beginMulticastPacket(); + fftUdp.write(reinterpret_cast(&transmitData), sizeof(transmitData)); + fftUdp.endPacket(); + return; + } // transmitAudioData() + + static bool isValidUdpSyncVersion(const char *header) { + return strncmp_P(header, PSTR(UDP_SYNC_HEADER), 6) == 0; + } + + bool receiveAudioData() // check & process new data. return TRUE in case that new audio data was received. + { + if (!udpSyncConnected) return false; + bool haveFreshData = false; + size_t packetSize = fftUdp.parsePacket(); + if (packetSize > 5) { + //DEBUGSR_PRINTLN("Received UDP Sync Packet"); + uint8_t fftBuff[packetSize]; + fftUdp.read(fftBuff, packetSize); + + // VERIFY THAT THIS IS A COMPATIBLE PACKET + if (packetSize == sizeof(audioSyncPacket) && (isValidUdpSyncVersion((const char *)fftBuff))) { + audioSyncPacket *receivedPacket = reinterpret_cast(fftBuff); + + // update samples for effects + volumeSmth = fmaxf(receivedPacket->sampleSmth, 0.0f); + volumeRaw = fmaxf(receivedPacket->sampleRaw, 0.0f); + + // update internal samples + sampleRaw = volumeRaw; + sampleAvg = volumeSmth; + rawSampleAgc = volumeRaw; + sampleAgc = volumeSmth; + multAgc = 1.0f; + + autoResetPeak(); + // Only change samplePeak IF it's currently false. + // If it's true already, then the animation still needs to respond. + if (!samplePeak) { + samplePeak = receivedPacket->samplePeak >0 ? true:false; + if (samplePeak) timeOfPeak = millis(); + //userVar1 = samplePeak; + } + + //These values are only available on the ESP32 + for (int i = 0; i < NUM_GEQ_CHANNELS; i++) fftResult[i] = receivedPacket->fftResult[i]; + + my_magnitude = fmaxf(receivedPacket->FFT_Magnitude, 0.0f); + FFT_Magnitude = my_magnitude; + FFT_MajorPeak = constrain(receivedPacket->FFT_MajorPeak, 1.0f, 11025.0f); // restrict value to range expected by effects + + //DEBUGSR_PRINTLN("Finished parsing UDP Sync Packet"); + haveFreshData = true; + } + } + return haveFreshData; + } + + + ////////////////////// + // usermod functions// + ////////////////////// + + public: + //Functions called by WLED or other usermods + + /* + * setup() is called once at boot. WiFi is not yet connected at this point. + * You can use it to initialize variables, sensors or similar. + * It is called *AFTER* readFromConfig() + */ + void setup() + { + disableSoundProcessing = true; // just to be sure + if (!initDone) { + // usermod exchangeable data + // we will assign all usermod exportable data here as pointers to original variables or arrays and allocate memory for pointers + 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; //*used (New) + um_data->u_type[0] = UMT_FLOAT; + um_data->u_data[1] = &volumeRaw; // used (New) + um_data->u_type[1] = UMT_UINT16; + um_data->u_data[2] = fftResult; //*used (Blurz, DJ Light, Noisemove, GEQ_base, 2D Funky Plank, Akemi) + um_data->u_type[2] = UMT_BYTE_ARR; + um_data->u_data[3] = &samplePeak; //*used (Puddlepeak, Ripplepeak, Waterfall) + um_data->u_type[3] = UMT_BYTE; + um_data->u_data[4] = &FFT_MajorPeak; //*used (Ripplepeak, Freqmap, Freqmatrix, Freqpixels, Freqwave, Gravfreq, Rocktaves, Waterfall) + um_data->u_type[4] = UMT_FLOAT; + um_data->u_data[5] = &my_magnitude; // used (New) + um_data->u_type[5] = UMT_FLOAT; + um_data->u_data[6] = &maxVol; // assigned in effect function from UI element!!! (Puddlepeak, Ripplepeak, Waterfall) + um_data->u_type[6] = UMT_BYTE; + um_data->u_data[7] = &binNum; // assigned in effect function from UI element!!! (Puddlepeak, Ripplepeak, Waterfall) + um_data->u_type[7] = UMT_BYTE; + } + + // Reset I2S peripheral for good measure + i2s_driver_uninstall(I2S_NUM_0); + #if !defined(CONFIG_IDF_TARGET_ESP32C3) + periph_module_reset(PERIPH_I2S0_MODULE); // not possible on -C3 + #endif + delay(100); // Give that poor microphone some time to setup. + switch (dmType) { + #if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S3) + // stub cases for not-yet-supported I2S modes on other ESP32 chips + case 0: //ADC analog + #if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3) + case 5: //PDM Microphone + #endif + #endif + case 1: + DEBUGSR_PRINT(F("AR: Generic I2S Microphone - ")); DEBUGSR_PRINTLN(F(I2S_MIC_CHANNEL_TEXT)); + audioSource = new I2SSource(SAMPLE_RATE, BLOCK_SIZE); + delay(100); + if (audioSource) audioSource->initialize(i2swsPin, i2ssdPin, i2sckPin); + break; + case 2: + DEBUGSR_PRINTLN(F("AR: ES7243 Microphone (right channel only).")); + audioSource = new ES7243(SAMPLE_RATE, BLOCK_SIZE); + delay(100); + if (audioSource) audioSource->initialize(sdaPin, sclPin, i2swsPin, i2ssdPin, i2sckPin, mclkPin); + break; + case 3: + DEBUGSR_PRINT(F("AR: SPH0645 Microphone - ")); DEBUGSR_PRINTLN(F(I2S_MIC_CHANNEL_TEXT)); + audioSource = new SPH0654(SAMPLE_RATE, BLOCK_SIZE); + delay(100); + audioSource->initialize(i2swsPin, i2ssdPin, i2sckPin); + break; + case 4: + DEBUGSR_PRINT(F("AR: Generic I2S Microphone with Master Clock - ")); DEBUGSR_PRINTLN(F(I2S_MIC_CHANNEL_TEXT)); + audioSource = new I2SSource(SAMPLE_RATE, BLOCK_SIZE); + delay(100); + if (audioSource) audioSource->initialize(i2swsPin, i2ssdPin, i2sckPin, mclkPin); + break; + #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) + case 5: + DEBUGSR_PRINT(F("AR: I2S PDM Microphone - ")); DEBUGSR_PRINTLN(F(I2S_MIC_CHANNEL_TEXT)); + audioSource = new I2SSource(SAMPLE_RATE, BLOCK_SIZE); + delay(100); + if (audioSource) audioSource->initialize(i2swsPin, i2ssdPin); + break; + #endif + #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) + // ADC over I2S is only possible on "classic" ESP32 + case 0: + default: + DEBUGSR_PRINTLN(F("AR: Analog Microphone (left channel only).")); + audioSource = new I2SAdcSource(SAMPLE_RATE, BLOCK_SIZE); + delay(100); + if (audioSource) audioSource->initialize(audioPin); + break; + #endif + } + delay(250); // give microphone enough time to initialise + + if (!audioSource) enabled = false; // audio failed to initialise + if (enabled) onUpdateBegin(false); // create FFT task + if (FFT_Task == nullptr) enabled = false; // FFT task creation failed + if (enabled) disableSoundProcessing = false; // all good - enable audio processing + + if((!audioSource) || (!audioSource->isInitialized())) { // audio source failed to initialize. Still stay "enabled", as there might be input arriving via UDP Sound Sync + DEBUGSR_PRINTLN(F("AR: Failed to initialize sound input driver. Please check input PIN settings.")); + disableSoundProcessing = true; + } + + if (enabled) connectUDPSoundSync(); + initDone = true; + } + + + /* + * connected() is called every time the WiFi is (re)connected + * Use it to initialize network interfaces + */ + void connected() + { + if (udpSyncConnected) { // clean-up: if open, close old UDP sync connection + udpSyncConnected = false; + fftUdp.stop(); + } + + if (audioSyncPort > 0 && (audioSyncEnabled & 0x03)) { + #ifndef ESP8266 + udpSyncConnected = fftUdp.beginMulticast(IPAddress(239, 0, 0, 1), audioSyncPort); + #else + udpSyncConnected = fftUdp.beginMulticast(WiFi.localIP(), IPAddress(239, 0, 0, 1), audioSyncPort); + #endif + } + } + + + /* + * loop() is called continuously. Here you can check for events, read sensors, etc. + * + * Tips: + * 1. You can use "if (WLED_CONNECTED)" to check for a successful network connection. + * Additionally, "if (WLED_MQTT_CONNECTED)" is available to check for a connection to an MQTT broker. + * + * 2. Try to avoid using the delay() function. NEVER use delays longer than 10 milliseconds. + * Instead, use a timer check as shown here. + */ + void loop() + { + static unsigned long lastUMRun = millis(); + + if (!enabled) { + disableSoundProcessing = true; // keep processing suspended (FFT task) + lastUMRun = millis(); // update time keeping + return; + } + // We cannot wait indefinitely before processing audio data + if (strip.isUpdating() && (millis() - lastUMRun < 2)) return; // be nice, but not too nice + + // suspend local sound processing when "real time mode" is active (E131, UDP, ADALIGHT, ARTNET) + if ( (realtimeOverride == REALTIME_OVERRIDE_NONE) // please add other overrides here if needed + &&( (realtimeMode == REALTIME_MODE_GENERIC) + ||(realtimeMode == REALTIME_MODE_E131) + ||(realtimeMode == REALTIME_MODE_UDP) + ||(realtimeMode == REALTIME_MODE_ADALIGHT) + ||(realtimeMode == REALTIME_MODE_ARTNET) ) ) // please add other modes here if needed + { + #ifdef WLED_DEBUG + if ((disableSoundProcessing == false) && (audioSyncEnabled == 0)) { // we just switched to "disabled" + DEBUG_PRINTLN("[AR userLoop] realtime mode active - audio processing suspended."); + DEBUG_PRINTF( " RealtimeMode = %d; RealtimeOverride = %d\n", int(realtimeMode), int(realtimeOverride)); + } + #endif + disableSoundProcessing = true; + } else { + #ifdef WLED_DEBUG + if ((disableSoundProcessing == true) && (audioSyncEnabled == 0) && audioSource->isInitialized()) { // we just switched to "enabled" + DEBUG_PRINTLN("[AR userLoop] realtime mode ended - audio processing resumed."); + DEBUG_PRINTF( " RealtimeMode = %d; RealtimeOverride = %d\n", int(realtimeMode), int(realtimeOverride)); + } + #endif + if ((disableSoundProcessing == true) && (audioSyncEnabled == 0)) lastUMRun = millis(); // just left "realtime mode" - update timekeeping + disableSoundProcessing = false; + } + + if (audioSyncEnabled & 0x02) disableSoundProcessing = true; // make sure everything is disabled IF in audio Receive mode + if (audioSyncEnabled & 0x01) disableSoundProcessing = false; // keep running audio IF we're in audio Transmit mode + if (!audioSource->isInitialized()) disableSoundProcessing = true; // no audio source + + + // Only run the sampling code IF we're not in Receive mode or realtime mode + if (!(audioSyncEnabled & 0x02) && !disableSoundProcessing) { + if (soundAgc > AGC_NUM_PRESETS) soundAgc = 0; // make sure that AGC preset is valid (to avoid array bounds violation) + + unsigned long t_now = millis(); // remember current time + int userloopDelay = int(t_now - lastUMRun); + if (lastUMRun == 0) userloopDelay=0; // startup - don't have valid data from last run. + + #ifdef WLED_DEBUG + // complain when audio userloop has been delayed for long time. Currently we need userloop running between 500 and 1500 times per second. + if ((userloopDelay > 23) && !disableSoundProcessing && (audioSyncEnabled == 0)) { + DEBUG_PRINTF("[AR userLoop] hickup detected -> was inactive for last %d millis!\n", userloopDelay); + } + #endif + + // run filters, and repeat in case of loop delays (hick-up compensation) + if (userloopDelay <2) userloopDelay = 0; // minor glitch, no problem + if (userloopDelay >200) userloopDelay = 200; // limit number of filter re-runs + do { + getSample(); // run microphone sampling filters + agcAvg(t_now - userloopDelay); // Calculated the PI adjusted value as sampleAvg + userloopDelay -= 2; // advance "simulated time" by 2ms + } while (userloopDelay > 0); + lastUMRun = t_now; // update time keeping + + // update samples for effects (raw, smooth) + volumeSmth = (soundAgc) ? sampleAgc : sampleAvg; + volumeRaw = (soundAgc) ? rawSampleAgc: sampleRaw; + // update FFTMagnitude, taking into account AGC amplification + my_magnitude = FFT_Magnitude; // / 16.0f, 8.0f, 4.0f done in effects + if (soundAgc) my_magnitude *= multAgc; + if (volumeSmth < 1 ) my_magnitude = 0.001f; // noise gate closed - mute + + limitSampleDynamics(); + } // if (!disableSoundProcessing) + + autoResetPeak(); // auto-reset sample peak after strip minShowDelay + if (!udpSyncConnected) udpSamplePeak = false; // reset UDP samplePeak while UDP is unconnected + + connectUDPSoundSync(); // ensure we have a connection - if needed + + // UDP Microphone Sync - receive mode + if ((audioSyncEnabled & 0x02) && udpSyncConnected) { + // Only run the audio listener code if we're in Receive mode + static float syncVolumeSmth = 0; + bool have_new_sample = false; + if (millis() - lastTime > delayMs) { + have_new_sample = receiveAudioData(); + if (have_new_sample) last_UDPTime = millis(); + lastTime = millis(); + } + if (have_new_sample) syncVolumeSmth = volumeSmth; // remember received sample + else volumeSmth = syncVolumeSmth; // restore originally received sample for next run of dynamics limiter + limitSampleDynamics(); // run dynamics limiter on received volumeSmth, to hide jumps and hickups + } + + #if defined(MIC_LOGGER) || defined(MIC_SAMPLING_LOG) || defined(FFT_SAMPLING_LOG) + EVERY_N_MILLIS(20) { + logAudio(); + } + #endif + + // Info Page: keep max sample from last 5 seconds + if ((millis() - sampleMaxTimer) > CYCLE_SAMPLEMAX) { + sampleMaxTimer = millis(); + maxSample5sec = (0.15 * maxSample5sec) + 0.85 *((soundAgc) ? sampleAgc : sampleAvg); // reset, and start with some smoothing + if (sampleAvg < 1) maxSample5sec = 0; // noise gate + } else { + if ((sampleAvg >= 1)) maxSample5sec = fmaxf(maxSample5sec, (soundAgc) ? rawSampleAgc : sampleRaw); // follow maximum volume + } + + //UDP Microphone Sync - transmit mode + if ((audioSyncEnabled & 0x01) && (millis() - lastTime > 20)) { + // Only run the transmit code IF we're in Transmit mode + transmitAudioData(); + lastTime = millis(); + } + + } + + + bool getUMData(um_data_t **data) + { + if (!data || !enabled) return false; // no pointer provided by caller or not enabled -> exit + *data = um_data; + return true; + } + + + void onUpdateBegin(bool init) + { +#ifdef WLED_DEBUG + fftTime = sampleTime = 0; +#endif + // gracefully suspend FFT task (if running) + disableSoundProcessing = true; + + // reset sound data + micDataReal = 0.0f; + volumeRaw = 0; volumeSmth = 0; + sampleAgc = 0; sampleAvg = 0; + sampleRaw = 0; rawSampleAgc = 0; + my_magnitude = 0; FFT_Magnitude = 0; FFT_MajorPeak = 1; + multAgc = 1; + // reset FFT data + memset(fftCalc, 0, sizeof(fftCalc)); + memset(fftAvg, 0, sizeof(fftAvg)); + memset(fftResult, 0, sizeof(fftResult)); + for(int i=(init?0:1); i=0 + && (buttonType[b] == BTN_TYPE_ANALOG || buttonType[b] == BTN_TYPE_ANALOG_INVERTED) + ) { + return true; + } + return false; + } + + + //////////////////////////// + // Settings and Info Page // + //////////////////////////// + + /* + * addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API. + * Creating an "u" object allows you to add custom key/value pairs to the Info section of the WLED web UI. + * Below it is shown how this could be used for e.g. a light sensor + */ + void addToJsonInfo(JsonObject& root) + { + char myStringBuffer[16]; // buffer for snprintf() + JsonObject user = root["u"]; + if (user.isNull()) user = root.createNestedObject("u"); + + JsonArray infoArr = user.createNestedArray(FPSTR(_name)); + + String uiDomString = F(""); + infoArr.add(uiDomString); + + if (enabled) { + // Input Level Slider + if (disableSoundProcessing == false) { // only show slider when audio processing is running + if (soundAgc > 0) + infoArr = user.createNestedArray(F("GEQ Input Level")); // if AGC is on, this slider only affects fftResult[] frequencies + else + infoArr = user.createNestedArray(F("Audio Input Level")); + uiDomString = F("
"); // + infoArr.add(uiDomString); + } + + // The following can be used for troubleshooting user errors and is so not enclosed in #ifdef WLED_DEBUG + + // current Audio input + infoArr = user.createNestedArray(F("Audio Source")); + if (audioSyncEnabled & 0x02) { + // UDP sound sync - receive mode + infoArr.add(F("UDP sound sync")); + if (udpSyncConnected) { + if (millis() - last_UDPTime < 2500) + infoArr.add(F(" - receiving")); + else + infoArr.add(F(" - idle")); + } else { + infoArr.add(F(" - no connection")); + } + } else { + // Analog or I2S digital input + if (audioSource && (audioSource->isInitialized())) { + // audio source sucessfully configured + if (audioSource->getType() == AudioSource::Type_I2SAdc) { + infoArr.add(F("ADC analog")); + } else { + infoArr.add(F("I2S digital")); + } + // input level or "silence" + if (maxSample5sec > 1.0) { + float my_usage = 100.0f * (maxSample5sec / 255.0f); + snprintf_P(myStringBuffer, 15, PSTR(" - peak %3d%%"), int(my_usage)); + infoArr.add(myStringBuffer); + } else { + infoArr.add(F(" - quiet")); + } + } else { + // error during audio source setup + infoArr.add(F("not initialized")); + infoArr.add(F(" - check GPIO config")); + } + } + + // Sound processing (FFT and input filters) + infoArr = user.createNestedArray(F("Sound Processing")); + if (audioSource && (disableSoundProcessing == false)) { + infoArr.add(F("running")); + } else { + infoArr.add(F("suspended")); + } + + // AGC or manual Gain + if ((soundAgc==0) && (disableSoundProcessing == false) && !(audioSyncEnabled & 0x02)) { + infoArr = user.createNestedArray(F("Manual Gain")); + float myGain = ((float)sampleGain/40.0f * (float)inputLevel/128.0f) + 1.0f/16.0f; // non-AGC gain from presets + infoArr.add(roundf(myGain*100.0f) / 100.0f); + infoArr.add("x"); + } + if (soundAgc && (disableSoundProcessing == false) && !(audioSyncEnabled & 0x02)) { + infoArr = user.createNestedArray(F("AGC Gain")); + infoArr.add(roundf(multAgc*100.0f) / 100.0f); + infoArr.add("x"); + } + + // UDP Sound Sync status + infoArr = user.createNestedArray(F("UDP Sound Sync")); + if (audioSyncEnabled) { + if (audioSyncEnabled & 0x01) { + infoArr.add(F("send mode")); + } else if (audioSyncEnabled & 0x02) { + infoArr.add(F("receive mode")); + } + } else + infoArr.add("off"); + if (audioSyncEnabled && !udpSyncConnected) infoArr.add(" (unconnected)"); + + #if defined(WLED_DEBUG) || defined(SR_DEBUG) + infoArr = user.createNestedArray(F("Sampling time")); + infoArr.add(float(sampleTime)/100.0f); + infoArr.add(" ms"); + infoArr = user.createNestedArray(F("FFT time")); + infoArr.add(float(fftTime-sampleTime)/100.0f); + infoArr.add(" ms"); + DEBUGSR_PRINTF("AR Sampling time: %5.2f ms\n", float(sampleTime)/100.0f); + DEBUGSR_PRINTF("AR FFT time : %5.2f ms\n", float(fftTime-sampleTime)/100.0f); + #endif + } + } + + + /* + * addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object). + * Values in the state object may be modified by connected clients + */ + void addToJsonState(JsonObject& root) + { + if (!initDone) return; // prevent crash on boot applyPreset() + JsonObject usermod = root[FPSTR(_name)]; + if (usermod.isNull()) { + usermod = root.createNestedObject(FPSTR(_name)); + } + usermod["on"] = enabled; + } + + + /* + * readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object). + * Values in the state object may be modified by connected clients + */ + void readFromJsonState(JsonObject& root) + { + if (!initDone) return; // prevent crash on boot applyPreset() + bool prevEnabled = enabled; + JsonObject usermod = root[FPSTR(_name)]; + if (!usermod.isNull()) { + if (usermod[FPSTR(_enabled)].is()) { + enabled = usermod[FPSTR(_enabled)].as(); + if (prevEnabled != enabled) onUpdateBegin(!enabled); + } + if (usermod[FPSTR(_inputLvl)].is()) { + inputLevel = min(255,max(0,usermod[FPSTR(_inputLvl)].as())); + } + } + } + + + /* + * addToConfig() can be used to add custom persistent settings to the cfg.json file in the "um" (usermod) object. + * It will be called by WLED when settings are actually saved (for example, LED settings are saved) + * If you want to force saving the current state, use serializeConfig() in your loop(). + * + * CAUTION: serializeConfig() will initiate a filesystem write operation. + * It might cause the LEDs to stutter and will cause flash wear if called too often. + * Use it sparingly and always in the loop, never in network callbacks! + * + * addToConfig() will make your settings editable through the Usermod Settings page automatically. + * + * Usermod Settings Overview: + * - Numeric values are treated as floats in the browser. + * - If the numeric value entered into the browser contains a decimal point, it will be parsed as a C float + * before being returned to the Usermod. The float data type has only 6-7 decimal digits of precision, and + * doubles are not supported, numbers will be rounded to the nearest float value when being parsed. + * The range accepted by the input field is +/- 1.175494351e-38 to +/- 3.402823466e+38. + * - If the numeric value entered into the browser doesn't contain a decimal point, it will be parsed as a + * C int32_t (range: -2147483648 to 2147483647) before being returned to the usermod. + * Overflows or underflows are truncated to the max/min value for an int32_t, and again truncated to the type + * used in the Usermod when reading the value from ArduinoJson. + * - Pin values can be treated differently from an integer value by using the key name "pin" + * - "pin" can contain a single or array of integer values + * - On the Usermod Settings page there is simple checking for pin conflicts and warnings for special pins + * - Red color indicates a conflict. Yellow color indicates a pin with a warning (e.g. an input-only pin) + * - Tip: use int8_t to store the pin value in the Usermod, so a -1 value (pin not set) can be used + * + * See usermod_v2_auto_save.h for an example that saves Flash space by reusing ArduinoJson key name strings + * + * If you need a dedicated settings page with custom layout for your Usermod, that takes a lot more work. + * You will have to add the setting to the HTML, xml.cpp and set.cpp manually. + * See the WLED Soundreactive fork (code and wiki) for reference. https://github.com/atuline/WLED + * + * I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings! + */ + void addToConfig(JsonObject& root) + { + JsonObject top = root.createNestedObject(FPSTR(_name)); + top[FPSTR(_enabled)] = enabled; + + JsonObject amic = top.createNestedObject(FPSTR(_analogmic)); + amic["pin"] = audioPin; + + JsonObject dmic = top.createNestedObject(FPSTR(_digitalmic)); + dmic[F("type")] = dmType; + JsonArray pinArray = dmic.createNestedArray("pin"); + pinArray.add(i2ssdPin); + pinArray.add(i2swsPin); + pinArray.add(i2sckPin); + pinArray.add(mclkPin); + pinArray.add(sdaPin); + pinArray.add(sclPin); + + JsonObject cfg = top.createNestedObject("cfg"); + cfg[F("squelch")] = soundSquelch; + cfg[F("gain")] = sampleGain; + cfg[F("AGC")] = soundAgc; + + JsonObject dynLim = top.createNestedObject("dynamics"); + dynLim[F("Limiter")] = limiterOn; + dynLim[F("Rise")] = attackTime; + dynLim[F("Fall")] = decayTime; + + JsonObject freqScale = top.createNestedObject("Frequency"); + freqScale[F("Scale")] = FFTScalingMode; + + JsonObject sync = top.createNestedObject("sync"); + sync[F("port")] = audioSyncPort; + sync[F("mode")] = audioSyncEnabled; + } + + + /* + * readFromConfig() can be used to read back the custom settings you added with addToConfig(). + * This is called by WLED when settings are loaded (currently this only happens immediately after boot, or after saving on the Usermod Settings page) + * + * readFromConfig() is called BEFORE setup(). This means you can use your persistent values in setup() (e.g. pin assignments, buffer sizes), + * but also that if you want to write persistent values to a dynamic buffer, you'd need to allocate it here instead of in setup. + * If you don't know what that is, don't fret. It most likely doesn't affect your use case :) + * + * Return true in case the config values returned from Usermod Settings were complete, or false if you'd like WLED to save your defaults to disk (so any missing values are editable in Usermod Settings) + * + * getJsonValue() returns false if the value is missing, or copies the value into the variable provided and returns true if the value is present + * The configComplete variable is true only if the "exampleUsermod" object and all values are present. If any values are missing, WLED will know to call addToConfig() to save them + * + * This function is guaranteed to be called on boot, but could also be called every time settings are updated + */ + bool readFromConfig(JsonObject& root) + { + JsonObject top = root[FPSTR(_name)]; + bool configComplete = !top.isNull(); + + configComplete &= getJsonValue(top[FPSTR(_enabled)], enabled); + + configComplete &= getJsonValue(top[FPSTR(_analogmic)]["pin"], audioPin); + + configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["type"], dmType); + configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["pin"][0], i2ssdPin); + configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["pin"][1], i2swsPin); + configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["pin"][2], i2sckPin); + configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["pin"][3], mclkPin); + configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["pin"][4], sdaPin); + configComplete &= getJsonValue(top[FPSTR(_digitalmic)]["pin"][5], sclPin); + + configComplete &= getJsonValue(top["cfg"][F("squelch")], soundSquelch); + configComplete &= getJsonValue(top["cfg"][F("gain")], sampleGain); + configComplete &= getJsonValue(top["cfg"][F("AGC")], soundAgc); + + configComplete &= getJsonValue(top["dynamics"][F("Limiter")], limiterOn); + configComplete &= getJsonValue(top["dynamics"][F("Rise")], attackTime); + configComplete &= getJsonValue(top["dynamics"][F("Fall")], decayTime); + + configComplete &= getJsonValue(top["Frequency"][F("Scale")], FFTScalingMode); + + configComplete &= getJsonValue(top["sync"][F("port")], audioSyncPort); + configComplete &= getJsonValue(top["sync"][F("mode")], audioSyncEnabled); + + return configComplete; + } + + + void appendConfigData() + { + oappend(SET_F("dd=addDropdown('AudioReactive','digitalmic:type');")); + #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) + oappend(SET_F("addOption(dd,'Generic Analog',0);")); + #endif + oappend(SET_F("addOption(dd,'Generic I2S',1);")); + oappend(SET_F("addOption(dd,'ES7243',2);")); + oappend(SET_F("addOption(dd,'SPH0654',3);")); + oappend(SET_F("addOption(dd,'Generic I2S with Mclk',4);")); + #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) + oappend(SET_F("addOption(dd,'Generic I2S PDM',5);")); + #endif + oappend(SET_F("dd=addDropdown('AudioReactive','cfg:AGC');")); + oappend(SET_F("addOption(dd,'Off',0);")); + oappend(SET_F("addOption(dd,'Normal',1);")); + oappend(SET_F("addOption(dd,'Vivid',2);")); + oappend(SET_F("addOption(dd,'Lazy',3);")); + + oappend(SET_F("dd=addDropdown('AudioReactive','dynamics:Limiter');")); + oappend(SET_F("addOption(dd,'Off',0);")); + oappend(SET_F("addOption(dd,'On',1);")); + oappend(SET_F("addInfo('AudioReactive:dynamics:Limiter',0,' On ');")); // 0 is field type, 1 is actual field + oappend(SET_F("addInfo('AudioReactive:dynamics:Rise',1,'ms (♪ effects only)');")); + oappend(SET_F("addInfo('AudioReactive:dynamics:Fall',1,'ms (♪ effects only)');")); + + oappend(SET_F("dd=addDropdown('AudioReactive','Frequency:Scale');")); + oappend(SET_F("addOption(dd,'None',0);")); + oappend(SET_F("addOption(dd,'Linear (Amplitude)',2);")); + oappend(SET_F("addOption(dd,'Square Root (Energy)',3);")); + oappend(SET_F("addOption(dd,'Logarithmic (Loudness)',1);")); + + oappend(SET_F("dd=addDropdown('AudioReactive','sync:mode');")); + oappend(SET_F("addOption(dd,'Off',0);")); + oappend(SET_F("addOption(dd,'Send',1);")); + oappend(SET_F("addOption(dd,'Receive',2);")); + oappend(SET_F("addInfo('AudioReactive:digitalmic:type',1,'requires reboot!');")); // 0 is field type, 1 is actual field + oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',0,'I2S SD');")); + oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',1,'I2S WS');")); + oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',2,'I2S SCK');")); + #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) + oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',3,'I2S Master CLK only use -1, 0, 1 or 3 for MCLK');")); + #else + oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',3,'I2S Master CLK');")); + #endif + oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',4,'I2C SDA');")); + oappend(SET_F("addInfo('AudioReactive:digitalmic:pin[]',5,'I2C SCL');")); + } + + + /* + * handleOverlayDraw() is called just before every show() (LED strip update frame) after effects have set the colors. + * Use this to blank out some LEDs or set them to a different color regardless of the set effect mode. + * Commonly used for custom clocks (Cronixie, 7 segment) + */ + //void handleOverlayDraw() + //{ + //strip.setPixelColor(0, RGBW32(0,0,0,0)) // set the first pixel to black + //} + + + /* + * getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!). + * This could be used in the future for the system to determine whether your usermod is installed. + */ + uint16_t getId() + { + return USERMOD_ID_AUDIOREACTIVE; + } +}; + +// strings to reduce flash memory usage (used more than twice) +const char AudioReactive::_name[] PROGMEM = "AudioReactive"; +const char AudioReactive::_enabled[] PROGMEM = "enabled"; +const char AudioReactive::_inputLvl[] PROGMEM = "inputLevel"; +const char AudioReactive::_analogmic[] PROGMEM = "analogmic"; +const char AudioReactive::_digitalmic[] PROGMEM = "digitalmic"; +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/usermods/audioreactive/audio_source.h b/usermods/audioreactive/audio_source.h new file mode 100644 index 000000000..c76b2bb07 --- /dev/null +++ b/usermods/audioreactive/audio_source.h @@ -0,0 +1,602 @@ +#pragma once + +#include +#include "wled.h" +#include +#include +#include // needed for SPH0465 timing workaround (classic ESP32) +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0) +#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32S3) && !defined(CONFIG_IDF_TARGET_ESP32C3) +#include +#include +#endif +// type of i2s_config_t.SampleRate was changed from "int" to "unsigned" in IDF 4.4.x +#define SRate_t uint32_t +#else +#define SRate_t int +#endif + +//#include +//#include +//#include +//#include + +// see https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/hw-reference/chip-series-comparison.html#related-documents +// and https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/api-reference/peripherals/i2s.html#overview-of-all-modes +#if defined(CONFIG_IDF_TARGET_ESP32C2) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C5) || defined(CONFIG_IDF_TARGET_ESP32C6) || defined(CONFIG_IDF_TARGET_ESP32H2) + // there are two things in these MCUs that could lead to problems with audio processing: + // * no floating point hardware (FPU) support - FFT uses float calculations. If done in software, a strong slow-down can be expected (between 8x and 20x) + // * single core, so FFT task might slow down other things like LED updates + #warning This audio reactive usermod does not support ESP32-C2, ESP32-C3 or ESP32-S2. +#endif + +/* ToDo: remove. ES7243 is controlled via compiler defines + Until this configuration is moved to the webinterface +*/ + +// if you have problems to get your microphone work on the left channel, uncomment the following line +//#define I2S_USE_RIGHT_CHANNEL // (experimental) define this to use right channel (digital mics only) + +// Uncomment the line below to utilize ADC1 _exclusively_ for I2S sound input. +// benefit: analog mic inputs will be sampled contiously -> better response times and less "glitches" +// WARNING: this option WILL lock-up your device in case that any other analogRead() operation is performed; +// for example if you want to read "analog buttons" +//#define I2S_GRAB_ADC1_COMPLETELY // (experimental) continously sample analog ADC microphone. WARNING will cause analogRead() lock-up + +// data type requested from the I2S driver - currently we always use 32bit +//#define I2S_USE_16BIT_SAMPLES // (experimental) define this to request 16bit - more efficient but possibly less compatible + +#ifdef I2S_USE_16BIT_SAMPLES +#define I2S_SAMPLE_RESOLUTION I2S_BITS_PER_SAMPLE_16BIT +#define I2S_datatype int16_t +#define I2S_unsigned_datatype uint16_t +#define I2S_data_size I2S_BITS_PER_CHAN_16BIT +#undef I2S_SAMPLE_DOWNSCALE_TO_16BIT +#else +#define I2S_SAMPLE_RESOLUTION I2S_BITS_PER_SAMPLE_32BIT +//#define I2S_SAMPLE_RESOLUTION I2S_BITS_PER_SAMPLE_24BIT +#define I2S_datatype int32_t +#define I2S_unsigned_datatype uint32_t +#define I2S_data_size I2S_BITS_PER_CHAN_32BIT +#define I2S_SAMPLE_DOWNSCALE_TO_16BIT +#endif + +/* There are several (confusing) options in IDF 4.4.x: + * I2S_CHANNEL_FMT_RIGHT_LEFT, I2S_CHANNEL_FMT_ALL_RIGHT and I2S_CHANNEL_FMT_ALL_LEFT stands for stereo mode, which means two channels will transport different data. + * I2S_CHANNEL_FMT_ONLY_RIGHT and I2S_CHANNEL_FMT_ONLY_LEFT they are mono mode, both channels will only transport same data. + * I2S_CHANNEL_FMT_MULTIPLE means TDM channels, up to 16 channel will available, and they are stereo as default. + * if you want to receive two channels, one is the actual data from microphone and another channel is suppose to receive 0, it's different data in two channels, you need to choose I2S_CHANNEL_FMT_RIGHT_LEFT in this case. +*/ + +#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0)) && (ESP_IDF_VERSION <= ESP_IDF_VERSION_VAL(4, 4, 3)) +// espressif bug: only_left has no sound, left and right are swapped +// https://github.com/espressif/esp-idf/issues/9635 I2S mic not working since 4.4 (IDFGH-8138) +// https://github.com/espressif/esp-idf/issues/8538 I2S channel selection issue? (IDFGH-6918) +// https://github.com/espressif/esp-idf/issues/6625 I2S: left/right channels are swapped for read (IDFGH-4826) +#ifdef I2S_USE_RIGHT_CHANNEL +#define I2S_MIC_CHANNEL I2S_CHANNEL_FMT_ONLY_LEFT +#define I2S_MIC_CHANNEL_TEXT "right channel only (work-around swapped channel bug in IDF 4.4)." +#else +//#define I2S_MIC_CHANNEL I2S_CHANNEL_FMT_ALL_LEFT +//#define I2S_MIC_CHANNEL I2S_CHANNEL_FMT_RIGHT_LEFT +#define I2S_MIC_CHANNEL I2S_CHANNEL_FMT_ONLY_RIGHT +#define I2S_MIC_CHANNEL_TEXT "left channel only (work-around swapped channel bug in IDF 4.4)." +#endif + +#else +// not swapped +#ifdef I2S_USE_RIGHT_CHANNEL +#define I2S_MIC_CHANNEL I2S_CHANNEL_FMT_ONLY_RIGHT +#define I2S_MIC_CHANNEL_TEXT "right channel only." +#else +#define I2S_MIC_CHANNEL I2S_CHANNEL_FMT_ONLY_LEFT +#define I2S_MIC_CHANNEL_TEXT "left channel only." +#endif +#endif + + +/* Interface class + AudioSource serves as base class for all microphone types + This enables accessing all microphones with one single interface + which simplifies the caller code +*/ +class AudioSource { + public: + /* All public methods are virtual, so they can be overridden + Everything but the destructor is also removed, to make sure each mic + Implementation provides its version of this function + */ + virtual ~AudioSource() {}; + + /* Initialize + This function needs to take care of anything that needs to be done + before samples can be obtained from the microphone. + */ + virtual void initialize(int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE) = 0; + + /* Deinitialize + Release all resources and deactivate any functionality that is used + by this microphone + */ + virtual void deinitialize() = 0; + + /* getSamples + Read num_samples from the microphone, and store them in the provided + buffer + */ + virtual void getSamples(float *buffer, uint16_t num_samples) = 0; + + /* check if the audio source driver was initialized successfully */ + virtual bool isInitialized(void) {return(_initialized);} + + /* identify Audiosource type - I2S-ADC or I2S-digital */ + typedef enum{Type_unknown=0, Type_I2SAdc=1, Type_I2SDigital=2} AudioSourceType; + virtual AudioSourceType getType(void) {return(Type_I2SDigital);} // default is "I2S digital source" - ADC type overrides this method + + protected: + /* Post-process audio sample - currently on needed for I2SAdcSource*/ + virtual I2S_datatype postProcessSample(I2S_datatype sample_in) {return(sample_in);} // default method can be overriden by instances (ADC) that need sample postprocessing + + // Private constructor, to make sure it is not callable except from derived classes + AudioSource(SRate_t sampleRate, int blockSize) : + _sampleRate(sampleRate), + _blockSize(blockSize), + _initialized(false) + {}; + + SRate_t _sampleRate; // Microphone sampling rate + int _blockSize; // I2S block size + bool _initialized; // Gets set to true if initialization is successful +}; + +/* Basic I2S microphone source + All functions are marked virtual, so derived classes can replace them +*/ +class I2SSource : public AudioSource { + public: + I2SSource(SRate_t sampleRate, int blockSize) : + AudioSource(sampleRate, blockSize) { + _config = { + .mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX), + .sample_rate = _sampleRate, + .bits_per_sample = I2S_SAMPLE_RESOLUTION, + .channel_format = I2S_MIC_CHANNEL, +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 2, 0) + .communication_format = i2s_comm_format_t(I2S_COMM_FORMAT_STAND_I2S), + //.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, + .intr_alloc_flags = ESP_INTR_FLAG_LEVEL2, + .dma_buf_count = 8, + .dma_buf_len = _blockSize, + .use_apll = 0, + .bits_per_chan = I2S_data_size, +#else + .communication_format = i2s_comm_format_t(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB), + .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, + .dma_buf_count = 8, + .dma_buf_len = _blockSize, + .use_apll = false +#endif + }; + } + + virtual void initialize(int8_t i2swsPin = I2S_PIN_NO_CHANGE, int8_t i2ssdPin = I2S_PIN_NO_CHANGE, int8_t i2sckPin = I2S_PIN_NO_CHANGE, int8_t mclkPin = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE) { + if (i2swsPin != I2S_PIN_NO_CHANGE && i2ssdPin != I2S_PIN_NO_CHANGE) { + if (!pinManager.allocatePin(i2swsPin, true, PinOwner::UM_Audioreactive) || + !pinManager.allocatePin(i2ssdPin, false, PinOwner::UM_Audioreactive)) { // #206 + DEBUGSR_PRINTF("\nAR: Failed to allocate I2S pins: ws=%d, sd=%d\n", i2swsPin, i2ssdPin); + return; + } + } + + // i2ssckPin needs special treatment, since it might be unused on PDM mics + if (i2sckPin != I2S_PIN_NO_CHANGE) { + if (!pinManager.allocatePin(i2sckPin, true, PinOwner::UM_Audioreactive)) { + DEBUGSR_PRINTF("\nAR: Failed to allocate I2S pins: sck=%d\n", i2sckPin); + return; + } + } else { + #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) + // This is an I2S PDM microphone, these microphones only use a clock and + // data line, to make it simpler to debug, use the WS pin as CLK and SD + // pin as DATA + _config.mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_PDM); // Change mode to pdm if clock pin not provided. PDM is not supported on ESP32-S2. PDM RX not supported on ESP32-C3 + #endif + } + + // Reserve the master clock pin if provided + _mclkPin = mclkPin; + if (mclkPin != I2S_PIN_NO_CHANGE) { + if(!pinManager.allocatePin(mclkPin, true, PinOwner::UM_Audioreactive)) return; + _routeMclk(mclkPin); + } + + _pinConfig = { +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0) + .mck_io_num = mclkPin, // "classic" ESP32 supports setting MCK on GPIO0/GPIO1/GPIO3 only. i2s_set_pin() will fail if wrong mck_io_num is provided. +#endif + .bck_io_num = i2sckPin, + .ws_io_num = i2swsPin, + .data_out_num = I2S_PIN_NO_CHANGE, + .data_in_num = i2ssdPin + }; + + esp_err_t err = i2s_driver_install(I2S_NUM_0, &_config, 0, nullptr); + if (err != ESP_OK) { + DEBUGSR_PRINTF("Failed to install i2s driver: %d\n", err); + return; + } + + err = i2s_set_pin(I2S_NUM_0, &_pinConfig); + if (err != ESP_OK) { + DEBUGSR_PRINTF("Failed to set i2s pin config: %d\n", err); + i2s_driver_uninstall(I2S_NUM_0); // uninstall already-installed driver + return; + } + +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 2, 0) + err = i2s_set_clk(I2S_NUM_0, _sampleRate, I2S_SAMPLE_RESOLUTION, I2S_CHANNEL_MONO); // set bit clocks. Also takes care of MCLK routing if needed. + if (err != ESP_OK) { + DEBUGSR_PRINTF("Failed to configure i2s clocks: %d\n", err); + i2s_driver_uninstall(I2S_NUM_0); // uninstall already-installed driver + return; + } +#endif + _initialized = true; + } + + virtual void deinitialize() { + _initialized = false; + esp_err_t err = i2s_driver_uninstall(I2S_NUM_0); + if (err != ESP_OK) { + DEBUGSR_PRINTF("Failed to uninstall i2s driver: %d\n", err); + return; + } + if (_pinConfig.ws_io_num != I2S_PIN_NO_CHANGE) pinManager.deallocatePin(_pinConfig.ws_io_num, PinOwner::UM_Audioreactive); + if (_pinConfig.data_in_num != I2S_PIN_NO_CHANGE) pinManager.deallocatePin(_pinConfig.data_in_num, PinOwner::UM_Audioreactive); + if (_pinConfig.bck_io_num != I2S_PIN_NO_CHANGE) pinManager.deallocatePin(_pinConfig.bck_io_num, PinOwner::UM_Audioreactive); + // Release the master clock pin + if (_mclkPin != I2S_PIN_NO_CHANGE) pinManager.deallocatePin(_mclkPin, PinOwner::UM_Audioreactive); + } + + virtual void getSamples(float *buffer, uint16_t num_samples) { + if (_initialized) { + esp_err_t err; + size_t bytes_read = 0; /* Counter variable to check if we actually got enough data */ + I2S_datatype newSamples[num_samples]; /* Intermediary sample storage */ + + err = i2s_read(I2S_NUM_0, (void *)newSamples, sizeof(newSamples), &bytes_read, portMAX_DELAY); + if (err != ESP_OK) { + DEBUGSR_PRINTF("Failed to get samples: %d\n", err); + return; + } + + // For correct operation, we need to read exactly sizeof(samples) bytes from i2s + if (bytes_read != sizeof(newSamples)) { + DEBUGSR_PRINTF("Failed to get enough samples: wanted: %d read: %d\n", sizeof(newSamples), bytes_read); + return; + } + + // Store samples in sample buffer and update DC offset + for (int i = 0; i < num_samples; i++) { + + newSamples[i] = postProcessSample(newSamples[i]); // perform postprocessing (needed for ADC samples) + + float currSample = 0.0f; +#ifdef I2S_SAMPLE_DOWNSCALE_TO_16BIT + currSample = (float) newSamples[i] / 65536.0f; // 32bit input -> 16bit; keeping lower 16bits as decimal places +#else + currSample = (float) newSamples[i]; // 16bit input -> use as-is +#endif + buffer[i] = currSample; + } + } + } + + protected: + void _routeMclk(int8_t mclkPin) { +#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) + // MCLK routing by writing registers is not needed any more with IDF > 4.4.0 + #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4, 4, 0) + // this way of MCLK routing only works on "classic" ESP32 + /* Enable the mclk routing depending on the selected mclk pin (ESP32: only 0,1,3) + Only I2S_NUM_0 is supported + */ + if (mclkPin == GPIO_NUM_0) { + PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0_CLK_OUT1); + WRITE_PERI_REG(PIN_CTRL,0xFFF0); + } else if (mclkPin == GPIO_NUM_1) { + PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0TXD_U, FUNC_U0TXD_CLK_OUT3); + WRITE_PERI_REG(PIN_CTRL, 0xF0F0); + } else { + PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0RXD_U, FUNC_U0RXD_CLK_OUT2); + WRITE_PERI_REG(PIN_CTRL, 0xFF00); + } + #endif +#endif + } + + i2s_config_t _config; + i2s_pin_config_t _pinConfig; + int8_t _mclkPin; +}; + +/* ES7243 Microphone + This is an I2S microphone that requires ininitialization over + I2C before I2S data can be received +*/ +class ES7243 : public I2SSource { + private: + // I2C initialization functions for ES7243 + void _es7243I2cBegin() { + Wire.begin(pin_ES7243_SDA, pin_ES7243_SCL, 100000U); + } + + void _es7243I2cWrite(uint8_t reg, uint8_t val) { +#ifndef ES7243_ADDR + Wire.beginTransmission(0x13); +#else + Wire.beginTransmission(ES7243_ADDR); +#endif + Wire.write((uint8_t)reg); + Wire.write((uint8_t)val); + Wire.endTransmission(); + } + + void _es7243InitAdc() { + _es7243I2cBegin(); + _es7243I2cWrite(0x00, 0x01); + _es7243I2cWrite(0x06, 0x00); + _es7243I2cWrite(0x05, 0x1B); + _es7243I2cWrite(0x01, 0x00); // 0x00 for 24 bit to match INMP441 - not sure if this needs adjustment to get 16bit samples from I2S + _es7243I2cWrite(0x08, 0x43); + _es7243I2cWrite(0x05, 0x13); + } + +public: + ES7243(SRate_t sampleRate, int blockSize) : + I2SSource(sampleRate, blockSize) { + _config.channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT; + }; + + void initialize(int8_t sdaPin, int8_t sclPin, int8_t i2swsPin, int8_t i2ssdPin, int8_t i2sckPin, int8_t mclkPin) { + // Reserve SDA and SCL pins of the I2C interface + if (!pinManager.allocatePin(sdaPin, true, PinOwner::HW_I2C) || + !pinManager.allocatePin(sclPin, true, PinOwner::HW_I2C)) { + return; + } + + pin_ES7243_SDA = sdaPin; + pin_ES7243_SCL = sclPin; + + // First route mclk, then configure ADC over I2C, then configure I2S + _es7243InitAdc(); + I2SSource::initialize(i2swsPin, i2ssdPin, i2sckPin, mclkPin); + } + + void deinitialize() { + // Release SDA and SCL pins of the I2C interface + pinManager.deallocatePin(pin_ES7243_SDA, PinOwner::HW_I2C); + pinManager.deallocatePin(pin_ES7243_SCL, PinOwner::HW_I2C); + I2SSource::deinitialize(); + } + + private: + int8_t pin_ES7243_SDA; + int8_t pin_ES7243_SCL; +}; + +#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) +// ADC over I2S is only availeable in "classic" ESP32 + +/* ADC over I2S Microphone + This microphone is an ADC pin sampled via the I2S interval + This allows to use the I2S API to obtain ADC samples with high sample rates + without the need of manual timing of the samples +*/ +class I2SAdcSource : public I2SSource { + public: + I2SAdcSource(SRate_t sampleRate, int blockSize) : + I2SSource(sampleRate, blockSize) { + _config = { + .mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_ADC_BUILT_IN), + .sample_rate = _sampleRate, + .bits_per_sample = I2S_SAMPLE_RESOLUTION, + .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 2, 0) + .communication_format = i2s_comm_format_t(I2S_COMM_FORMAT_STAND_I2S), +#else + .communication_format = i2s_comm_format_t(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB), +#endif + .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, + .dma_buf_count = 8, + .dma_buf_len = _blockSize, + .use_apll = false, + .tx_desc_auto_clear = false, + .fixed_mclk = 0 + }; + } + + /* identify Audiosource type - I2S-ADC*/ + AudioSourceType getType(void) {return(Type_I2SAdc);} + + void initialize(int8_t audioPin, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE) { + _myADCchannel = 0x0F; + if(!pinManager.allocatePin(audioPin, false, PinOwner::UM_Audioreactive)) { + DEBUGSR_PRINTF("failed to allocate GPIO for audio analog input: %d\n", audioPin); + return; + } + _audioPin = audioPin; + + // Determine Analog channel. Only Channels on ADC1 are supported + int8_t channel = digitalPinToAnalogChannel(_audioPin); + if (channel > 9) { + DEBUGSR_PRINTF("Incompatible GPIO used for audio in: %d\n", _audioPin); + return; + } else { + adc_gpio_init(ADC_UNIT_1, adc_channel_t(channel)); + _myADCchannel = channel; + } + + // Install Driver + esp_err_t err = i2s_driver_install(I2S_NUM_0, &_config, 0, nullptr); + if (err != ESP_OK) { + DEBUGSR_PRINTF("Failed to install i2s driver: %d\n", err); + return; + } + + adc1_config_width(ADC_WIDTH_BIT_12); // ensure that ADC runs with 12bit resolution + + // Enable I2S mode of ADC + err = i2s_set_adc_mode(ADC_UNIT_1, adc1_channel_t(channel)); + if (err != ESP_OK) { + DEBUGSR_PRINTF("Failed to set i2s adc mode: %d\n", err); + return; + } + + // see example in https://github.com/espressif/arduino-esp32/blob/master/libraries/ESP32/examples/I2S/HiFreq_ADC/HiFreq_ADC.ino + adc1_config_channel_atten(adc1_channel_t(channel), ADC_ATTEN_DB_11); // configure ADC input amplification + + #if defined(I2S_GRAB_ADC1_COMPLETELY) + // according to docs from espressif, the ADC needs to be started explicitly + // fingers crossed + err = i2s_adc_enable(I2S_NUM_0); + if (err != ESP_OK) { + DEBUGSR_PRINTF("Failed to enable i2s adc: %d\n", err); + //return; + } + #else + err = i2s_adc_disable(I2S_NUM_0); + //err = i2s_stop(I2S_NUM_0); + if (err != ESP_OK) { + DEBUGSR_PRINTF("Failed to initially disable i2s adc: %d\n", err); + } + #endif + + _initialized = true; + } + + + I2S_datatype postProcessSample(I2S_datatype sample_in) { + static I2S_datatype lastADCsample = 0; // last good sample + static unsigned int broken_samples_counter = 0; // number of consecutive broken (and fixed) ADC samples + I2S_datatype sample_out = 0; + + // bring sample down down to 16bit unsigned + I2S_unsigned_datatype rawData = * reinterpret_cast (&sample_in); // C++ acrobatics to get sample as "unsigned" + #ifndef I2S_USE_16BIT_SAMPLES + rawData = (rawData >> 16) & 0xFFFF; // scale input down from 32bit -> 16bit + I2S_datatype lastGoodSample = lastADCsample / 16384 ; // prepare "last good sample" accordingly (26bit-> 12bit with correct sign handling) + #else + rawData = rawData & 0xFFFF; // input is already in 16bit, just mask off possible junk + I2S_datatype lastGoodSample = lastADCsample * 4; // prepare "last good sample" accordingly (10bit-> 12bit) + #endif + + // decode ADC sample data fields + uint16_t the_channel = (rawData >> 12) & 0x000F; // upper 4 bit = ADC channel + uint16_t the_sample = rawData & 0x0FFF; // lower 12bit -> ADC sample (unsigned) + I2S_datatype finalSample = (int(the_sample) - 2048); // convert unsigned sample to signed (centered at 0); + + if ((the_channel != _myADCchannel) && (_myADCchannel != 0x0F)) { // 0x0F means "don't know what my channel is" + // fix bad sample + finalSample = lastGoodSample; // replace with last good ADC sample + broken_samples_counter ++; + if (broken_samples_counter > 256) _myADCchannel = 0x0F; // too many bad samples in a row -> disable sample corrections + //Serial.print("\n!ADC rogue sample 0x"); Serial.print(rawData, HEX); Serial.print("\tchannel:");Serial.println(the_channel); + } else broken_samples_counter = 0; // good sample - reset counter + + // back to original resolution + #ifndef I2S_USE_16BIT_SAMPLES + finalSample = finalSample << 16; // scale up from 16bit -> 32bit; + #endif + + finalSample = finalSample / 4; // mimic old analog driver behaviour (12bit -> 10bit) + sample_out = (3 * finalSample + lastADCsample) / 4; // apply low-pass filter (2-tap FIR) + //sample_out = (finalSample + lastADCsample) / 2; // apply stronger low-pass filter (2-tap FIR) + + lastADCsample = sample_out; // update ADC last sample + return(sample_out); + } + + + void getSamples(float *buffer, uint16_t num_samples) { + /* Enable ADC. This has to be enabled and disabled directly before and + * after sampling, otherwise Wifi dies + */ + if (_initialized) { + #if !defined(I2S_GRAB_ADC1_COMPLETELY) + // old code - works for me without enable/disable, at least on ESP32. + //esp_err_t err = i2s_start(I2S_NUM_0); + esp_err_t err = i2s_adc_enable(I2S_NUM_0); + if (err != ESP_OK) { + DEBUGSR_PRINTF("Failed to enable i2s adc: %d\n", err); + return; + } + #endif + + I2SSource::getSamples(buffer, num_samples); + + #if !defined(I2S_GRAB_ADC1_COMPLETELY) + // old code - works for me without enable/disable, at least on ESP32. + err = i2s_adc_disable(I2S_NUM_0); //i2s_adc_disable() may cause crash with IDF 4.4 (https://github.com/espressif/arduino-esp32/issues/6832) + //err = i2s_stop(I2S_NUM_0); + if (err != ESP_OK) { + DEBUGSR_PRINTF("Failed to disable i2s adc: %d\n", err); + return; + } + #endif + } + } + + void deinitialize() { + pinManager.deallocatePin(_audioPin, PinOwner::UM_Audioreactive); + _initialized = false; + _myADCchannel = 0x0F; + + esp_err_t err; + #if defined(I2S_GRAB_ADC1_COMPLETELY) + // according to docs from espressif, the ADC needs to be stopped explicitly + // fingers crossed + err = i2s_adc_disable(I2S_NUM_0); + if (err != ESP_OK) { + DEBUGSR_PRINTF("Failed to disable i2s adc: %d\n", err); + } + #endif + + i2s_stop(I2S_NUM_0); + err = i2s_driver_uninstall(I2S_NUM_0); + if (err != ESP_OK) { + DEBUGSR_PRINTF("Failed to uninstall i2s driver: %d\n", err); + return; + } + } + + private: + int8_t _audioPin; + int8_t _myADCchannel = 0x0F; // current ADC channel for analog input. 0x0F means "undefined" +}; +#endif + +/* SPH0645 Microphone + This is an I2S microphone with some timing quirks that need + special consideration. +*/ + +// https://github.com/espressif/esp-idf/issues/7192 SPH0645 i2s microphone issue when migrate from legacy esp-idf version (IDFGH-5453) +// a user recommended this: Try to set .communication_format to I2S_COMM_FORMAT_STAND_I2S and call i2s_set_clk() after i2s_set_pin(). +class SPH0654 : public I2SSource { + public: + SPH0654(SRate_t sampleRate, int blockSize) : + I2SSource(sampleRate, blockSize) + {} + + void initialize(uint8_t i2swsPin, uint8_t i2ssdPin, uint8_t i2sckPin, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE, int8_t = I2S_PIN_NO_CHANGE) { + I2SSource::initialize(i2swsPin, i2ssdPin, i2sckPin); +#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) +// these registers are only existing in "classic" ESP32 + REG_SET_BIT(I2S_TIMING_REG(I2S_NUM_0), BIT(9)); + REG_SET_BIT(I2S_CONF_REG(I2S_NUM_0), I2S_RX_MSB_SHIFT); +#else + #warning FIX ME! Please. +#endif + } +}; diff --git a/usermods/audioreactive/readme.md b/usermods/audioreactive/readme.md new file mode 100644 index 000000000..f14c168dc --- /dev/null +++ b/usermods/audioreactive/readme.md @@ -0,0 +1,36 @@ +# Audioreactive usermod + +This usermod allows controlling LEDs using audio input. Audio input can be either microphone or analog-in (AUX) using appropriate adapter. +Supported microphones range from analog (MAX4466, MAX9814, ...) to digital (INMP441, ICS-43434, ...). + +The usermod does audio processing and provides data structure that specially written effect can use. + +The usermod **does not** provide effects or draws anything to LED strip/matrix. + +## Installation + +Add `-D USERMOD_AUDIOREACTIVE` to your PlatformIO environment as well as `arduinoFFT` to your `lib_deps`. +If you are not using PlatformIO (which you should) try adding `#define USERMOD_AUDIOREACTIVE` to *my_config.h* and make sure you have _arduinoFFT_ library downloaded and installed. + +Customised _arduinoFFT_ library for use with this usermod can be found at https://github.com/blazoncek/arduinoFFT.git + +## Configuration + +All parameters are runtime configurable though some may require hard boot after change (I2S microphone or selected GPIOs). + +If you want to define default GPIOs during compile time use the following (default values in parentheses): + +- `DMTYPE=x` : defines digital microphone type: 0=analog, 1=generic I2S, 2=ES7243 I2S, 3=SPH0645 I2S, 4=generic I2S with master clock, 5=PDM I2S +- `AUDIOPIN=x` : GPIO for analog microphone/AUX-in (36) +- `I2S_SDPIN=x` : GPIO for SD pin on digital mcrophone (32) +- `I2S_WSPIN=x` : GPIO for WS pin on digital mcrophone (15) +- `I2S_CKPIN=x` : GPIO for SCK pin on digital mcrophone (14) +- `ES7243_SDAPIN` : GPIO for I2C SDA pin on ES7243 microphone (-1) +- `ES7243_SCLPIN` : GPIO for I2C SCL pin on ES7243 microphone (-1) +- `MCLK_PIN=x` : GPIO for master clock pin on digital mcrophone (-1) + +**NOTE** Due to the fact that usermod uses I2S peripherial for analog audio sampling, use of analog *buttons* (i.e. potentiometers) is disabled while running this usermod with analog microphone. + +## Release notes + +2022-06 Ported from [soundreactive](https://github.com/atuline/WLED) by @blazoncek (AKA Blaz Kristan) diff --git a/usermods/mpu6050_imu/usermod_mpu6050_imu.h b/usermods/mpu6050_imu/usermod_mpu6050_imu.h index 4aa2a128f..4ce51c433 100644 --- a/usermods/mpu6050_imu/usermod_mpu6050_imu.h +++ b/usermods/mpu6050_imu/usermod_mpu6050_imu.h @@ -42,14 +42,6 @@ #include "Wire.h" #endif -#ifdef ARDUINO_ARCH_ESP32 - #define HW_PIN_SCL 22 - #define HW_PIN_SDA 21 -#else - #define HW_PIN_SCL 5 - #define HW_PIN_SDA 4 -#endif - // ================================================================ // === INTERRUPT DETECTION ROUTINE === // ================================================================ @@ -93,7 +85,7 @@ class MPU6050Driver : public Usermod { * setup() is called once at boot. WiFi is not yet connected at this point. */ void setup() { - PinManagerPinType pins[2] = { { HW_PIN_SCL, true }, { HW_PIN_SDA, true } }; + PinManagerPinType pins[2] = { { i2c_scl, true }, { i2c_sda, true } }; if (!pinManager.allocateMultiplePins(pins, 2, PinOwner::HW_I2C)) { enabled = false; return; } // join I2C bus (I2Cdev library doesn't do this automatically) #if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE @@ -258,20 +250,20 @@ class MPU6050Driver : public Usermod { * addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object). * Values in the state object may be modified by connected clients */ - void addToJsonState(JsonObject& root) - { + //void addToJsonState(JsonObject& root) + //{ //root["user0"] = userVar0; - } + //} /* * readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object). * Values in the state object may be modified by connected clients */ - void readFromJsonState(JsonObject& root) - { + //void readFromJsonState(JsonObject& root) + //{ //if (root["bri"] == 255) DEBUG_PRINTLN(F("Don't burn down your garage!")); - } + //} /* @@ -279,13 +271,13 @@ class MPU6050Driver : public Usermod { * It will be called by WLED when settings are actually saved (for example, LED settings are saved) * I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings! */ - void addToConfig(JsonObject& root) - { - JsonObject top = root.createNestedObject("MPU6050_IMU"); - JsonArray pins = top.createNestedArray("pin"); - pins.add(HW_PIN_SCL); - pins.add(HW_PIN_SDA); - } +// void addToConfig(JsonObject& root) +// { +// JsonObject top = root.createNestedObject("MPU6050_IMU"); +// JsonArray pins = top.createNestedArray("pin"); +// pins.add(HW_PIN_SCL); +// pins.add(HW_PIN_SDA); +// } /* * getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!). diff --git a/usermods/multi_relay/usermod_multi_relay.h b/usermods/multi_relay/usermod_multi_relay.h index b38c8a25c..7381a00df 100644 --- a/usermods/multi_relay/usermod_multi_relay.h +++ b/usermods/multi_relay/usermod_multi_relay.h @@ -80,7 +80,7 @@ class MultiRelay : public Usermod { void handleOffTimer() { unsigned long now = millis(); bool activeRelays = false; - for (uint8_t i=0; i 0 && now - _switchTimerStart > (_relay[i].delay*1000)) { if (!_relay[i].external) toggleRelay(i); _relay[i].active = false; @@ -182,7 +182,7 @@ class MultiRelay : public Usermod { */ MultiRelay() { const int8_t defPins[] = {MULTI_RELAY_PINS}; - for (uint8_t i=0; i=0) count++; + for (int i=0; i=0) count++; return count; } @@ -268,7 +268,7 @@ class MultiRelay : public Usermod { strcat_P(subuf, PSTR("/relay/#")); mqtt->subscribe(subuf, 0); if (HAautodiscovery) publishHomeAssistantAutodiscovery(); - for (uint8_t i=0; i=0 && !_relay[i].external) _relay[i].active = true; } } @@ -382,7 +382,7 @@ class MultiRelay : public Usermod { } bool handled = false; - for (uint8_t i=0; i WLED_DEBOUNCE_THRESHOLD) { //fire edge event only after 50ms without change (debounce) - for (uint8_t i=0; i=0 && _relay[i].button == b) { + for (int i=0; i 350 && !buttonPressedBefore[b]) { buttonWaitTime[b] = 0; //shortPressAction(b); //not exposed - for (uint8_t i=0; i=0 && _relay[i].button == b) { + for (int i=0; i"); - uiDomString += F("Relay "); - uiDomString += i; - uiDomString += F(" "); - JsonArray infoArr = user.createNestedArray(uiDomString); // timer value - - infoArr.add(_relay[i].state ? "on" : "off"); + uiDomString += F(""); + infoArr.add(uiDomString); } } } @@ -505,7 +507,7 @@ class MultiRelay : public Usermod { } #if MULTI_RELAY_MAX_RELAYS > 1 JsonArray rel_arr = multiRelay.createNestedArray(F("relays")); - for (uint8_t i=0; i() && usermod[FPSTR(_relay_str)].is() && usermod[FPSTR(_relay_str)].as()>=0) { - switchRelay(usermod[FPSTR(_relay_str)].as(), usermod["on"].as()); + if (usermod[FPSTR(_relay_str)].is() && usermod[FPSTR(_relay_str)].as()>=0) { + int rly = usermod[FPSTR(_relay_str)].as(); + if (usermod["on"].is()) { + switchRelay(rly, usermod["on"].as()); + } else if (usermod["on"].is() && usermod["on"].as()[0] == 't') { + toggleRelay(rly); + } } } else if (root[FPSTR(_name)].is()) { JsonArray relays = root[FPSTR(_name)].as(); for (JsonVariant r : relays) { - if (r["on"].is() && r[FPSTR(_relay_str)].is() && r[FPSTR(_relay_str)].as()>=0) { - switchRelay(r[FPSTR(_relay_str)].as(), r["on"].as()); + if (r[FPSTR(_relay_str)].is() && r[FPSTR(_relay_str)].as()>=0) { + int rly = r[FPSTR(_relay_str)].as(); + if (r["on"].is()) { + switchRelay(rly, r["on"].as()); + } else if (r["on"].is() && r["on"].as()[0] == 't') { + toggleRelay(rly); + } } } } @@ -546,7 +558,7 @@ class MultiRelay : public Usermod { top[FPSTR(_enabled)] = enabled; top[FPSTR(_broadcast)] = periodicBroadcastSec; - for (uint8_t i=0; i=0) { pinManager.deallocatePin(oldPin[i], PinOwner::UM_MultiRelay); } // allocate new pins - for (uint8_t i=0; i=0 && pinManager.allocatePin(_relay[i].pin, true, PinOwner::UM_MultiRelay)) { if (!_relay[i].external) { _relay[i].state = !offMode; diff --git a/usermods/stairway_wipe_basic/stairway-wipe-usermod-v2.h b/usermods/stairway_wipe_basic/stairway-wipe-usermod-v2.h index 08d551be0..238ec7d9c 100644 --- a/usermods/stairway_wipe_basic/stairway-wipe-usermod-v2.h +++ b/usermods/stairway_wipe_basic/stairway-wipe-usermod-v2.h @@ -96,7 +96,7 @@ class StairwayWipeUsermod : public Usermod { resetTimebase(); //make sure wipe starts from beginning //set wipe direction - WS2812FX::Segment& seg = strip.getSegment(0); + Segment& seg = strip.getSegment(0); bool doReverse = (userVar0 == 2); seg.setOption(1, doReverse); diff --git a/usermods/stairway_wipe_basic/wled06_usermod.ino b/usermods/stairway_wipe_basic/wled06_usermod.ino index eeece4438..c1264ebfb 100644 --- a/usermods/stairway_wipe_basic/wled06_usermod.ino +++ b/usermods/stairway_wipe_basic/wled06_usermod.ino @@ -89,7 +89,7 @@ void startWipe() resetTimebase(); //make sure wipe starts from beginning //set wipe direction - WS2812FX::Segment& seg = strip.getSegment(0); + Segment& seg = strip.getSegment(0); bool doReverse = (userVar0 == 2); seg.setOption(1, doReverse); diff --git a/usermods/usermod_v2_auto_save/readme.md b/usermods/usermod_v2_auto_save/readme.md index 7cf81085e..8d00fcca7 100644 --- a/usermods/usermod_v2_auto_save/readme.md +++ b/usermods/usermod_v2_auto_save/readme.md @@ -1,20 +1,17 @@ # Auto Save -v2 Usermod to automatically save settings -to preset number AUTOSAVE_PRESET_NUM after a change to any of - +v2 Usermod to automatically save settings +to preset number AUTOSAVE_PRESET_NUM after a change to any of: * brightness * effect speed * effect intensity * mode (effect) * palette -but it will wait for AUTOSAVE_SETTLE_MS milliseconds, a "settle" -period in case there are other changes (any change will -extend the "settle" window). +but it will wait for AUTOSAVE_AFTER_SEC seconds, +a "settle" period in case there are other changes (any change will extend the "settle" window). -It will additionally load preset AUTOSAVE_PRESET_NUM at startup. -during the first `loop()`. Reasoning below. +It will additionally load preset AUTOSAVE_PRESET_NUM at startup during the first `loop()`. AutoSaveUsermod is standalone, but if FourLineDisplayUsermod is installed, it will notify the user of the saved changes. @@ -28,10 +25,21 @@ This file should be placed in the same directory as `platformio.ini`. ### Define Your Options -* `USERMOD_AUTO_SAVE` - define this to have this the Auto Save usermod included wled00\usermods_list.cpp -* `USERMOD_FOUR_LINE_DISPLAY` - define this to have this the Four Line Display mod included wled00\usermods_list.cpp - also tells this usermod that the display is available (see the Four Line Display usermod `readme.md` for more details) +* `USERMOD_AUTO_SAVE` - define this to have this the Auto Save usermod included wled00\usermods_list.cpp +* `AUTOSAVE_AFTER_SEC` - define the delay time after the settings auto-saving routine should be executed +* `AUTOSAVE_PRESET_NUM` - define the preset number used by autosave usermod +* `USERMOD_AUTO_SAVE_ON_BOOT` - define if autosave should be enabled on boot +* `USERMOD_FOUR_LINE_DISPLAY` - define this to have this the Four Line Display mod included wled00\usermods_list.cpp + also tells this usermod that the display is available + (see the Four Line Display usermod `readme.md` for more details) -You can configure auto-save parameters using Usermods settings page. +Example to add in platformio_override: + -D USERMOD_AUTO_SAVE + -D AUTOSAVE_AFTER_SEC=10 + -D AUTOSAVE_PRESET_NUM=100 + -D USERMOD_AUTO_SAVE_ON_BOOT=true + +You can also configure auto-save parameters using Usermods settings page. ### PlatformIO requirements diff --git a/usermods/usermod_v2_auto_save/usermod_v2_auto_save.h b/usermods/usermod_v2_auto_save/usermod_v2_auto_save.h index c289cc32a..41e0a7ecf 100644 --- a/usermods/usermod_v2_auto_save/usermod_v2_auto_save.h +++ b/usermods/usermod_v2_auto_save/usermod_v2_auto_save.h @@ -33,9 +33,23 @@ class AutoSaveUsermod : public Usermod { bool enabled = true; // configurable parameters + #ifdef AUTOSAVE_AFTER_SEC + uint16_t autoSaveAfterSec = AUTOSAVE_AFTER_SEC; + #else uint16_t autoSaveAfterSec = 15; // 15s by default + #endif + + #ifdef AUTOSAVE_PRESET_NUM + uint8_t autoSavePreset = AUTOSAVE_PRESET_NUM; + #else uint8_t autoSavePreset = 250; // last possible preset + #endif + + #ifdef USERMOD_AUTO_SAVE_ON_BOOT + bool applyAutoSaveOnBoot = USERMOD_AUTO_SAVE_ON_BOOT; + #else bool applyAutoSaveOnBoot = false; // do we load auto-saved preset on boot? + #endif // If we've detected the need to auto save, this will be non zero. unsigned long autoSaveAfter = 0; @@ -64,6 +78,7 @@ class AutoSaveUsermod : public Usermod { PSTR("~ %02d-%02d %02d:%02d:%02d ~"), month(localTime), day(localTime), hour(localTime), minute(localTime), second(localTime)); + cacheInvalidate++; // force reload of presets savePreset(autoSavePreset, presetNameBuffer); } diff --git a/usermods/usermod_v2_four_line_display/usermod_v2_four_line_display.h b/usermods/usermod_v2_four_line_display/usermod_v2_four_line_display.h index a49961dd5..3fcf66128 100644 --- a/usermods/usermod_v2_four_line_display/usermod_v2_four_line_display.h +++ b/usermods/usermod_v2_four_line_display/usermod_v2_four_line_display.h @@ -24,54 +24,31 @@ // //The SCL and SDA pins are defined here. +#ifndef FLD_PIN_SCL + #define FLD_PIN_SCL i2c_scl +#endif +#ifndef FLD_PIN_SDA + #define FLD_PIN_SDA i2c_sda +#endif +#ifndef FLD_PIN_CLOCKSPI + #define FLD_PIN_CLOCKSPI spi_sclk +#endif + #ifndef FLD_PIN_DATASPI + #define FLD_PIN_DATASPI spi_mosi +#endif +#ifndef FLD_PIN_CS + #define FLD_PIN_CS spi_cs +#endif #ifdef ARDUINO_ARCH_ESP32 - #define HW_PIN_SCL 22 - #define HW_PIN_SDA 21 - #define HW_PIN_CLOCKSPI 18 - #define HW_PIN_DATASPI 23 - #ifndef FLD_PIN_SCL - #define FLD_PIN_SCL 22 - #endif - #ifndef FLD_PIN_SDA - #define FLD_PIN_SDA 21 - #endif - #ifndef FLD_PIN_CLOCKSPI - #define FLD_PIN_CLOCKSPI 18 - #endif - #ifndef FLD_PIN_DATASPI - #define FLD_PIN_DATASPI 23 - #endif #ifndef FLD_PIN_DC #define FLD_PIN_DC 19 #endif - #ifndef FLD_PIN_CS - #define FLD_PIN_CS 5 - #endif #ifndef FLD_PIN_RESET #define FLD_PIN_RESET 26 #endif #else - #define HW_PIN_SCL 5 - #define HW_PIN_SDA 4 - #define HW_PIN_CLOCKSPI 14 - #define HW_PIN_DATASPI 13 - #ifndef FLD_PIN_SCL - #define FLD_PIN_SCL 5 - #endif - #ifndef FLD_PIN_SDA - #define FLD_PIN_SDA 4 - #endif - #ifndef FLD_PIN_CLOCKSPI - #define FLD_PIN_CLOCKSPI 14 - #endif - #ifndef FLD_PIN_DATASPI - #define FLD_PIN_DATASPI 13 - #endif #ifndef FLD_PIN_DC #define FLD_PIN_DC 12 - #endif - #ifndef FLD_PIN_CS - #define FLD_PIN_CS 15 #endif #ifndef FLD_PIN_RESET #define FLD_PIN_RESET 16 @@ -192,13 +169,14 @@ class FourLineDisplayUsermod : public Usermod { bool isHW; PinOwner po = PinOwner::UM_FourLineDisplay; if (type == SSD1306_SPI || type == SSD1306_SPI64) { - isHW = (ioPin[0]==HW_PIN_CLOCKSPI && ioPin[1]==HW_PIN_DATASPI); + isHW = (ioPin[0]==spi_sclk && ioPin[1]==spi_mosi); + if (isHW) po = PinOwner::HW_SPI; // allow multiple allocations of HW I2C bus pins PinManagerPinType pins[5] = { { ioPin[0], true }, { ioPin[1], true }, { ioPin[2], true }, { ioPin[3], true }, { ioPin[4], true }}; if (!pinManager.allocateMultiplePins(pins, 5, po)) { type=NONE; return; } } else { - isHW = (ioPin[0]==HW_PIN_SCL && ioPin[1]==HW_PIN_SDA); + isHW = (ioPin[0]==i2c_scl && ioPin[1]==i2c_sda); + if (isHW) po = PinOwner::HW_I2C; // allow multiple allocations of HW I2C bus pins PinManagerPinType pins[2] = { { ioPin[0], true }, { ioPin[1], true } }; - if (ioPin[0]==HW_PIN_SCL && ioPin[1]==HW_PIN_SDA) po = PinOwner::HW_I2C; // allow multiple allocations of HW I2C bus pins if (!pinManager.allocateMultiplePins(pins, 2, po)) { type=NONE; return; } } @@ -718,8 +696,14 @@ class FourLineDisplayUsermod : public Usermod { if (pinsChanged || type!=newType) { if (type != NONE) delete u8x8; PinOwner po = PinOwner::UM_FourLineDisplay; - if (ioPin[0]==HW_PIN_SCL && ioPin[1]==HW_PIN_SDA) po = PinOwner::HW_I2C; // allow multiple allocations of HW I2C bus pins - pinManager.deallocateMultiplePins((const uint8_t *)ioPin, (type == SSD1306_SPI || type == SSD1306_SPI64) ? 5 : 2, po); + bool isSPI = (type == SSD1306_SPI || type == SSD1306_SPI64); + if (isSPI) { + if (ioPin[0]==spi_sclk && ioPin[1]==spi_mosi) po = PinOwner::HW_SPI; // allow multiple allocations of HW SPI bus pins + pinManager.deallocateMultiplePins((const uint8_t *)ioPin, 5, po); + } else { + if (ioPin[0]==i2c_scl && ioPin[1]==i2c_sda) po = PinOwner::HW_I2C; // allow multiple allocations of HW I2C bus pins + pinManager.deallocateMultiplePins((const uint8_t *)ioPin, 2, po); + } for (byte i=0; i<5; i++) ioPin[i] = newPin[i]; if (ioPin[0]<0 || ioPin[1]<0) { // data & clock must be > -1 type = NONE; diff --git a/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h b/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h index 383accc52..2c3731587 100644 --- a/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h +++ b/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h @@ -25,54 +25,32 @@ // //The SCL and SDA pins are defined here. +#ifndef FLD_PIN_SCL + #define FLD_PIN_SCL i2c_scl +#endif +#ifndef FLD_PIN_SDA + #define FLD_PIN_SDA i2c_sda +#endif +#ifndef FLD_PIN_CLOCKSPI + #define FLD_PIN_CLOCKSPI spi_sclk +#endif + #ifndef FLD_PIN_DATASPI + #define FLD_PIN_DATASPI spi_mosi +#endif +#ifndef FLD_PIN_CS + #define FLD_PIN_CS spi_cs +#endif + #ifdef ARDUINO_ARCH_ESP32 - #define HW_PIN_SCL 22 - #define HW_PIN_SDA 21 - #define HW_PIN_CLOCKSPI 18 - #define HW_PIN_DATASPI 23 - #ifndef FLD_PIN_SCL - #define FLD_PIN_SCL 22 - #endif - #ifndef FLD_PIN_SDA - #define FLD_PIN_SDA 21 - #endif - #ifndef FLD_PIN_CLOCKSPI - #define FLD_PIN_CLOCKSPI 18 - #endif - #ifndef FLD_PIN_DATASPI - #define FLD_PIN_DATASPI 23 - #endif #ifndef FLD_PIN_DC #define FLD_PIN_DC 19 #endif - #ifndef FLD_PIN_CS - #define FLD_PIN_CS 5 - #endif #ifndef FLD_PIN_RESET #define FLD_PIN_RESET 26 #endif #else - #define HW_PIN_SCL 5 - #define HW_PIN_SDA 4 - #define HW_PIN_CLOCKSPI 14 - #define HW_PIN_DATASPI 13 - #ifndef FLD_PIN_SCL - #define FLD_PIN_SCL 5 - #endif - #ifndef FLD_PIN_SDA - #define FLD_PIN_SDA 4 - #endif - #ifndef FLD_PIN_CLOCKSPI - #define FLD_PIN_CLOCKSPI 14 - #endif - #ifndef FLD_PIN_DATASPI - #define FLD_PIN_DATASPI 13 - #endif #ifndef FLD_PIN_DC #define FLD_PIN_DC 12 - #endif - #ifndef FLD_PIN_CS - #define FLD_PIN_CS 15 #endif #ifndef FLD_PIN_RESET #define FLD_PIN_RESET 16 @@ -92,13 +70,20 @@ #define SCREEN_TIMEOUT_MS 60*1000 // 1 min // Minimum time between redrawing screen in ms -#define USER_LOOP_REFRESH_RATE_MS 1000 +#define REFRESH_RATE_MS 1000 // Extra char (+1) for null #define LINE_BUFFER_SIZE 16+1 #define MAX_JSON_CHARS 19+1 #define MAX_MODE_LINE_SPACE 13+1 + +#ifdef ARDUINO_ARCH_ESP32 +static TaskHandle_t Display_Task = nullptr; +void DisplayTaskCode(void * parameter); +#endif + + typedef enum { NONE = 0, SSD1306, // U8X8_SSD1306_128X32_UNIVISION_HW_I2C @@ -112,10 +97,15 @@ typedef enum { class FourLineDisplayUsermod : public Usermod { + public: + FourLineDisplayUsermod() { if (!instance) instance = this; } + static FourLineDisplayUsermod* getInstance(void) { return instance; } private: + static FourLineDisplayUsermod *instance; bool initDone = false; + volatile bool drawing = false; // HW interface & configuration U8X8 *u8x8 = nullptr; // pointer to U8X8 display object @@ -132,8 +122,8 @@ class FourLineDisplayUsermod : public Usermod { bool flip = false; // flip display 180° uint8_t contrast = 10; // screen contrast uint8_t lineHeight = 1; // 1 row or 2 rows - uint16_t refreshRate = USER_LOOP_REFRESH_RATE_MS; // in ms - uint32_t screenTimeout = SCREEN_TIMEOUT_MS; // in ms + uint16_t refreshRate = REFRESH_RATE_MS; // in ms + uint32_t screenTimeout = SCREEN_TIMEOUT_MS; // in ms bool sleepMode = true; // allow screen sleep? bool clockMode = false; // display clock bool showSeconds = true; // display clock with seconds @@ -195,6 +185,123 @@ class FourLineDisplayUsermod : public Usermod { u8x8_cad_EndTransfer(u8x8_struct); } + /** + * Wrappers for screen drawing + */ + void setFlipMode(uint8_t mode) { + if (type == NONE || !enabled) return; + u8x8->setFlipMode(mode); + } + void setContrast(uint8_t contrast) { + if (type == NONE || !enabled) return; + u8x8->setContrast(contrast); + } + void drawString(uint8_t col, uint8_t row, const char *string, bool ignoreLH=false) { + if (type == NONE || !enabled) return; + u8x8->setFont(u8x8_font_chroma48medium8_r); + if (!ignoreLH && lineHeight==2) u8x8->draw1x2String(col, row, string); + else u8x8->drawString(col, row, string); + } + void draw2x2String(uint8_t col, uint8_t row, const char *string) { + if (type == NONE || !enabled) return; + u8x8->setFont(u8x8_font_chroma48medium8_r); + u8x8->draw2x2String(col, row, string); + } + void drawGlyph(uint8_t col, uint8_t row, char glyph, const uint8_t *font, bool ignoreLH=false) { + if (type == NONE || !enabled) return; + u8x8->setFont(font); + if (!ignoreLH && lineHeight==2) u8x8->draw1x2Glyph(col, row, glyph); + else u8x8->drawGlyph(col, row, glyph); + } + void draw2x2Glyph(uint8_t col, uint8_t row, char glyph, const uint8_t *font) { + if (type == NONE || !enabled) return; + u8x8->setFont(font); + u8x8->draw2x2Glyph(col, row, glyph); + } + uint8_t getCols() { + if (type==NONE || !enabled) return 0; + return u8x8->getCols(); + } + void clear() { + if (type == NONE || !enabled) return; + u8x8->clear(); + } + void setPowerSave(uint8_t save) { + if (type == NONE || !enabled) return; + u8x8->setPowerSave(save); + } + + void center(String &line, uint8_t width) { + int len = line.length(); + if (len0; i--) line = ' ' + line; + for (byte i=line.length(); i 11) { AmPmHour -= 12; isitAM = false; } + if (AmPmHour == 0) { AmPmHour = 12; } + } + if (knownHour != hourCurrent) { + // only update date when hour changes + sprintf_P(lineBuffer, PSTR("%s %2d "), monthShortStr(month(localTime)), day(localTime)); + draw2x2String(2, lineHeight==1 ? 0 : lineHeight, lineBuffer); // adjust for 8 line displays, draw month and day + } + sprintf_P(lineBuffer,PSTR("%2d:%02d"), (useAMPM ? AmPmHour : hourCurrent), minuteCurrent); + draw2x2String(2, lineHeight*2, lineBuffer); //draw hour, min. blink ":" depending on odd/even seconds + if (useAMPM) drawString(12, lineHeight*2, (isitAM ? "AM" : "PM"), true); //draw am/pm if using 12 time + + drawStatusIcons(); //icons power, wifi, timer, etc + + knownMinute = minuteCurrent; + knownHour = hourCurrent; + } + if (showSeconds && secondCurrent != lastSecond) { + lastSecond = secondCurrent; + draw2x2String(6, lineHeight*2, secondCurrent%2 ? " " : ":"); + sprintf_P(lineBuffer, PSTR("%02d"), secondCurrent); + drawString(12, lineHeight*2+1, lineBuffer, true); // even with double sized rows print seconds in 1 line + } + drawing = false; + } + public: // gets called once at boot. Do all initialization that doesn't depend on @@ -205,13 +312,32 @@ class FourLineDisplayUsermod : public Usermod { bool isHW, isSPI = (type == SSD1306_SPI || type == SSD1306_SPI64); PinOwner po = PinOwner::UM_FourLineDisplay; if (isSPI) { - isHW = (ioPin[0]==HW_PIN_CLOCKSPI && ioPin[1]==HW_PIN_DATASPI); - PinManagerPinType pins[5] = { { ioPin[0], true }, { ioPin[1], true }, { ioPin[2], true }, { ioPin[3], true }, { ioPin[4], true }}; - if (!pinManager.allocateMultiplePins(pins, 5, po)) { type=NONE; return; } - } else { - isHW = (ioPin[0]==HW_PIN_SCL && ioPin[1]==HW_PIN_SDA); + uint8_t hw_sclk = spi_sclk<0 ? HW_PIN_CLOCKSPI : spi_sclk; + uint8_t hw_mosi = spi_mosi<0 ? HW_PIN_DATASPI : spi_mosi; + if (ioPin[0] < 0 || ioPin[1] < 0) { + ioPin[0] = hw_sclk; + ioPin[1] = hw_mosi; + } + isHW = (ioPin[0]==hw_sclk && ioPin[1]==hw_mosi); + PinManagerPinType cspins[3] = { { ioPin[2], true }, { ioPin[3], true }, { ioPin[4], true } }; + if (!pinManager.allocateMultiplePins(cspins, 3, PinOwner::UM_FourLineDisplay)) { type=NONE; return; } + if (isHW) po = PinOwner::HW_SPI; // allow multiple allocations of HW I2C bus pins PinManagerPinType pins[2] = { { ioPin[0], true }, { ioPin[1], true } }; - if (ioPin[0]==HW_PIN_SCL && ioPin[1]==HW_PIN_SDA) po = PinOwner::HW_I2C; // allow multiple allocations of HW I2C bus pins + if (!pinManager.allocateMultiplePins(pins, 2, po)) { + pinManager.deallocateMultiplePins(cspins, 3, PinOwner::UM_FourLineDisplay); + type = NONE; + return; + } + } else { + uint8_t hw_scl = i2c_scl<0 ? HW_PIN_SCL : i2c_scl; + uint8_t hw_sda = i2c_sda<0 ? HW_PIN_SDA : i2c_sda; + if (ioPin[0] < 0 || ioPin[1] < 0) { + ioPin[0] = hw_scl; + ioPin[1] = hw_sda; + } + isHW = (ioPin[0]==hw_scl && ioPin[1]==hw_sda); + if (isHW) po = PinOwner::HW_I2C; // allow multiple allocations of HW I2C bus pins + PinManagerPinType pins[2] = { {ioPin[0], true }, { ioPin[1], true } }; if (!pinManager.allocateMultiplePins(pins, 2, po)) { type=NONE; return; } } @@ -276,10 +402,12 @@ class FourLineDisplayUsermod : public Usermod { else u8x8 = (U8X8 *) new U8X8_SSD1305_128X64_ADAFRUIT_HW_I2C(U8X8_PIN_NONE, ioPin[0], ioPin[1]); // Pins are Reset, SCL, SDA break; case SSD1306_SPI: + // u8x8 uses global SPI variable that is attached to VSPI bus on ESP32 if (!isHW) u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_4W_SW_SPI(ioPin[0], ioPin[1], ioPin[2], ioPin[3], ioPin[4]); else u8x8 = (U8X8 *) new U8X8_SSD1306_128X32_UNIVISION_4W_HW_SPI(ioPin[2], ioPin[3], ioPin[4]); // Pins are cs, dc, reset break; case SSD1306_SPI64: + // u8x8 uses global SPI variable that is attached to VSPI bus on ESP32 if (!isHW) u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_4W_SW_SPI(ioPin[0], ioPin[1], ioPin[2], ioPin[3], ioPin[4]); else u8x8 = (U8X8 *) new U8X8_SSD1306_128X64_NONAME_4W_HW_SPI(ioPin[2], ioPin[3], ioPin[4]); // Pins are cs, dc, reset break; @@ -304,6 +432,7 @@ class FourLineDisplayUsermod : public Usermod { setPowerSave(0); //drawString(0, 0, "Loading..."); overlayLogo(3500); + onUpdateBegin(false); // create Display task initDone = true; } @@ -319,63 +448,13 @@ class FourLineDisplayUsermod : public Usermod { * Da loop. */ void loop() { + #ifndef ARDUINO_ARCH_ESP32 if (!enabled || strip.isUpdating()) return; unsigned long now = millis(); if (now < nextUpdate) return; nextUpdate = now + ((displayTurnedOff && clockMode && showSeconds) ? 1000 : refreshRate); redraw(false); - } - - /** - * Wrappers for screen drawing - */ - void setFlipMode(uint8_t mode) { - if (type == NONE || !enabled) return; - u8x8->setFlipMode(mode); - } - void setContrast(uint8_t contrast) { - if (type == NONE || !enabled) return; - u8x8->setContrast(contrast); - } - void drawString(uint8_t col, uint8_t row, const char *string, bool ignoreLH=false) { - if (type == NONE || !enabled) return; - u8x8->setFont(u8x8_font_chroma48medium8_r); - if (!ignoreLH && lineHeight==2) u8x8->draw1x2String(col, row, string); - else u8x8->drawString(col, row, string); - } - void draw2x2String(uint8_t col, uint8_t row, const char *string) { - if (type == NONE || !enabled) return; - u8x8->setFont(u8x8_font_chroma48medium8_r); - u8x8->draw2x2String(col, row, string); - } - void drawGlyph(uint8_t col, uint8_t row, char glyph, const uint8_t *font, bool ignoreLH=false) { - if (type == NONE || !enabled) return; - u8x8->setFont(font); - if (!ignoreLH && lineHeight==2) u8x8->draw1x2Glyph(col, row, glyph); - else u8x8->drawGlyph(col, row, glyph); - } - void draw2x2Glyph(uint8_t col, uint8_t row, char glyph, const uint8_t *font) { - if (type == NONE || !enabled) return; - u8x8->setFont(font); - u8x8->draw2x2Glyph(col, row, glyph); - } - uint8_t getCols() { - if (type==NONE || !enabled) return 0; - return u8x8->getCols(); - } - void clear() { - if (type == NONE || !enabled) return; - u8x8->clear(); - } - void setPowerSave(uint8_t save) { - if (type == NONE || !enabled) return; - u8x8->setPowerSave(save); - } - - void center(String &line, uint8_t width) { - int len = line.length(); - if (len0; i--) line = ' ' + line; - for (byte i=line.length(); i127)) { + // remove note symbol from effect names + for (byte i=5; i<=printedChars; i++) lineBuffer[i-5] = lineBuffer[i]; //include '\0' + printedChars -= 5; } if (lineHeight == 2) { // use this code for 8 line display char smallBuffer1[MAX_MODE_LINE_SPACE]; @@ -635,10 +704,14 @@ class FourLineDisplayUsermod : public Usermod { bool wakeDisplay() { if (type == NONE || !enabled) return false; if (displayTurnedOff) { + unsigned long now = millis(); + while (drawing && millis()-now < 250) delay(1); // wait if someone else is drawing + drawing = true; clear(); // Turn the display back on sleepOrClock(false); //lastRedraw = millis(); + drawing = false; return true; } return false; @@ -650,6 +723,9 @@ class FourLineDisplayUsermod : public Usermod { * Used in Rotary Encoder usermod. */ void overlay(const char* line1, long showHowLong, byte glyphType) { + unsigned long now = millis(); + while (drawing && millis()-now < 250) delay(1); // wait if someone else is drawing + drawing = true; // Turn the display back on if (!wakeDisplay()) clear(); // Print the overlay @@ -663,6 +739,7 @@ class FourLineDisplayUsermod : public Usermod { drawString(0, (glyphType<255?3:0)*lineHeight, buf.c_str()); } overlayUntil = millis() + showHowLong; + drawing = false; } /** @@ -670,6 +747,9 @@ class FourLineDisplayUsermod : public Usermod { * Clears the screen and prints. */ void overlayLogo(long showHowLong) { + unsigned long now = millis(); + while (drawing && millis()-now < 250) delay(1); // wait if someone else is drawing + drawing = true; // Turn the display back on if (!wakeDisplay()) clear(); // Print the overlay @@ -719,6 +799,7 @@ class FourLineDisplayUsermod : public Usermod { } } overlayUntil = millis() + showHowLong; + drawing = false; } /** @@ -727,6 +808,9 @@ class FourLineDisplayUsermod : public Usermod { * Used in Auto Save usermod */ void overlay(const char* line1, const char* line2, long showHowLong) { + unsigned long now = millis(); + while (drawing && millis()-now < 250) delay(1); // wait if someone else is drawing + drawing = true; // Turn the display back on if (!wakeDisplay()) clear(); // Print the overlay @@ -741,9 +825,14 @@ class FourLineDisplayUsermod : public Usermod { drawString(0, 2*lineHeight, buf.c_str()); } overlayUntil = millis() + showHowLong; + drawing = false; } void networkOverlay(const char* line1, long showHowLong) { + unsigned long now = millis(); + while (drawing && millis()-now < 250) delay(1); // wait if someone else is drawing + drawing = true; + String line; // Turn the display back on if (!wakeDisplay()) clear(); @@ -774,6 +863,7 @@ class FourLineDisplayUsermod : public Usermod { center(line, getCols()); drawString(0, lineHeight*3, line.c_str()); overlayUntil = millis() + showHowLong; + drawing = false; } @@ -794,52 +884,6 @@ class FourLineDisplayUsermod : public Usermod { } } - /** - * Display the current date and time in large characters - * on the middle rows. Based 24 or 12 hour depending on - * the useAMPM configuration. - */ - void showTime() { - if (type == NONE || !enabled || !displayTurnedOff) return; - - char lineBuffer[LINE_BUFFER_SIZE]; - static byte lastSecond; - byte secondCurrent = second(localTime); - byte minuteCurrent = minute(localTime); - byte hourCurrent = hour(localTime); - - if (knownMinute != minuteCurrent) { //only redraw clock if it has changed - //updateLocalTime(); - byte AmPmHour = hourCurrent; - boolean isitAM = true; - if (useAMPM) { - if (AmPmHour > 11) { AmPmHour -= 12; isitAM = false; } - if (AmPmHour == 0) { AmPmHour = 12; } - } - if (knownHour != hourCurrent) { - // only update date when hour changes - sprintf_P(lineBuffer, PSTR("%s %2d "), monthShortStr(month(localTime)), day(localTime)); - draw2x2String(2, lineHeight==1 ? 0 : lineHeight, lineBuffer); // adjust for 8 line displays, draw month and day - } - sprintf_P(lineBuffer,PSTR("%2d:%02d"), (useAMPM ? AmPmHour : hourCurrent), minuteCurrent); - draw2x2String(2, lineHeight*2, lineBuffer); //draw hour, min. blink ":" depending on odd/even seconds - if (useAMPM) drawString(12, lineHeight*2, (isitAM ? "AM" : "PM"), true); //draw am/pm if using 12 time - - drawStatusIcons(); //icons power, wifi, timer, etc - - knownMinute = minuteCurrent; - knownHour = hourCurrent; - } else { - if (secondCurrent == lastSecond) return; - } - if (showSeconds) { - lastSecond = secondCurrent; - draw2x2String(6, lineHeight*2, secondCurrent%2 ? " " : ":"); - sprintf_P(lineBuffer, PSTR("%02d"), secondCurrent); - drawString(12, lineHeight*2+1, lineBuffer, true); // even with double sized rows print seconds in 1 line - } - } - /** * handleButton() can be used to override default button behaviour. Returning true * will prevent button working in a default way. @@ -913,7 +957,44 @@ class FourLineDisplayUsermod : public Usermod { } return handled; } - + + #if CONFIG_FREERTOS_UNICORE + #define ARDUINO_RUNNING_CORE 0 + #else + #define ARDUINO_RUNNING_CORE 1 + #endif + void onUpdateBegin(bool init) { + #ifdef ARDUINO_ARCH_ESP32 + if (init && Display_Task) { + vTaskSuspend(Display_Task); // update is about to begin, disable task to prevent crash + } else { + // update has failed or create task requested + if (Display_Task) + vTaskResume(Display_Task); + else + xTaskCreatePinnedToCore( + [](void * par) { // Function to implement the task + // see https://www.freertos.org/vtaskdelayuntil.html + const TickType_t xFrequency = REFRESH_RATE_MS * portTICK_PERIOD_MS / 2; + TickType_t xLastWakeTime = xTaskGetTickCount(); + for(;;) { + delay(1); // DO NOT DELETE THIS LINE! It is needed to give the IDLE(0) task enough time and to keep the watchdog happy. + // taskYIELD(), yield(), vTaskDelay() and esp_task_wdt_feed() didn't seem to work. + vTaskDelayUntil(&xLastWakeTime, xFrequency); // release CPU, by doing nothing for REFRESH_RATE_MS millis + FourLineDisplayUsermod::getInstance()->redraw(false); + } + }, + "4LD", // Name of the task + 3072, // Stack size in words + NULL, // Task input parameter + 1, // Priority of the task (not idle) + &Display_Task, // Task handle + ARDUINO_RUNNING_CORE + ); + } + #endif + } + /* * addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API. * Creating an "u" object allows you to add custom key/value pairs to the Info section of the WLED web UI. @@ -941,6 +1022,23 @@ class FourLineDisplayUsermod : public Usermod { // if (!initDone) return; // prevent crash on boot applyPreset() //} + void appendConfigData() { + oappend(SET_F("dd=addDropdown('4LineDisplay','type');")); + oappend(SET_F("addOption(dd,'None',0);")); + oappend(SET_F("addOption(dd,'SSD1306',1);")); + oappend(SET_F("addOption(dd,'SH1106',2);")); + oappend(SET_F("addOption(dd,'SSD1306 128x64',3);")); + oappend(SET_F("addOption(dd,'SSD1305',4);")); + oappend(SET_F("addOption(dd,'SSD1305 128x64',5);")); + oappend(SET_F("addOption(dd,'SSD1306 SPI',6);")); + oappend(SET_F("addOption(dd,'SSD1306 SPI 128x64',7);")); + oappend(SET_F("addInfo('4LineDisplay:pin[]',0,'I2C/SPI CLK (-1 use global)');")); + oappend(SET_F("addInfo('4LineDisplay:pin[]',1,'I2C/SPI DTA (-1 use global)');")); + oappend(SET_F("addInfo('4LineDisplay:pin[]',2,'SPI CS');")); + oappend(SET_F("addInfo('4LineDisplay:pin[]',3,'SPI DC');")); + oappend(SET_F("addInfo('4LineDisplay:pin[]',4,'SPI RST');")); + } + /* * addToConfig() can be used to add custom persistent settings to the cfg.json file in the "um" (usermod) object. * It will be called by WLED when settings are actually saved (for example, LED settings are saved) @@ -956,17 +1054,32 @@ class FourLineDisplayUsermod : public Usermod { * I highly recommend checking out the basics of ArduinoJson serialization and deserialization in order to use custom settings! */ void addToConfig(JsonObject& root) { + // determine if we are using global HW pins (data & clock) + int8_t hw_dta, hw_clk; + if ((type == SSD1306_SPI || type == SSD1306_SPI64)) { + hw_clk = spi_sclk<0 ? HW_PIN_CLOCKSPI : spi_sclk; + hw_dta = spi_mosi<0 ? HW_PIN_DATASPI : spi_mosi; + } else { + hw_clk = i2c_scl<0 ? HW_PIN_SCL : i2c_scl; + hw_dta = i2c_sda<0 ? HW_PIN_SDA : i2c_sda; + } + JsonObject top = root.createNestedObject(FPSTR(_name)); top[FPSTR(_enabled)] = enabled; + JsonArray io_pin = top.createNestedArray("pin"); - for (byte i=0; i<5; i++) io_pin.add(ioPin[i]); - top["help4Pins"] = F("Clk,Data,CS,DC,RST"); // help for Settings page + for (int i=0; i<5; i++) { + if (i==0 && ioPin[i]==hw_clk) io_pin.add(-1); // do not store global HW pin + else if (i==1 && ioPin[i]==hw_dta) io_pin.add(-1); // do not store global HW pin + else io_pin.add(ioPin[i]); + } top["type"] = type; - top["help4Type"] = F("1=SSD1306,2=SH1106,3=SSD1306_128x64,4=SSD1305,5=SSD1305_128x64,6=SSD1306_SPI,7=SSD1306_SPI_128x64"); // help for Settings page top[FPSTR(_flip)] = (bool) flip; top[FPSTR(_contrast)] = contrast; top[FPSTR(_contrastFix)] = (bool) contrastFix; + #ifndef ARDUINO_ARCH_ESP32 top[FPSTR(_refreshRate)] = refreshRate; + #endif top[FPSTR(_screenTimeOut)] = screenTimeout/1000; top[FPSTR(_sleepMode)] = (bool) sleepMode; top[FPSTR(_clockMode)] = (bool) clockMode; @@ -986,7 +1099,7 @@ class FourLineDisplayUsermod : public Usermod { bool readFromConfig(JsonObject& root) { bool needsRedraw = false; DisplayType newType = type; - int8_t newPin[5]; for (byte i=0; i<5; i++) newPin[i] = ioPin[i]; + int8_t oldPin[5]; for (byte i=0; i<5; i++) oldPin[i] = ioPin[i]; JsonObject top = root[FPSTR(_name)]; if (top.isNull()) { @@ -997,11 +1110,13 @@ class FourLineDisplayUsermod : public Usermod { enabled = top[FPSTR(_enabled)] | enabled; newType = top["type"] | newType; - for (byte i=0; i<5; i++) newPin[i] = top["pin"][i] | ioPin[i]; + for (byte i=0; i<5; i++) ioPin[i] = top["pin"][i] | ioPin[i]; flip = top[FPSTR(_flip)] | flip; contrast = top[FPSTR(_contrast)] | contrast; + #ifndef ARDUINO_ARCH_ESP32 refreshRate = top[FPSTR(_refreshRate)] | refreshRate; refreshRate = min(5000, max(250, (int)refreshRate)); + #endif screenTimeout = (top[FPSTR(_screenTimeOut)] | screenTimeout/1000) * 1000; sleepMode = top[FPSTR(_sleepMode)] | sleepMode; clockMode = top[FPSTR(_clockMode)] | clockMode; @@ -1015,24 +1130,31 @@ class FourLineDisplayUsermod : public Usermod { DEBUG_PRINT(FPSTR(_name)); if (!initDone) { // first run: reading from cfg.json - for (byte i=0; i<5; i++) ioPin[i] = newPin[i]; type = newType; DEBUG_PRINTLN(F(" config loaded.")); } else { DEBUG_PRINTLN(F(" config (re)loaded.")); // changing parameters from settings page bool pinsChanged = false; - for (byte i=0; i<5; i++) if (ioPin[i] != newPin[i]) { pinsChanged = true; break; } + for (byte i=0; i<5; i++) if (ioPin[i] != oldPin[i]) { pinsChanged = true; break; } if (pinsChanged || type!=newType) { if (type != NONE) delete u8x8; PinOwner po = PinOwner::UM_FourLineDisplay; - if (ioPin[0]==HW_PIN_SCL && ioPin[1]==HW_PIN_SDA) po = PinOwner::HW_I2C; // allow multiple allocations of HW I2C bus pins - pinManager.deallocateMultiplePins((const uint8_t *)ioPin, (type == SSD1306_SPI || type == SSD1306_SPI64) ? 5 : 2, po); - for (byte i=0; i<5; i++) ioPin[i] = newPin[i]; - if (ioPin[0]<0 || ioPin[1]<0) { // data & clock must be > -1 - type = NONE; - return true; - } else type = newType; + bool isSPI = (type == SSD1306_SPI || type == SSD1306_SPI64); + if (isSPI) { + pinManager.deallocateMultiplePins((const uint8_t *)(&oldPin[2]), 3, po); + uint8_t hw_sclk = spi_sclk<0 ? HW_PIN_CLOCKSPI : spi_sclk; + uint8_t hw_mosi = spi_mosi<0 ? HW_PIN_DATASPI : spi_mosi; + bool isHW = (oldPin[0]==hw_sclk && oldPin[1]==hw_mosi); + if (isHW) po = PinOwner::HW_SPI; + } else { + uint8_t hw_scl = i2c_scl<0 ? HW_PIN_SCL : i2c_scl; + uint8_t hw_sda = i2c_sda<0 ? HW_PIN_SDA : i2c_sda; + bool isHW = (oldPin[0]==hw_scl && oldPin[1]==hw_sda); + if (isHW) po = PinOwner::HW_I2C; + } + pinManager.deallocateMultiplePins((const uint8_t *)oldPin, 2, po); + type = newType; setup(); needsRedraw |= true; } else { @@ -1070,3 +1192,5 @@ const char FourLineDisplayUsermod::_clockMode[] PROGMEM = "clockMode"; const char FourLineDisplayUsermod::_showSeconds[] PROGMEM = "showSeconds"; const char FourLineDisplayUsermod::_busClkFrequency[] PROGMEM = "i2c-freq-kHz"; const char FourLineDisplayUsermod::_contrastFix[] PROGMEM = "contrastFix"; + +FourLineDisplayUsermod *FourLineDisplayUsermod::instance = nullptr; diff --git a/usermods/usermod_v2_mode_sort/usermod_v2_mode_sort.h b/usermods/usermod_v2_mode_sort/usermod_v2_mode_sort.h index 2be7ce84c..092206bb6 100644 --- a/usermods/usermod_v2_mode_sort/usermod_v2_mode_sort.h +++ b/usermods/usermod_v2_mode_sort/usermod_v2_mode_sort.h @@ -162,7 +162,6 @@ public: break; } } - re_sortModes(palettes_qstrings, palettes_alpha_indexes, strip.getPaletteCount(), skipPaletteCount); } @@ -189,6 +188,7 @@ public: bool complete = false; for (size_t i = 0; i < strlen_P(json); i++) { singleJsonSymbol = pgm_read_byte_near(json + i); + if (singleJsonSymbol == '\0') break; switch (singleJsonSymbol) { case '"': insideQuotes = !insideQuotes; @@ -200,18 +200,14 @@ public: case '[': break; case ']': - complete = true; + if (!insideQuotes) complete = true; break; case ',': - modeIndex++; + if (!insideQuotes) modeIndex++; default: - if (!insideQuotes) { - break; - } - } - if (complete) { - break; + if (!insideQuotes) break; } + if (complete) break; } return modeStrings; } diff --git a/usermods/usermod_v2_rotary_encoder_ui/readme.md b/usermods/usermod_v2_rotary_encoder_ui/readme.md index 9bbcd6a6c..b5a8a924f 100644 --- a/usermods/usermod_v2_rotary_encoder_ui/readme.md +++ b/usermods/usermod_v2_rotary_encoder_ui/readme.md @@ -15,11 +15,17 @@ This file should be placed in the same directory as `platformio.ini`. ### Define Your Options -* `USERMOD_ROTARY_ENCODER_UI` - define this to have this user mod included wled00\usermods_list.cpp -* `USERMOD_FOUR_LINE_DISPLAY` - define this to have this the Four Line Display mod included wled00\usermods_list.cpp - also tells this usermod that the display is available (see the Four Line Display usermod `readme.md` for more details) -* `ENCODER_DT_PIN` - The encoders DT pin, defaults to 12 -* `ENCODER_CLK_PIN` - The encoders CLK pin, defaults to 14 -* `ENCODER_SW_PIN` - The encoders SW pin, defaults to 13 +* `USERMOD_ROTARY_ENCODER_UI` - define this to have this user mod included wled00\usermods_list.cpp +* `USERMOD_ROTARY_ENCODER_GPIO` - define the GPIO function (INPUT, INPUT_PULLUP, etc...) +* `USERMOD_FOUR_LINE_DISPLAY` - define this to have this the Four Line Display mod included wled00\usermods_list.cpp + also tells this usermod that the display is available + (see the Four Line Display usermod `readme.md` for more details) +* `ENCODER_DT_PIN` - The encoders DT pin, defaults to 12 +* `ENCODER_CLK_PIN` - The encoders CLK pin, defaults to 14 +* `ENCODER_SW_PIN` - The encoders SW pin, defaults to 13 +* `USERMOD_ROTARY_ENCODER_GPIO` - The GPIO functionality: + `INPUT_PULLUP` to use internal pull-up + `INPUT` to use pull-up on the PCB ### PlatformIO requirements diff --git a/usermods/usermod_v2_rotary_encoder_ui/usermod_v2_rotary_encoder_ui.h b/usermods/usermod_v2_rotary_encoder_ui/usermod_v2_rotary_encoder_ui.h index 0ea77e844..02bc0ccda 100644 --- a/usermods/usermod_v2_rotary_encoder_ui/usermod_v2_rotary_encoder_ui.h +++ b/usermods/usermod_v2_rotary_encoder_ui/usermod_v2_rotary_encoder_ui.h @@ -97,6 +97,7 @@ public: */ void setup() { + DEBUG_PRINTLN(F("Usermod Rotary Encoder init.")); PinManagerPinType pins[3] = { { pinA, false }, { pinB, false }, { pinC, false } }; if (!pinManager.allocateMultiplePins(pins, 3, PinOwner::UM_RotaryEncoderUI)) { // BUG: configuring this usermod with conflicting pins @@ -109,9 +110,13 @@ public: return; } - pinMode(pinA, INPUT_PULLUP); - pinMode(pinB, INPUT_PULLUP); - pinMode(pinC, INPUT_PULLUP); + #ifndef USERMOD_ROTARY_ENCODER_GPIO + #define USERMOD_ROTARY_ENCODER_GPIO INPUT_PULLUP + #endif + pinMode(pinA, USERMOD_ROTARY_ENCODER_GPIO); + pinMode(pinB, USERMOD_ROTARY_ENCODER_GPIO); + pinMode(pinC, USERMOD_ROTARY_ENCODER_GPIO); + currentTime = millis(); loopTime = currentTime; @@ -439,14 +444,11 @@ public: DEBUG_PRINTLN(F(": No config found. (Using defaults.)")); return false; } - int8_t newDTpin = pinA; - int8_t newCLKpin = pinB; - int8_t newSWpin = pinC; + int8_t newDTpin = top[FPSTR(_DT_pin)] | pinA; + int8_t newCLKpin = top[FPSTR(_CLK_pin)] | pinB; + int8_t newSWpin = top[FPSTR(_SW_pin)] | pinC; enabled = top[FPSTR(_enabled)] | enabled; - newDTpin = top[FPSTR(_DT_pin)] | newDTpin; - newCLKpin = top[FPSTR(_CLK_pin)] | newCLKpin; - newSWpin = top[FPSTR(_SW_pin)] | newSWpin; DEBUG_PRINT(FPSTR(_name)); if (!initDone) { diff --git a/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h b/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h index 4629f547a..ecc994e60 100644 --- a/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h +++ b/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h @@ -47,7 +47,7 @@ // The last UI state, remove color and saturation option if diplay not active(too many options) #ifdef USERMOD_FOUR_LINE_DISPLAY - #define LAST_UI_STATE 8 + #define LAST_UI_STATE 11 #else #define LAST_UI_STATE 4 #endif @@ -56,7 +56,7 @@ #define MODE_SORT_SKIP_COUNT 1 // Which list is being sorted -static char **listBeingSorted; +static const char **listBeingSorted; /** * Modes and palettes are stored as strings that @@ -65,8 +65,8 @@ static char **listBeingSorted; * JSON_mode_names or JSON_palette_names. */ static int re_qstringCmp(const void *ap, const void *bp) { - char *a = listBeingSorted[*((byte *)ap)]; - char *b = listBeingSorted[*((byte *)bp)]; + const char *a = listBeingSorted[*((byte *)ap)]; + const char *b = listBeingSorted[*((byte *)bp)]; int i = 0; do { char aVal = pgm_read_byte_near(a + i); @@ -139,13 +139,13 @@ private: #endif // Pointers the start of the mode names within JSON_mode_names - char **modes_qstrings = nullptr; + const char **modes_qstrings = nullptr; // Array of mode indexes in alphabetical order. byte *modes_alpha_indexes = nullptr; // Pointers the start of the palette names within JSON_palette_names - char **palettes_qstrings = nullptr; + const char **palettes_qstrings = nullptr; // Array of palette indexes in alphabetical order. byte *palettes_alpha_indexes = nullptr; @@ -161,7 +161,6 @@ private: uint8_t knownPalette = 0; uint8_t currentCCT = 128; - bool isRgbw = false; byte presetHigh = 0; byte presetLow = 0; @@ -186,7 +185,8 @@ private: * modes_alpha_indexes and palettes_alpha_indexes. */ void sortModesAndPalettes() { - modes_qstrings = re_findModeStrings(JSON_mode_names, strip.getModeCount()); + //modes_qstrings = re_findModeStrings(JSON_mode_names, strip.getModeCount()); + modes_qstrings = strip.getModeDataSrc(); modes_alpha_indexes = re_initIndexArray(strip.getModeCount()); re_sortModes(modes_qstrings, modes_alpha_indexes, strip.getModeCount(), MODE_SORT_SKIP_COUNT); @@ -212,8 +212,8 @@ private: * Return an array of mode or palette names from the JSON string. * They don't end in '\0', they end in '"'. */ - char **re_findModeStrings(const char json[], int numModes) { - char **modeStrings = (char **)malloc(sizeof(char *) * numModes); + const char **re_findModeStrings(const char json[], int numModes) { + const char **modeStrings = (const char **)malloc(sizeof(const char *) * numModes); uint8_t modeIndex = 0; bool insideQuotes = false; // advance past the mark for markLineNum that may exist. @@ -250,7 +250,8 @@ private: /** * Sort either the modes or the palettes using quicksort. */ - void re_sortModes(char **modeNames, byte *indexes, int count, int numSkip) { + void re_sortModes(const char **modeNames, byte *indexes, int count, int numSkip) { + if (!modeNames) return; listBeingSorted = modeNames; qsort(indexes + numSkip, count - numSkip, sizeof(byte), re_qstringCmp); listBeingSorted = nullptr; @@ -277,22 +278,20 @@ public: return; } - pinMode(pinA, INPUT_PULLUP); - pinMode(pinB, INPUT_PULLUP); - pinMode(pinC, INPUT_PULLUP); - loopTime = millis(); + #ifndef USERMOD_ROTARY_ENCODER_GPIO + #define USERMOD_ROTARY_ENCODER_GPIO INPUT_PULLUP + #endif + pinMode(pinA, USERMOD_ROTARY_ENCODER_GPIO); + pinMode(pinB, USERMOD_ROTARY_ENCODER_GPIO); + pinMode(pinC, USERMOD_ROTARY_ENCODER_GPIO); - for (uint8_t s = 0; s < busses.getNumBusses(); s++) { - Bus *bus = busses.getBus(s); - if (!bus || bus->getLength()==0) break; - isRgbw |= bus->isRgbw(); - } + loopTime = millis(); currentCCT = (approximateKelvinFromRGB(RGBW32(col[0], col[1], col[2], col[3])) - 1900) >> 5; if (!initDone) sortModesAndPalettes(); -#ifdef USERMOD_FOUR_LINE_DISPLAY +#ifdef USERMOD_FOUR_LINE_DISPLAY // This Usermod uses FourLineDisplayUsermod for the best experience. // But it's optional. But you want it. display = (FourLineDisplayUsermod*) usermods.lookup(USERMOD_ID_FOUR_LINE_DISP); @@ -372,19 +371,43 @@ public: if (buttonWaitTime && currentTime-buttonWaitTime>350 && !buttonPressedBefore) { //same speed as in button.cpp buttonWaitTime = 0; char newState = select_state + 1; - bool changedState = true; - if (newState > LAST_UI_STATE || (newState == 8 && presetHigh==0 && presetLow == 0)) newState = 0; + bool changedState = false; + char lineBuffer[64]; + do { + // finde new state + switch (newState) { + case 0: strcpy_P(lineBuffer, PSTR("Brightness")); changedState = true; break; + case 1: if (!extractModeSlider(effectCurrent, 0, lineBuffer, 63)) newState++; else changedState = true; break; // speed + case 2: if (!extractModeSlider(effectCurrent, 1, lineBuffer, 63)) newState++; else changedState = true; break; // intensity + case 3: strcpy_P(lineBuffer, PSTR("Color Palette")); changedState = true; break; + case 4: strcpy_P(lineBuffer, PSTR("Effect")); changedState = true; break; + case 5: strcpy_P(lineBuffer, PSTR("Main Color")); changedState = true; break; + case 6: strcpy_P(lineBuffer, PSTR("Saturation")); changedState = true; break; + case 7: + if (!(strip.getSegment(applyToAll ? strip.getFirstSelectedSegId() : strip.getMainSegmentId()).getLightCapabilities() & 0x04)) newState++; + else { strcpy_P(lineBuffer, PSTR("CCT")); changedState = true; } + break; + case 8: if (presetHigh==0 || presetLow == 0) newState++; else { strcpy_P(lineBuffer, PSTR("Preset")); changedState = true; } break; + case 9: + case 10: + case 11: if (!extractModeSlider(effectCurrent, newState-7, lineBuffer, 63)) newState++; else changedState = true; break; // custom + } + if (newState > LAST_UI_STATE) newState = 0; + } while (!changedState); if (display != nullptr) { switch (newState) { - case 0: changedState = changeState(PSTR("Brightness"), 1, 0, 1); break; //1 = sun - case 1: changedState = changeState(PSTR("Speed"), 1, 4, 2); break; //2 = skip forward - case 2: changedState = changeState(PSTR("Intensity"), 1, 8, 3); break; //3 = fire - case 3: changedState = changeState(PSTR("Color Palette"), 2, 0, 4); break; //4 = custom palette - case 4: changedState = changeState(PSTR("Effect"), 3, 0, 5); break; //5 = puzzle piece - case 5: changedState = changeState(PSTR("Main Color"), 255, 255, 7); break; //7 = brush - case 6: changedState = changeState(PSTR("Saturation"), 255, 255, 8); break; //8 = contrast - case 7: changedState = changeState(PSTR("CCT"), 255, 255, 10); break; //10 = star - case 8: changedState = changeState(PSTR("Preset"), 255, 255, 11); break; //11 = heart + case 0: changedState = changeState(lineBuffer, 1, 0, 1); break; //1 = sun + case 1: changedState = changeState(lineBuffer, 1, 4, 2); break; //2 = skip forward + case 2: changedState = changeState(lineBuffer, 1, 8, 3); break; //3 = fire + case 3: changedState = changeState(lineBuffer, 2, 0, 4); break; //4 = custom palette + case 4: changedState = changeState(lineBuffer, 3, 0, 5); break; //5 = puzzle piece + case 5: changedState = changeState(lineBuffer, 255, 255, 7); break; //7 = brush + case 6: changedState = changeState(lineBuffer, 255, 255, 8); break; //8 = contrast + case 7: changedState = changeState(lineBuffer, 255, 255, 10); break; //10 = star + case 8: changedState = changeState(lineBuffer, 255, 255, 11); break; //11 = heart + case 9: changedState = changeState(lineBuffer, 255, 255, 10); break; //10 = star + case 10: changedState = changeState(lineBuffer, 255, 255, 10); break; //10 = star + case 11: changedState = changeState(lineBuffer, 255, 255, 10); break; //10 = star } } if (changedState) select_state = newState; @@ -397,29 +420,35 @@ public: if (Enc_B == LOW) //changes to LOW so that then encoder registers a change at the very end of a pulse { // B is high so clockwise switch(select_state) { - case 0: changeBrightness(true); break; - case 1: changeEffectSpeed(true); break; - case 2: changeEffectIntensity(true); break; - case 3: changePalette(true); break; - case 4: changeEffect(true); break; - case 5: changeHue(true); break; - case 6: changeSat(true); break; - case 7: changeCCT(true); break; - case 8: changePreset(true); break; + case 0: changeBrightness(true); break; + case 1: changeEffectSpeed(true); break; + case 2: changeEffectIntensity(true); break; + case 3: changePalette(true); break; + case 4: changeEffect(true); break; + case 5: changeHue(true); break; + case 6: changeSat(true); break; + case 7: changeCCT(true); break; + case 8: changePreset(true); break; + case 9: changeCustom(1,true); break; + case 10: changeCustom(2,true); break; + case 11: changeCustom(3,true); break; } } else if (Enc_B == HIGH) { // B is low so counter-clockwise switch(select_state) { - case 0: changeBrightness(false); break; - case 1: changeEffectSpeed(false); break; - case 2: changeEffectIntensity(false); break; - case 3: changePalette(false); break; - case 4: changeEffect(false); break; - case 5: changeHue(false); break; - case 6: changeSat(false); break; - case 7: changeCCT(false); break; - case 8: changePreset(false); break; + case 0: changeBrightness(false); break; + case 1: changeEffectSpeed(false); break; + case 2: changeEffectIntensity(false); break; + case 3: changePalette(false); break; + case 4: changeEffect(false); break; + case 5: changeHue(false); break; + case 6: changeSat(false); break; + case 7: changeCCT(false); break; + case 8: changePreset(false); break; + case 9: changeCustom(1,false); break; + case 10: changeCustom(2,false); break; + case 11: changeCustom(3,false); break; } } } @@ -503,14 +532,14 @@ public: effectCurrent = modes_alpha_indexes[effectCurrentIndex]; stateChanged = true; if (applyToAll) { - for (byte i=0; iwakeDisplay()) { + display->redraw(true); + // Throw away wake up input + return; + } + display->updateRedrawTime(); + #endif + stateChanged = true; + if (applyToAll) { + uint8_t id = strip.getFirstSelectedSegId(); + Segment& sid = strip.getSegment(id); + switch (par) { + case 3: val = sid.custom3 = max(min((increase ? sid.custom3+fadeAmount : sid.custom3-fadeAmount), 255), 0); break; + case 2: val = sid.custom2 = max(min((increase ? sid.custom2+fadeAmount : sid.custom2-fadeAmount), 255), 0); break; + default: val = sid.custom1 = max(min((increase ? sid.custom1+fadeAmount : sid.custom1-fadeAmount), 255), 0); break; + } + for (byte i=0; ioverlay(lineBuffer, 500, 10); // use star + #endif + } + + void changePalette(bool increase) { #ifdef USERMOD_FOUR_LINE_DISPLAY if (display && display->wakeDisplay()) { @@ -588,14 +662,14 @@ public: effectPalette = palettes_alpha_indexes[effectPaletteIndex]; stateChanged = true; if (applyToAll) { - for (byte i=0; ioverlay(lineBuffer, 500, 7); // use brush + #endif } void changeSat(bool increase){ @@ -641,16 +720,21 @@ public: currentSat1 = max(min((increase ? currentSat1+fadeAmount : currentSat1-fadeAmount), 255), 0); colorHStoRGB(currentHue1*256, currentSat1, col); if (applyToAll) { - for (byte i=0; ioverlay(lineBuffer, 500, 8); // use contrast + #endif } void changePreset(bool increase) { @@ -663,6 +747,12 @@ public: display->updateRedrawTime(); #endif if (presetHigh && presetLow && presetHigh > presetLow) { + StaticJsonDocument<64> root; + char str[64]; + sprintf_P(str, PSTR("%d~%d~%s"), presetLow, presetHigh, increase?"":"-"); + root[F("ps")] = str; + deserializeState(root.as(), CALL_MODE_BUTTON_PRESET); +/* String apireq = F("win&PL=~"); if (!increase) apireq += '-'; apireq += F("&P1="); @@ -670,7 +760,12 @@ public: apireq += F("&P2="); apireq += presetHigh; handleSet(nullptr, apireq, false); +*/ lampUdated(); + #ifdef USERMOD_FOUR_LINE_DISPLAY + sprintf(str, "%d", currentPreset); + display->overlay(str, 500, 11); // use heart + #endif } } @@ -685,16 +780,21 @@ public: #endif currentCCT = max(min((increase ? currentCCT+fadeAmount : currentCCT-fadeAmount), 255), 0); // if (applyToAll) { - for (byte i=0; ioverlay(lineBuffer, 500, 10); // use star + #endif } /* diff --git a/usermods/usermod_v2_word_clock/readme.md b/usermods/usermod_v2_word_clock/readme.md index dac79bc15..7b81582fc 100644 --- a/usermods/usermod_v2_word_clock/readme.md +++ b/usermods/usermod_v2_word_clock/readme.md @@ -1,11 +1,12 @@ # Word Clock Usermod V2 This usermod can be used to drive a wordclock with a 11x10 pixel matrix with WLED. There are also 4 additional dots for the minutes. -The visualisation is desribed in 4 mask with LED numbers (single dots for minutes, minutes, hours and "clock/Uhr"). -There are 2 parameters to chnage the behaviour: +The visualisation is desribed in 4 mask with LED numbers (single dots for minutes, minutes, hours and "clock/Uhr"). The index of the LEDs in the masks always starts with the index 0, even if the ledOffset is not 0. +There are 3 parameters to change the behaviour: active: enable/disable usermod -diplayItIs: enable/disable display of "Es ist" on the clock. +diplayItIs: enable/disable display of "Es ist" on the clock +ledOffset: number of LEDs before the wordclock LEDs ### Update for alternatative wiring pattern Based on this fantastic work I added an alternative wiring pattern. diff --git a/usermods/usermod_v2_word_clock/usermod_v2_word_clock.h b/usermods/usermod_v2_word_clock/usermod_v2_word_clock.h index cc85a09af..1068cd961 100644 --- a/usermods/usermod_v2_word_clock/usermod_v2_word_clock.h +++ b/usermods/usermod_v2_word_clock/usermod_v2_word_clock.h @@ -23,6 +23,7 @@ class WordClockUsermod : public Usermod // set your config variables to their boot default value (this can also be done in readFromConfig() or a constructor if you prefer) bool usermodActive = false; bool displayItIs = false; + int ledOffset = 100; bool meander = false; // defines for mask sizes @@ -407,6 +408,7 @@ class WordClockUsermod : public Usermod JsonObject top = root.createNestedObject("WordClockUsermod"); top["active"] = usermodActive; top["displayItIs"] = displayItIs; + top["ledOffset"] = ledOffset; top["Meander wiring?"] = meander; } @@ -436,6 +438,7 @@ class WordClockUsermod : public Usermod configComplete &= getJsonValue(top["active"], usermodActive); configComplete &= getJsonValue(top["displayItIs"], displayItIs); + configComplete &= getJsonValue(top["ledOffset"], ledOffset); configComplete &= getJsonValue(top["Meander wiring?"], meander); return configComplete; @@ -458,7 +461,7 @@ class WordClockUsermod : public Usermod if (maskLedsOn[x] == 0) { // set pixel off - strip.setPixelColor(x, RGBW32(0,0,0,0)); + strip.setPixelColor(x + ledOffset, RGBW32(0,0,0,0)); } } } diff --git a/usermods/word-clock-matrix/word-clock-matrix.cpp b/usermods/word-clock-matrix/word-clock-matrix.cpp index 628b52625..67c5b1e47 100644 --- a/usermods/word-clock-matrix/word-clock-matrix.cpp +++ b/usermods/word-clock-matrix/word-clock-matrix.cpp @@ -27,14 +27,14 @@ saveMacro(1, "&FX=0&R=255&G=255&B=255", false); //strip.getSegment(1).setOption(SEG_OPTION_SELECTED, true); //select first two segments (background color + FX settable) - WS2812FX::Segment &seg = strip.getSegment(0); + Segment &seg = strip.getSegment(0); seg.colors[0] = ((0 << 24) | ((0 & 0xFF) << 16) | ((0 & 0xFF) << 8) | ((0 & 0xFF))); strip.getSegment(0).setOption(0, false); strip.getSegment(0).setOption(2, false); //other segments are text for (int i = 1; i < 10; i++) { - WS2812FX::Segment &seg = strip.getSegment(i); + Segment &seg = strip.getSegment(i); seg.colors[0] = ((0 << 24) | ((0 & 0xFF) << 16) | ((190 & 0xFF) << 8) | ((180 & 0xFF))); strip.getSegment(i).setOption(0, true); strip.setBrightness(128); @@ -50,7 +50,7 @@ void selectWordSegments(bool state) { for (int i = 1; i < 10; i++) { - //WS2812FX::Segment &seg = strip.getSegment(i); + //Segment &seg = strip.getSegment(i); strip.getSegment(i).setOption(0, state); // strip.getSegment(1).setOption(SEG_OPTION_SELECTED, true); //seg.mode = 12; diff --git a/wled00/FX.cpp b/wled00/FX.cpp index fa6cb3551..eea58baba 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -26,17 +26,59 @@ #include "FX.h" #include "wled.h" +#include "fcn_declare.h" #define IBN 5100 -#define PALETTE_SOLID_WRAP (paletteBlend == 1 || paletteBlend == 3) +#define PALETTE_SOLID_WRAP (strip.paletteBlend == 1 || strip.paletteBlend == 3) +#define indexToVStrip(index, stripNr) ((index) | (int((stripNr)+1)<<16)) + +// effect utility functions +uint8_t sin_gap(uint16_t in) { + if (in & 0x100) return 0; + return sin8(in + 192); // correct phase shift of sine so that it starts and stops at 0 +} + +uint16_t triwave16(uint16_t in) { + if (in < 0x8000) return in *2; + return 0xFFFF - (in - 0x8000)*2; +} + +/* + * Generates a tristate square wave w/ attac & decay + * @param x input value 0-255 + * @param pulsewidth 0-127 + * @param attdec attac & decay, max. pulsewidth / 2 + * @returns signed waveform value + */ +int8_t tristate_square8(uint8_t x, uint8_t pulsewidth, uint8_t attdec) { + int8_t a = 127; + if (x > 127) { + a = -127; + x -= 127; + } + + if (x < attdec) { //inc to max + return (int16_t) x * a / attdec; + } + else if (x < pulsewidth - attdec) { //max + return a; + } + else if (x < pulsewidth) { //dec to 0 + return (int16_t) (pulsewidth - x) * a / attdec; + } + return 0; +} + +// effect functions /* * No blinking. Just plain old static light. */ -uint16_t WS2812FX::mode_static(void) { - fill(SEGCOLOR(0)); - return (SEGMENT.getOption(SEG_OPTION_TRANSITIONAL)) ? FRAMETIME : 350; //update faster if in transition +uint16_t mode_static(void) { + SEGMENT.fill(SEGCOLOR(0)); + return 350; } +static const char _data_FX_MODE_STATIC[] PROGMEM = "Solid"; /* @@ -44,13 +86,13 @@ uint16_t WS2812FX::mode_static(void) { * Alternate between color1 and color2 * if(strobe == true) then create a strobe effect */ -uint16_t WS2812FX::blink(uint32_t color1, uint32_t color2, bool strobe, bool do_palette) { +uint16_t blink(uint32_t color1, uint32_t color2, bool strobe, bool do_palette) { uint32_t cycleTime = (255 - SEGMENT.speed)*20; uint32_t onTime = FRAMETIME; if (!strobe) onTime += ((cycleTime * SEGMENT.intensity) >> 8); cycleTime += FRAMETIME*2; - uint32_t it = now / cycleTime; - uint32_t rem = now % cycleTime; + uint32_t it = strip.now / cycleTime; + uint32_t rem = strip.now % cycleTime; bool on = false; if (it != SEGENV.step //new iteration, force on state for one frame, even if set time is too brief @@ -63,45 +105,49 @@ uint16_t WS2812FX::blink(uint32_t color1, uint32_t color2, bool strobe, bool do_ uint32_t color = on ? color1 : color2; if (color == color1 && do_palette) { - for(uint16_t i = 0; i < SEGLEN; i++) { - setPixelColor(i, color_from_palette(i, true, PALETTE_SOLID_WRAP, 0)); + for (int i = 0; i < SEGLEN; i++) { + SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0)); } - } else fill(color); + } else SEGMENT.fill(color); return FRAMETIME; } /* - * Normal blinking. 50% on/off time. + * Normal blinking. Intensity sets duty cycle. */ -uint16_t WS2812FX::mode_blink(void) { +uint16_t mode_blink(void) { return blink(SEGCOLOR(0), SEGCOLOR(1), false, true); } +static const char _data_FX_MODE_BLINK[] PROGMEM = "Blink@!,Duty cycle;!,!,;!;1d"; /* * Classic Blink effect. Cycling through the rainbow. */ -uint16_t WS2812FX::mode_blink_rainbow(void) { - return blink(color_wheel(SEGENV.call & 0xFF), SEGCOLOR(1), false, false); +uint16_t mode_blink_rainbow(void) { + return blink(SEGMENT.color_wheel(SEGENV.call & 0xFF), SEGCOLOR(1), false, false); } +static const char _data_FX_MODE_BLINK_RAINBOW[] PROGMEM = "Blink Rainbow@Frequency,Blink duration;!,!,;!;1d"; /* * Classic Strobe effect. */ -uint16_t WS2812FX::mode_strobe(void) { +uint16_t mode_strobe(void) { return blink(SEGCOLOR(0), SEGCOLOR(1), true, true); } +static const char _data_FX_MODE_STROBE[] PROGMEM = "Strobe@!,;!,!,;!;1d"; /* * Classic Strobe effect. Cycling through the rainbow. */ -uint16_t WS2812FX::mode_strobe_rainbow(void) { - return blink(color_wheel(SEGENV.call & 0xFF), SEGCOLOR(1), true, false); +uint16_t mode_strobe_rainbow(void) { + return blink(SEGMENT.color_wheel(SEGENV.call & 0xFF), SEGCOLOR(1), true, false); } +static const char _data_FX_MODE_STROBE_RAINBOW[] PROGMEM = "Strobe Rainbow@!,;,!,;!;1d"; /* @@ -109,9 +155,9 @@ uint16_t WS2812FX::mode_strobe_rainbow(void) { * LEDs are turned on (color1) in sequence, then turned off (color2) in sequence. * if (bool rev == true) then LEDs are turned off in reverse order */ -uint16_t WS2812FX::color_wipe(bool rev, bool useRandomColors) { +uint16_t color_wipe(bool rev, bool useRandomColors) { uint32_t cycleTime = 750 + (255 - SEGMENT.speed)*150; - uint32_t perc = now % cycleTime; + uint32_t perc = strip.now % cycleTime; uint16_t prog = (perc * 65535) / cycleTime; bool back = (prog > 32767); if (back) { @@ -127,11 +173,11 @@ uint16_t WS2812FX::color_wipe(bool rev, bool useRandomColors) { SEGENV.step = 3; } if (SEGENV.step == 1) { //if flag set, change to new random color - SEGENV.aux1 = get_random_wheel_index(SEGENV.aux0); + SEGENV.aux1 = SEGMENT.get_random_wheel_index(SEGENV.aux0); SEGENV.step = 2; } if (SEGENV.step == 3) { - SEGENV.aux0 = get_random_wheel_index(SEGENV.aux1); + SEGENV.aux0 = SEGMENT.get_random_wheel_index(SEGENV.aux1); SEGENV.step = 0; } } @@ -142,19 +188,19 @@ uint16_t WS2812FX::color_wipe(bool rev, bool useRandomColors) { rem /= (SEGMENT.intensity +1); if (rem > 255) rem = 255; - uint32_t col1 = useRandomColors? color_wheel(SEGENV.aux1) : SEGCOLOR(1); - for (uint16_t i = 0; i < SEGLEN; i++) + uint32_t col1 = useRandomColors? SEGMENT.color_wheel(SEGENV.aux1) : SEGCOLOR(1); + for (int i = 0; i < SEGLEN; i++) { uint16_t index = (rev && back)? SEGLEN -1 -i : i; - uint32_t col0 = useRandomColors? color_wheel(SEGENV.aux0) : color_from_palette(index, true, PALETTE_SOLID_WRAP, 0); + uint32_t col0 = useRandomColors? SEGMENT.color_wheel(SEGENV.aux0) : SEGMENT.color_from_palette(index, true, PALETTE_SOLID_WRAP, 0); if (i < ledIndex) { - setPixelColor(index, back? col1 : col0); + SEGMENT.setPixelColor(index, back? col1 : col0); } else { - setPixelColor(index, back? col0 : col1); - if (i == ledIndex) setPixelColor(index, color_blend(back? col0 : col1, back? col1 : col0, rem)); + SEGMENT.setPixelColor(index, back? col0 : col1); + if (i == ledIndex) SEGMENT.setPixelColor(index, color_blend(back? col0 : col1, back? col1 : col0, rem)); } } return FRAMETIME; @@ -164,43 +210,48 @@ uint16_t WS2812FX::color_wipe(bool rev, bool useRandomColors) { /* * Lights all LEDs one after another. */ -uint16_t WS2812FX::mode_color_wipe(void) { +uint16_t mode_color_wipe(void) { return color_wipe(false, false); } +static const char _data_FX_MODE_COLOR_WIPE[] PROGMEM = "Wipe@!,!;!,!,;!;1d"; + /* * Lights all LEDs one after another. Turns off opposite */ -uint16_t WS2812FX::mode_color_sweep(void) { +uint16_t mode_color_sweep(void) { return color_wipe(true, false); } +static const char _data_FX_MODE_COLOR_SWEEP[] PROGMEM = "Sweep@!,!;!,!,;!;1d"; /* * Turns all LEDs after each other to a random color. * Then starts over with another color. */ -uint16_t WS2812FX::mode_color_wipe_random(void) { +uint16_t mode_color_wipe_random(void) { return color_wipe(false, true); } +static const char _data_FX_MODE_COLOR_WIPE_RANDOM[] PROGMEM = "Wipe Random@!,;1,2,3;!;1d"; /* * Random color introduced alternating from start and end of strip. */ -uint16_t WS2812FX::mode_color_sweep_random(void) { +uint16_t mode_color_sweep_random(void) { return color_wipe(true, true); } +static const char _data_FX_MODE_COLOR_SWEEP_RANDOM[] PROGMEM = "Sweep Random"; /* * Lights all LEDs in one random color up. Then switches them * to the next random color. */ -uint16_t WS2812FX::mode_random_color(void) { +uint16_t mode_random_color(void) { uint32_t cycleTime = 200 + (255 - SEGMENT.speed)*50; - uint32_t it = now / cycleTime; - uint32_t rem = now % cycleTime; + uint32_t it = strip.now / cycleTime; + uint32_t rem = strip.now % cycleTime; uint16_t fadedur = (cycleTime * SEGMENT.intensity) >> 8; uint32_t fade = 255; @@ -216,68 +267,74 @@ uint16_t WS2812FX::mode_random_color(void) { if (it != SEGENV.step) //new color { SEGENV.aux1 = SEGENV.aux0; - SEGENV.aux0 = get_random_wheel_index(SEGENV.aux0); //aux0 will store our random color wheel index + SEGENV.aux0 = SEGMENT.get_random_wheel_index(SEGENV.aux0); //aux0 will store our random color wheel index SEGENV.step = it; } - fill(color_blend(color_wheel(SEGENV.aux1), color_wheel(SEGENV.aux0), fade)); + SEGMENT.fill(color_blend(SEGMENT.color_wheel(SEGENV.aux1), SEGMENT.color_wheel(SEGENV.aux0), fade)); return FRAMETIME; } +static const char _data_FX_MODE_RANDOM_COLOR[] PROGMEM = "Random Colors@!,Fade time;1,2,3;!;1d"; /* * Lights every LED in a random color. Changes all LED at the same time * to new random colors. */ -uint16_t WS2812FX::dynamic(boolean smooth=false) { +uint16_t dynamic(boolean smooth=false) { if (!SEGENV.allocateData(SEGLEN)) return mode_static(); //allocation failed if(SEGENV.call == 0) { - for (uint16_t i = 0; i < SEGLEN; i++) SEGENV.data[i] = random8(); + for (int i = 0; i < SEGLEN; i++) SEGENV.data[i] = random8(); } uint32_t cycleTime = 50 + (255 - SEGMENT.speed)*15; - uint32_t it = now / cycleTime; + uint32_t it = strip.now / cycleTime; if (it != SEGENV.step && SEGMENT.speed != 0) //new color { - for (uint16_t i = 0; i < SEGLEN; i++) { + for (int i = 0; i < SEGLEN; i++) { if (random8() <= SEGMENT.intensity) SEGENV.data[i] = random8(); } SEGENV.step = it; } if (smooth) { - for (uint16_t i = 0; i < SEGLEN; i++) { - blendPixelColor(i, color_wheel(SEGENV.data[i]),16); + for (int i = 0; i < SEGLEN; i++) { + SEGMENT.blendPixelColor(i, SEGMENT.color_wheel(SEGENV.data[i]),16); // TODO } } else { - for (uint16_t i = 0; i < SEGLEN; i++) { - setPixelColor(i, color_wheel(SEGENV.data[i])); + for (int i = 0; i < SEGLEN; i++) { + SEGMENT.setPixelColor(i, SEGMENT.color_wheel(SEGENV.data[i])); } } return FRAMETIME; } + /* * Original effect "Dynamic" */ -uint16_t WS2812FX::mode_dynamic(void) { +uint16_t mode_dynamic(void) { return dynamic(false); } +static const char _data_FX_MODE_DYNAMIC[] PROGMEM = "Dynamic@!,!;1,2,3;!;1d"; + /* * effect "Dynamic" with smoth color-fading */ -uint16_t WS2812FX::mode_dynamic_smooth(void) { +uint16_t mode_dynamic_smooth(void) { return dynamic(true); } +static const char _data_FX_MODE_DYNAMIC_SMOOTH[] PROGMEM = "Dynamic Smooth"; + /* * Does the "standby-breathing" of well known i-Devices. */ -uint16_t WS2812FX::mode_breath(void) { +uint16_t mode_breath(void) { uint16_t var = 0; - uint16_t counter = (now * ((SEGMENT.speed >> 3) +10)); + uint16_t counter = (strip.now * ((SEGMENT.speed >> 3) +10)); counter = (counter >> 2) + (counter >> 4); //0-16384 + 0-2048 if (counter < 16384) { if (counter > 8192) counter = 8192 - (counter - 8192); @@ -285,54 +342,56 @@ uint16_t WS2812FX::mode_breath(void) { } uint8_t lum = 30 + var; - for(uint16_t i = 0; i < SEGLEN; i++) { - setPixelColor(i, color_blend(SEGCOLOR(1), color_from_palette(i, true, PALETTE_SOLID_WRAP, 0), lum)); + for (int i = 0; i < SEGLEN; i++) { + SEGMENT.setPixelColor(i, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0), lum)); } return FRAMETIME; } +static const char _data_FX_MODE_BREATH[] PROGMEM = "Breathe@!,;!,!;!;1d"; /* * Fades the LEDs between two colors */ -uint16_t WS2812FX::mode_fade(void) { - uint16_t counter = (now * ((SEGMENT.speed >> 3) +10)); +uint16_t mode_fade(void) { + uint16_t counter = (strip.now * ((SEGMENT.speed >> 3) +10)); uint8_t lum = triwave16(counter) >> 8; - for(uint16_t i = 0; i < SEGLEN; i++) { - setPixelColor(i, color_blend(SEGCOLOR(1), color_from_palette(i, true, PALETTE_SOLID_WRAP, 0), lum)); + for (int i = 0; i < SEGLEN; i++) { + SEGMENT.setPixelColor(i, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0), lum)); } return FRAMETIME; } +static const char _data_FX_MODE_FADE[] PROGMEM = "Fade@!,;!,!,;!;1d"; /* * Scan mode parent function */ -uint16_t WS2812FX::scan(bool dual) +uint16_t scan(bool dual) { uint32_t cycleTime = 750 + (255 - SEGMENT.speed)*150; - uint32_t perc = now % cycleTime; + uint32_t perc = strip.now % cycleTime; uint16_t prog = (perc * 65535) / cycleTime; uint16_t size = 1 + ((SEGMENT.intensity * SEGLEN) >> 9); uint16_t ledIndex = (prog * ((SEGLEN *2) - size *2)) >> 16; - fill(SEGCOLOR(1)); + SEGMENT.fill(SEGCOLOR(1)); int led_offset = ledIndex - (SEGLEN - size); led_offset = abs(led_offset); if (dual) { - for (uint16_t j = led_offset; j < led_offset + size; j++) { + for (int j = led_offset; j < led_offset + size; j++) { uint16_t i2 = SEGLEN -1 -j; - setPixelColor(i2, color_from_palette(i2, true, PALETTE_SOLID_WRAP, (SEGCOLOR(2))? 2:0)); + SEGMENT.setPixelColor(i2, SEGMENT.color_from_palette(i2, true, PALETTE_SOLID_WRAP, (SEGCOLOR(2))? 2:0)); } } - for (uint16_t j = led_offset; j < led_offset + size; j++) { - setPixelColor(j, color_from_palette(j, true, PALETTE_SOLID_WRAP, 0)); + for (int j = led_offset; j < led_offset + size; j++) { + SEGMENT.setPixelColor(j, SEGMENT.color_from_palette(j, true, PALETTE_SOLID_WRAP, 0)); } return FRAMETIME; @@ -342,79 +401,114 @@ uint16_t WS2812FX::scan(bool dual) /* * Runs a single pixel back and forth. */ -uint16_t WS2812FX::mode_scan(void) { +uint16_t mode_scan(void) { return scan(false); } +static const char _data_FX_MODE_SCAN[] PROGMEM = "Scan@!,# of dots;!,!,!;!;1d"; /* * Runs two pixel back and forth in opposite directions. */ -uint16_t WS2812FX::mode_dual_scan(void) { +uint16_t mode_dual_scan(void) { return scan(true); } +static const char _data_FX_MODE_DUAL_SCAN[] PROGMEM = "Scan Dual@!,# of dots;!,!,!;!;1d"; /* * Cycles all LEDs at once through a rainbow. */ -uint16_t WS2812FX::mode_rainbow(void) { - uint16_t counter = (now * ((SEGMENT.speed >> 2) +2)) & 0xFFFF; +uint16_t mode_rainbow(void) { + uint16_t counter = (strip.now * ((SEGMENT.speed >> 2) +2)) & 0xFFFF; counter = counter >> 8; if (SEGMENT.intensity < 128){ - fill(color_blend(color_wheel(counter),WHITE,128-SEGMENT.intensity)); + SEGMENT.fill(color_blend(SEGMENT.color_wheel(counter),WHITE,128-SEGMENT.intensity)); } else { - fill(color_wheel(counter)); + SEGMENT.fill(SEGMENT.color_wheel(counter)); } return FRAMETIME; } +static const char _data_FX_MODE_RAINBOW[] PROGMEM = "Colorloop@!,Saturation;1,2,3;!;1d"; /* * Cycles a rainbow over the entire string of LEDs. */ -uint16_t WS2812FX::mode_rainbow_cycle(void) { - uint16_t counter = (now * ((SEGMENT.speed >> 2) +2)) & 0xFFFF; +uint16_t mode_rainbow_cycle(void) { + uint16_t counter = (strip.now * ((SEGMENT.speed >> 2) +2)) & 0xFFFF; counter = counter >> 8; - for(uint16_t i = 0; i < SEGLEN; i++) { + for (int i = 0; i < SEGLEN; i++) { //intensity/29 = 0 (1/16) 1 (1/8) 2 (1/4) 3 (1/2) 4 (1) 5 (2) 6 (4) 7 (8) 8 (16) uint8_t index = (i * (16 << (SEGMENT.intensity /29)) / SEGLEN) + counter; - setPixelColor(i, color_wheel(index)); + SEGMENT.setPixelColor(i, SEGMENT.color_wheel(index)); } return FRAMETIME; } +static const char _data_FX_MODE_RAINBOW_CYCLE[] PROGMEM = "Rainbow@!,Size;1,2,3;!;1d"; + + +/* + * Alternating pixels running function. + */ +uint16_t running(uint32_t color1, uint32_t color2, bool theatre = false) { + uint8_t width = (theatre ? 3 : 1) + (SEGMENT.intensity >> 4); // window + uint32_t cycleTime = 50 + (255 - SEGMENT.speed); + uint32_t it = strip.now / cycleTime; + bool usePalette = color1 == SEGCOLOR(0); + + for (int i = 0; i < SEGLEN; i++) { + uint32_t col = color2; + if (usePalette) color1 = SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0); + if (theatre) { + if ((i % width) == SEGENV.aux0) col = color1; + } else { + int8_t pos = (i % (width<<1)); + if ((pos < SEGENV.aux0-width) || ((pos >= SEGENV.aux0) && (pos < SEGENV.aux0+width))) col = color1; + } + SEGMENT.setPixelColor(i,col); + } + + if (it != SEGENV.step) { + SEGENV.aux0 = (SEGENV.aux0 +1) % (theatre ? width : (width<<1)); + SEGENV.step = it; + } + return FRAMETIME; +} /* * Theatre-style crawling lights. * Inspired by the Adafruit examples. */ -uint16_t WS2812FX::mode_theater_chase(void) { +uint16_t mode_theater_chase(void) { return running(SEGCOLOR(0), SEGCOLOR(1), true); } +static const char _data_FX_MODE_THEATER_CHASE[] PROGMEM = "Theater@!,Gap size;!,!,;!;1d"; /* * Theatre-style crawling lights with rainbow effect. * Inspired by the Adafruit examples. */ -uint16_t WS2812FX::mode_theater_chase_rainbow(void) { - return running(color_wheel(SEGENV.step), SEGCOLOR(1), true); +uint16_t mode_theater_chase_rainbow(void) { + return running(SEGMENT.color_wheel(SEGENV.step), SEGCOLOR(1), true); } +static const char _data_FX_MODE_THEATER_CHASE_RAINBOW[] PROGMEM = "Theater Rainbow@!,Gap size;1,2,3;!;1d"; /* * Running lights effect with smooth sine transition base. */ -uint16_t WS2812FX::running_base(bool saw, bool dual=false) { +uint16_t running_base(bool saw, bool dual=false) { uint8_t x_scale = SEGMENT.intensity >> 2; - uint32_t counter = (now * SEGMENT.speed) >> 9; + uint32_t counter = (strip.now * SEGMENT.speed) >> 9; - for(uint16_t i = 0; i < SEGLEN; i++) { + for (int i = 0; i < SEGLEN; i++) { uint16_t a = i*x_scale - counter; if (saw) { a &= 0xFF; @@ -427,14 +521,14 @@ uint16_t WS2812FX::running_base(bool saw, bool dual=false) { a = 255 - a; } uint8_t s = dual ? sin_gap(a) : sin8(a); - uint32_t ca = color_blend(SEGCOLOR(1), color_from_palette(i, true, PALETTE_SOLID_WRAP, 0), s); + uint32_t ca = color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0), s); if (dual) { uint16_t b = (SEGLEN-1-i)*x_scale - counter; uint8_t t = sin_gap(b); - uint32_t cb = color_blend(SEGCOLOR(1), color_from_palette(i, true, PALETTE_SOLID_WRAP, 2), t); + uint32_t cb = color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 2), t); ca = color_blend(ca, cb, 127); } - setPixelColor(i, ca); + SEGMENT.setPixelColor(i, ca); } return FRAMETIME; } @@ -444,36 +538,40 @@ uint16_t WS2812FX::running_base(bool saw, bool dual=false) { * Running lights in opposite directions. * Idea: Make the gap width controllable with a third slider in the future */ -uint16_t WS2812FX::mode_running_dual(void) { +uint16_t mode_running_dual(void) { return running_base(false, true); } +static const char _data_FX_MODE_RUNNING_DUAL[] PROGMEM = "Running Dual"; /* * Running lights effect with smooth sine transition. */ -uint16_t WS2812FX::mode_running_lights(void) { +uint16_t mode_running_lights(void) { return running_base(false); } +static const char _data_FX_MODE_RUNNING_LIGHTS[] PROGMEM = "Running@!,Wave width;!,!,;!;1d"; /* * Running lights effect with sawtooth transition. */ -uint16_t WS2812FX::mode_saw(void) { +uint16_t mode_saw(void) { return running_base(true); } +static const char _data_FX_MODE_SAW[] PROGMEM = "Saw@!,Width;!,!,;!;1d"; /* * Blink several LEDs in random colors on, reset, repeat. * Inspired by www.tweaking4all.com/hardware/arduino/adruino-led-strip-effects/ */ -uint16_t WS2812FX::mode_twinkle(void) { - fill(SEGCOLOR(1)); +uint16_t mode_twinkle(void) { + //SEGMENT.fill(SEGCOLOR(1)); + SEGMENT.fade_out(224); uint32_t cycleTime = 20 + (255 - SEGMENT.speed)*5; - uint32_t it = now / cycleTime; + uint32_t it = strip.now / cycleTime; if (it != SEGENV.step) { uint16_t maxOn = map(SEGMENT.intensity, 0, 255, 1, SEGLEN); // make sure at least one LED is on @@ -493,35 +591,36 @@ uint16_t WS2812FX::mode_twinkle(void) { PRNG16 = (uint16_t)(PRNG16 * 2053) + 13849; // next 'random' number uint32_t p = (uint32_t)SEGLEN * (uint32_t)PRNG16; uint16_t j = p >> 16; - setPixelColor(j, color_from_palette(j, true, PALETTE_SOLID_WRAP, 0)); + SEGMENT.setPixelColor(j, SEGMENT.color_from_palette(j, true, PALETTE_SOLID_WRAP, 0)); } return FRAMETIME; } +static const char _data_FX_MODE_TWINKLE[] PROGMEM = "Twinkle@!,;!,!,;!;mp12=0,1d"; //pixels /* * Dissolve function */ -uint16_t WS2812FX::dissolve(uint32_t color) { - bool wa = (SEGCOLOR(1) != 0 && _brightness < 255); //workaround, can't compare getPixel to color if not full brightness +uint16_t dissolve(uint32_t color) { + bool wa = (SEGCOLOR(1) != 0 && strip.getBrightness() < 255); //workaround, can't compare getPixel to color if not full brightness - for (uint16_t j = 0; j <= SEGLEN / 15; j++) + for (int j = 0; j <= SEGLEN / 15; j++) { if (random8() <= SEGMENT.intensity) { - for (uint8_t times = 0; times < 10; times++) //attempt to spawn a new pixel 5 times + for (size_t times = 0; times < 10; times++) //attempt to spawn a new pixel 5 times { uint16_t i = random16(SEGLEN); if (SEGENV.aux0) { //dissolve to primary/palette - if (getPixelColor(i) == SEGCOLOR(1) || wa) { + if (SEGMENT.getPixelColor(i) == SEGCOLOR(1) || wa) { // TODO if (color == SEGCOLOR(0)) { - setPixelColor(i, color_from_palette(i, true, PALETTE_SOLID_WRAP, 0)); - } else { setPixelColor(i, color); } + SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0)); + } else { SEGMENT.setPixelColor(i, color); } break; //only spawn 1 new pixel per frame per 50 LEDs } } else { //dissolve to secondary - if (getPixelColor(i) != SEGCOLOR(1)) { setPixelColor(i, SEGCOLOR(1)); break; } + if (SEGMENT.getPixelColor(i) != SEGCOLOR(1)) { SEGMENT.setPixelColor(i, SEGCOLOR(1)); break; } // TODO } } } @@ -540,117 +639,124 @@ uint16_t WS2812FX::dissolve(uint32_t color) { /* * Blink several LEDs on and then off */ -uint16_t WS2812FX::mode_dissolve(void) { +uint16_t mode_dissolve(void) { return dissolve(SEGCOLOR(0)); } +static const char _data_FX_MODE_DISSOLVE[] PROGMEM = "Dissolve@Repeat speed,Dissolve speed;!,!,;!;1d"; /* * Blink several LEDs on and then off in random colors */ -uint16_t WS2812FX::mode_dissolve_random(void) { - return dissolve(color_wheel(random8())); +uint16_t mode_dissolve_random(void) { + return dissolve(SEGMENT.color_wheel(random8())); } +static const char _data_FX_MODE_DISSOLVE_RANDOM[] PROGMEM = "Dissolve Rnd@Repeat speed,Dissolve speed;,!,;!;1d"; /* * Blinks one LED at a time. * Inspired by www.tweaking4all.com/hardware/arduino/adruino-led-strip-effects/ */ -uint16_t WS2812FX::mode_sparkle(void) { - for(uint16_t i = 0; i < SEGLEN; i++) { - setPixelColor(i, color_from_palette(i, true, PALETTE_SOLID_WRAP, 1)); +uint16_t mode_sparkle(void) { + for(int i = 0; i < SEGLEN; i++) { + SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 1)); } uint32_t cycleTime = 10 + (255 - SEGMENT.speed)*2; - uint32_t it = now / cycleTime; + uint32_t it = strip.now / cycleTime; if (it != SEGENV.step) { SEGENV.aux0 = random16(SEGLEN); // aux0 stores the random led index SEGENV.step = it; } - setPixelColor(SEGENV.aux0, SEGCOLOR(0)); + SEGMENT.setPixelColor(SEGENV.aux0, SEGCOLOR(0)); return FRAMETIME; } +static const char _data_FX_MODE_SPARKLE[] PROGMEM = "Sparkle@!,;!,!,;!;mp12=0,1d"; /* * Lights all LEDs in the color. Flashes single col 1 pixels randomly. (List name: Sparkle Dark) * Inspired by www.tweaking4all.com/hardware/arduino/adruino-led-strip-effects/ */ -uint16_t WS2812FX::mode_flash_sparkle(void) { +uint16_t mode_flash_sparkle(void) { for(uint16_t i = 0; i < SEGLEN; i++) { - setPixelColor(i, color_from_palette(i, true, PALETTE_SOLID_WRAP, 0)); + SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0)); } - if (now - SEGENV.aux0 > SEGENV.step) { + if (strip.now - SEGENV.aux0 > SEGENV.step) { if(random8((255-SEGMENT.intensity) >> 4) == 0) { - setPixelColor(random16(SEGLEN), SEGCOLOR(1)); //flash + SEGMENT.setPixelColor(random16(SEGLEN), SEGCOLOR(1)); //flash } - SEGENV.step = now; + SEGENV.step = strip.now; SEGENV.aux0 = 255-SEGMENT.speed; } return FRAMETIME; } +static const char _data_FX_MODE_FLASH_SPARKLE[] PROGMEM = "Sparkle Dark@!,!;Bg,Fx,;!;mp12=0,1d"; /* * Like flash sparkle. With more flash. * Inspired by www.tweaking4all.com/hardware/arduino/adruino-led-strip-effects/ */ -uint16_t WS2812FX::mode_hyper_sparkle(void) { - for(uint16_t i = 0; i < SEGLEN; i++) { - setPixelColor(i, color_from_palette(i, true, PALETTE_SOLID_WRAP, 0)); +uint16_t mode_hyper_sparkle(void) { + for (int i = 0; i < SEGLEN; i++) { + SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0)); } - if (now - SEGENV.aux0 > SEGENV.step) { - if(random8((255-SEGMENT.intensity) >> 4) == 0) { - for(uint16_t i = 0; i < MAX(1, SEGLEN/3); i++) { - setPixelColor(random16(SEGLEN), SEGCOLOR(1)); + if (strip.now - SEGENV.aux0 > SEGENV.step) { + if (random8((255-SEGMENT.intensity) >> 4) == 0) { + for (int i = 0; i < MAX(1, SEGLEN/3); i++) { + SEGMENT.setPixelColor(random16(SEGLEN), SEGCOLOR(1)); } } - SEGENV.step = now; + SEGENV.step = strip.now; SEGENV.aux0 = 255-SEGMENT.speed; } return FRAMETIME; } +static const char _data_FX_MODE_HYPER_SPARKLE[] PROGMEM = "Sparkle+@!,!;Bg,Fx,;!;mp12=0,1d"; /* * Strobe effect with different strobe count and pause, controlled by speed. */ -uint16_t WS2812FX::mode_multi_strobe(void) { - for(uint16_t i = 0; i < SEGLEN; i++) { - setPixelColor(i, color_from_palette(i, true, PALETTE_SOLID_WRAP, 1)); +uint16_t mode_multi_strobe(void) { + for (int i = 0; i < SEGLEN; i++) { + SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 1)); } SEGENV.aux0 = 50 + 20*(uint16_t)(255-SEGMENT.speed); uint16_t count = 2 * ((SEGMENT.intensity / 10) + 1); if(SEGENV.aux1 < count) { if((SEGENV.aux1 & 1) == 0) { - fill(SEGCOLOR(0)); + SEGMENT.fill(SEGCOLOR(0)); SEGENV.aux0 = 15; } else { SEGENV.aux0 = 50; } } - if (now - SEGENV.aux0 > SEGENV.step) { + if (strip.now - SEGENV.aux0 > SEGENV.step) { SEGENV.aux1++; if (SEGENV.aux1 > count) SEGENV.aux1 = 0; - SEGENV.step = now; + SEGENV.step = strip.now; } return FRAMETIME; } +static const char _data_FX_MODE_MULTI_STROBE[] PROGMEM = "Strobe Mega@!,!;!,!,;!;1d"; + /* * Android loading circle */ -uint16_t WS2812FX::mode_android(void) { +uint16_t mode_android(void) { - for(uint16_t i = 0; i < SEGLEN; i++) { - setPixelColor(i, color_from_palette(i, true, PALETTE_SOLID_WRAP, 1)); + for (int i = 0; i < SEGLEN; i++) { + SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 1)); } if (SEGENV.aux1 > ((float)SEGMENT.intensity/255.0)*(float)SEGLEN) @@ -677,30 +783,32 @@ uint16_t WS2812FX::mode_android(void) { if (a + SEGENV.aux1 < SEGLEN) { - for(int i = a; i < a+SEGENV.aux1; i++) { - setPixelColor(i, SEGCOLOR(0)); + for (int i = a; i < a+SEGENV.aux1; i++) { + SEGMENT.setPixelColor(i, SEGCOLOR(0)); } } else { - for(int i = a; i < SEGLEN; i++) { - setPixelColor(i, SEGCOLOR(0)); + for (int i = a; i < SEGLEN; i++) { + SEGMENT.setPixelColor(i, SEGCOLOR(0)); } - for(int i = 0; i < SEGENV.aux1 - (SEGLEN -a); i++) { - setPixelColor(i, SEGCOLOR(0)); + for (int i = 0; i < SEGENV.aux1 - (SEGLEN -a); i++) { + SEGMENT.setPixelColor(i, SEGCOLOR(0)); } } SEGENV.step = a; return 3 + ((8 * (uint32_t)(255 - SEGMENT.speed)) / SEGLEN); } +static const char _data_FX_MODE_ANDROID[] PROGMEM = "Android@!,Width;!,!,;!;mp12=1,1d"; //vertical + /* * color chase function. * color1 = background color * color2 and color3 = colors of two adjacent leds */ -uint16_t WS2812FX::chase(uint32_t color1, uint32_t color2, uint32_t color3, bool do_palette) { - uint16_t counter = now * ((SEGMENT.speed >> 2) + 1); +uint16_t chase(uint32_t color1, uint32_t color2, uint32_t color3, bool do_palette) { + uint16_t counter = strip.now * ((SEGMENT.speed >> 2) + 1); uint16_t a = counter * SEGLEN >> 16; bool chase_random = (SEGMENT.mode == FX_MODE_CHASE_RANDOM); @@ -708,9 +816,9 @@ uint16_t WS2812FX::chase(uint32_t color1, uint32_t color2, uint32_t color3, bool if (a < SEGENV.step) //we hit the start again, choose new color for Chase random { SEGENV.aux1 = SEGENV.aux0; //store previous random color - SEGENV.aux0 = get_random_wheel_index(SEGENV.aux0); + SEGENV.aux0 = SEGMENT.get_random_wheel_index(SEGENV.aux0); } - color1 = color_wheel(SEGENV.aux0); + color1 = SEGMENT.color_wheel(SEGENV.aux0); } SEGENV.step = a; @@ -725,41 +833,41 @@ uint16_t WS2812FX::chase(uint32_t color1, uint32_t color2, uint32_t color3, bool //background if (do_palette) { - for(uint16_t i = 0; i < SEGLEN; i++) { - setPixelColor(i, color_from_palette(i, true, PALETTE_SOLID_WRAP, 1)); + for (int i = 0; i < SEGLEN; i++) { + SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 1)); } - } else fill(color1); + } else SEGMENT.fill(color1); //if random, fill old background between a and end if (chase_random) { - color1 = color_wheel(SEGENV.aux1); - for (uint16_t i = a; i < SEGLEN; i++) - setPixelColor(i, color1); + color1 = SEGMENT.color_wheel(SEGENV.aux1); + for (int i = a; i < SEGLEN; i++) + SEGMENT.setPixelColor(i, color1); } //fill between points a and b with color2 if (a < b) { - for (uint16_t i = a; i < b; i++) - setPixelColor(i, color2); + for (int i = a; i < b; i++) + SEGMENT.setPixelColor(i, color2); } else { - for (uint16_t i = a; i < SEGLEN; i++) //fill until end - setPixelColor(i, color2); - for (uint16_t i = 0; i < b; i++) //fill from start until b - setPixelColor(i, color2); + for (int i = a; i < SEGLEN; i++) //fill until end + SEGMENT.setPixelColor(i, color2); + for (int i = 0; i < b; i++) //fill from start until b + SEGMENT.setPixelColor(i, color2); } //fill between points b and c with color2 if (b < c) { - for (uint16_t i = b; i < c; i++) - setPixelColor(i, color3); + for (int i = b; i < c; i++) + SEGMENT.setPixelColor(i, color3); } else { - for (uint16_t i = b; i < SEGLEN; i++) //fill until end - setPixelColor(i, color3); - for (uint16_t i = 0; i < c; i++) //fill from start until c - setPixelColor(i, color3); + for (int i = b; i < SEGLEN; i++) //fill until end + SEGMENT.setPixelColor(i, color3); + for (int i = 0; i < c; i++) //fill from start until c + SEGMENT.setPixelColor(i, color3); } return FRAMETIME; @@ -769,60 +877,64 @@ uint16_t WS2812FX::chase(uint32_t color1, uint32_t color2, uint32_t color3, bool /* * Bicolor chase, more primary color. */ -uint16_t WS2812FX::mode_chase_color(void) { +uint16_t mode_chase_color(void) { return chase(SEGCOLOR(1), (SEGCOLOR(2)) ? SEGCOLOR(2) : SEGCOLOR(0), SEGCOLOR(0), true); } +static const char _data_FX_MODE_CHASE_COLOR[] PROGMEM = "Chase@!,Width;!,!,!;!;1d"; /* * Primary running followed by random color. */ -uint16_t WS2812FX::mode_chase_random(void) { +uint16_t mode_chase_random(void) { return chase(SEGCOLOR(1), (SEGCOLOR(2)) ? SEGCOLOR(2) : SEGCOLOR(0), SEGCOLOR(0), false); } +static const char _data_FX_MODE_CHASE_RANDOM[] PROGMEM = "Chase Random@!,Width;!,,!;!;1d"; /* * Primary, secondary running on rainbow. */ -uint16_t WS2812FX::mode_chase_rainbow(void) { +uint16_t mode_chase_rainbow(void) { uint8_t color_sep = 256 / SEGLEN; if (color_sep == 0) color_sep = 1; // correction for segments longer than 256 LEDs uint8_t color_index = SEGENV.call & 0xFF; - uint32_t color = color_wheel(((SEGENV.step * color_sep) + color_index) & 0xFF); + uint32_t color = SEGMENT.color_wheel(((SEGENV.step * color_sep) + color_index) & 0xFF); return chase(color, SEGCOLOR(0), SEGCOLOR(1), false); } +static const char _data_FX_MODE_CHASE_RAINBOW[] PROGMEM = "Chase Rainbow@!,Width;!,!,;;1d"; /* * Primary running on rainbow. */ -uint16_t WS2812FX::mode_chase_rainbow_white(void) { +uint16_t mode_chase_rainbow_white(void) { uint16_t n = SEGENV.step; uint16_t m = (SEGENV.step + 1) % SEGLEN; - uint32_t color2 = color_wheel(((n * 256 / SEGLEN) + (SEGENV.call & 0xFF)) & 0xFF); - uint32_t color3 = color_wheel(((m * 256 / SEGLEN) + (SEGENV.call & 0xFF)) & 0xFF); + uint32_t color2 = SEGMENT.color_wheel(((n * 256 / SEGLEN) + (SEGENV.call & 0xFF)) & 0xFF); + uint32_t color3 = SEGMENT.color_wheel(((m * 256 / SEGLEN) + (SEGENV.call & 0xFF)) & 0xFF); return chase(SEGCOLOR(0), color2, color3, false); } +static const char _data_FX_MODE_CHASE_RAINBOW_WHITE[] PROGMEM = "Rainbow Runner@!,Size;Bg,,;;1d"; /* * Red - Amber - Green - Blue lights running */ -uint16_t WS2812FX::mode_colorful(void) { +uint16_t mode_colorful(void) { uint8_t numColors = 4; //3, 4, or 5 uint32_t cols[9]{0x00FF0000,0x00EEBB00,0x0000EE00,0x000077CC}; if (SEGMENT.intensity > 160 || SEGMENT.palette) { //palette or color if (!SEGMENT.palette) { numColors = 3; - for (uint8_t i = 0; i < 3; i++) cols[i] = SEGCOLOR(i); + for (size_t i = 0; i < 3; i++) cols[i] = SEGCOLOR(i); } else { uint16_t fac = 80; if (SEGMENT.palette == 52) {numColors = 5; fac = 61;} //C9 2 has 5 colors - for (uint8_t i = 0; i < numColors; i++) { - cols[i] = color_from_palette(i*fac, false, true, 255); + for (size_t i = 0; i < numColors; i++) { + cols[i] = SEGMENT.color_from_palette(i*fac, false, true, 255); } } } else if (SEGMENT.intensity < 80) //pastel (easter) colors @@ -832,10 +944,10 @@ uint16_t WS2812FX::mode_colorful(void) { cols[2] = 0x0077FF77; cols[3] = 0x0077F0F0; } - for (uint8_t i = numColors; i < numColors*2 -1; i++) cols[i] = cols[i-numColors]; + for (size_t i = numColors; i < numColors*2 -1U; i++) cols[i] = cols[i-numColors]; uint32_t cycleTime = 50 + (8 * (uint32_t)(255 - SEGMENT.speed)); - uint32_t it = now / cycleTime; + uint32_t it = strip.now / cycleTime; if (it != SEGENV.step) { if (SEGMENT.speed > 0) SEGENV.aux0++; @@ -843,54 +955,56 @@ uint16_t WS2812FX::mode_colorful(void) { SEGENV.step = it; } - for (uint16_t i = 0; i < SEGLEN; i+= numColors) + for (int i = 0; i < SEGLEN; i+= numColors) { - for (uint16_t j = 0; j < numColors; j++) setPixelColor(i + j, cols[SEGENV.aux0 + j]); + for (int j = 0; j < numColors; j++) SEGMENT.setPixelColor(i + j, cols[SEGENV.aux0 + j]); } return FRAMETIME; } +static const char _data_FX_MODE_COLORFUL[] PROGMEM = "Colorful@!,Saturation;1,2,3;!;1d"; /* * Emulates a traffic light. */ -uint16_t WS2812FX::mode_traffic_light(void) { - for(uint16_t i=0; i < SEGLEN; i++) - setPixelColor(i, color_from_palette(i, true, PALETTE_SOLID_WRAP, 1)); +uint16_t mode_traffic_light(void) { + for (int i=0; i < SEGLEN; i++) + SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 1)); uint32_t mdelay = 500; for (int i = 0; i < SEGLEN-2 ; i+=3) { switch (SEGENV.aux0) { - case 0: setPixelColor(i, 0x00FF0000); mdelay = 150 + (100 * (uint32_t)(255 - SEGMENT.speed));break; - case 1: setPixelColor(i, 0x00FF0000); mdelay = 150 + (20 * (uint32_t)(255 - SEGMENT.speed)); setPixelColor(i+1, 0x00EECC00); break; - case 2: setPixelColor(i+2, 0x0000FF00); mdelay = 150 + (100 * (uint32_t)(255 - SEGMENT.speed));break; - case 3: setPixelColor(i+1, 0x00EECC00); mdelay = 150 + (20 * (uint32_t)(255 - SEGMENT.speed));break; + case 0: SEGMENT.setPixelColor(i, 0x00FF0000); mdelay = 150 + (100 * (uint32_t)(255 - SEGMENT.speed));break; + case 1: SEGMENT.setPixelColor(i, 0x00FF0000); mdelay = 150 + (20 * (uint32_t)(255 - SEGMENT.speed)); SEGMENT.setPixelColor(i+1, 0x00EECC00); break; + case 2: SEGMENT.setPixelColor(i+2, 0x0000FF00); mdelay = 150 + (100 * (uint32_t)(255 - SEGMENT.speed));break; + case 3: SEGMENT.setPixelColor(i+1, 0x00EECC00); mdelay = 150 + (20 * (uint32_t)(255 - SEGMENT.speed));break; } } - if (now - SEGENV.step > mdelay) + if (strip.now - SEGENV.step > mdelay) { SEGENV.aux0++; if (SEGENV.aux0 == 1 && SEGMENT.intensity > 140) SEGENV.aux0 = 2; //skip Red + Amber, to get US-style sequence if (SEGENV.aux0 > 3) SEGENV.aux0 = 0; - SEGENV.step = now; + SEGENV.step = strip.now; } return FRAMETIME; } +static const char _data_FX_MODE_TRAFFIC_LIGHT[] PROGMEM = "Traffic Light@!,;,!,;!;1d"; /* * Sec flashes running on prim. */ #define FLASH_COUNT 4 -uint16_t WS2812FX::mode_chase_flash(void) { +uint16_t mode_chase_flash(void) { uint8_t flash_step = SEGENV.call % ((FLASH_COUNT * 2) + 1); - for(uint16_t i = 0; i < SEGLEN; i++) { - setPixelColor(i, color_from_palette(i, true, PALETTE_SOLID_WRAP, 0)); + for (int i = 0; i < SEGLEN; i++) { + SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0)); } uint16_t delay = 10 + ((30 * (uint16_t)(255 - SEGMENT.speed)) / SEGLEN); @@ -898,8 +1012,8 @@ uint16_t WS2812FX::mode_chase_flash(void) { if(flash_step % 2 == 0) { uint16_t n = SEGENV.step; uint16_t m = (SEGENV.step + 1) % SEGLEN; - setPixelColor( n, SEGCOLOR(1)); - setPixelColor( m, SEGCOLOR(1)); + SEGMENT.setPixelColor( n, SEGCOLOR(1)); + SEGMENT.setPixelColor( m, SEGCOLOR(1)); delay = 20; } else { delay = 30; @@ -909,98 +1023,59 @@ uint16_t WS2812FX::mode_chase_flash(void) { } return delay; } +static const char _data_FX_MODE_CHASE_FLASH[] PROGMEM = "Chase Flash@!,;Bg,Fx,!;!;1d"; /* * Prim flashes running, followed by random color. */ -uint16_t WS2812FX::mode_chase_flash_random(void) { +uint16_t mode_chase_flash_random(void) { uint8_t flash_step = SEGENV.call % ((FLASH_COUNT * 2) + 1); - for(uint16_t i = 0; i < SEGENV.step; i++) { - setPixelColor(i, color_wheel(SEGENV.aux0)); + for (int i = 0; i < SEGENV.aux1; i++) { + SEGMENT.setPixelColor(i, SEGMENT.color_wheel(SEGENV.aux0)); } uint16_t delay = 1 + ((10 * (uint16_t)(255 - SEGMENT.speed)) / SEGLEN); if(flash_step < (FLASH_COUNT * 2)) { - uint16_t n = SEGENV.step; - uint16_t m = (SEGENV.step + 1) % SEGLEN; + uint16_t n = SEGENV.aux1; + uint16_t m = (SEGENV.aux1 + 1) % SEGLEN; if(flash_step % 2 == 0) { - setPixelColor( n, SEGCOLOR(0)); - setPixelColor( m, SEGCOLOR(0)); + SEGMENT.setPixelColor( n, SEGCOLOR(0)); + SEGMENT.setPixelColor( m, SEGCOLOR(0)); delay = 20; } else { - setPixelColor( n, color_wheel(SEGENV.aux0)); - setPixelColor( m, SEGCOLOR(1)); + SEGMENT.setPixelColor( n, SEGMENT.color_wheel(SEGENV.aux0)); + SEGMENT.setPixelColor( m, SEGCOLOR(1)); delay = 30; } } else { - SEGENV.step = (SEGENV.step + 1) % SEGLEN; + SEGENV.aux1 = (SEGENV.aux1 + 1) % SEGLEN; - if (SEGENV.step == 0) { - SEGENV.aux0 = get_random_wheel_index(SEGENV.aux0); + if (SEGENV.aux1 == 0) { + SEGENV.aux0 = SEGMENT.get_random_wheel_index(SEGENV.aux0); } } return delay; } +static const char _data_FX_MODE_CHASE_FLASH_RANDOM[] PROGMEM = "Chase Flash Rnd@!,;,Fx,;0;1d"; -/* - * Alternating pixels running function. - */ -uint16_t WS2812FX::running(uint32_t color1, uint32_t color2, bool theatre) { - uint8_t width = (theatre ? 3 : 1) + (SEGMENT.intensity >> 4); // window - uint32_t cycleTime = 50 + (255 - SEGMENT.speed); - uint32_t it = now / cycleTime; - bool usePalette = color1 == SEGCOLOR(0); - - for(uint16_t i = 0; i < SEGLEN; i++) { - uint32_t col = color2; - if (usePalette) color1 = color_from_palette(i, true, PALETTE_SOLID_WRAP, 0); - if (theatre) { - if ((i % width) == SEGENV.aux0) col = color1; - } else { - int8_t pos = (i % (width<<1)); - if ((pos < SEGENV.aux0-width) || ((pos >= SEGENV.aux0) && (pos < SEGENV.aux0+width))) col = color1; - } - setPixelColor(i,col); - } - - if (it != SEGENV.step) { - SEGENV.aux0 = (SEGENV.aux0 +1) % (theatre ? width : (width<<1)); - SEGENV.step = it; - } - return FRAMETIME; -} - /* * Alternating color/sec pixels running. */ -uint16_t WS2812FX::mode_running_color(void) { +uint16_t mode_running_color(void) { return running(SEGCOLOR(0), SEGCOLOR(1)); } - -/* - * Alternating red/white pixels running. - */ -uint16_t WS2812FX::mode_candy_cane(void) { - return running(RED, WHITE); -} - -/* - * Alternating orange/purple pixels running. - */ -uint16_t WS2812FX::mode_halloween(void) { - return running(PURPLE, ORANGE); -} +static const char _data_FX_MODE_RUNNING_COLOR[] PROGMEM = "Chase 2@!,Width;!,!,;!;1d"; /* * Random colored pixels running. ("Stream") */ -uint16_t WS2812FX::mode_running_random(void) { +uint16_t mode_running_random(void) { uint32_t cycleTime = 25 + (3 * (uint32_t)(255 - SEGMENT.speed)); - uint32_t it = now / cycleTime; + uint32_t it = strip.now / cycleTime; if (SEGENV.call == 0) SEGENV.aux0 = random16(); // random seed for PRNG on start uint8_t zoneSize = ((255-SEGMENT.intensity) >> 4) +1; @@ -1008,7 +1083,7 @@ uint16_t WS2812FX::mode_running_random(void) { uint8_t z = it % zoneSize; bool nzone = (!z && it != SEGENV.aux1); - for (uint16_t i=SEGLEN-1; i > 0; i--) { + for (int i=SEGLEN-1; i > 0; i--) { if (nzone || z >= zoneSize) { uint8_t lastrand = PRNG16 >> 8; int16_t diff = 0; @@ -1022,47 +1097,41 @@ uint16_t WS2812FX::mode_running_random(void) { } z = 0; } - setPixelColor(i, color_wheel(PRNG16 >> 8)); + SEGMENT.setPixelColor(i, SEGMENT.color_wheel(PRNG16 >> 8)); z++; } SEGENV.aux1 = it; return FRAMETIME; } +static const char _data_FX_MODE_RUNNING_RANDOM[] PROGMEM = "Stream"; -/* - * K.I.T.T. - */ -uint16_t WS2812FX::mode_larson_scanner(void){ - return larson_scanner(false); -} - -uint16_t WS2812FX::larson_scanner(bool dual) { - uint16_t counter = now * ((SEGMENT.speed >> 2) +8); +uint16_t larson_scanner(bool dual) { + uint16_t counter = strip.now * ((SEGMENT.speed >> 2) +8); uint16_t index = counter * SEGLEN >> 16; - fade_out(SEGMENT.intensity); + SEGMENT.fade_out(SEGMENT.intensity); if (SEGENV.step > index && SEGENV.step - index > SEGLEN/2) { SEGENV.aux0 = !SEGENV.aux0; } - for (uint16_t i = SEGENV.step; i < index; i++) { + for (int i = SEGENV.step; i < index; i++) { uint16_t j = (SEGENV.aux0)?i:SEGLEN-1-i; - setPixelColor( j, color_from_palette(j, true, PALETTE_SOLID_WRAP, 0)); + SEGMENT.setPixelColor( j, SEGMENT.color_from_palette(j, true, PALETTE_SOLID_WRAP, 0)); } if (dual) { uint32_t c; if (SEGCOLOR(2) != 0) { c = SEGCOLOR(2); } else { - c = color_from_palette(index, true, PALETTE_SOLID_WRAP, 0); + c = SEGMENT.color_from_palette(index, true, PALETTE_SOLID_WRAP, 0); } - for (uint16_t i = SEGENV.step; i < index; i++) { + for (int i = SEGENV.step; i < index; i++) { uint16_t j = (SEGENV.aux0)?SEGLEN-1-i:i; - setPixelColor(j, c); + SEGMENT.setPixelColor(j, c); } } @@ -1071,118 +1140,159 @@ uint16_t WS2812FX::larson_scanner(bool dual) { } +/* + * K.I.T.T. + */ +uint16_t mode_larson_scanner(void){ + return larson_scanner(false); +} +static const char _data_FX_MODE_LARSON_SCANNER[] PROGMEM = "Scanner@!,Fade rate;!,!,;!;mp12=0,1d"; + + +/* + * Creates two Larson scanners moving in opposite directions + * Custom mode by Keith Lord: https://github.com/kitesurfer1404/WS2812FX/blob/master/src/custom/DualLarson.h + */ +uint16_t mode_dual_larson_scanner(void){ + return larson_scanner(true); +} +static const char _data_FX_MODE_DUAL_LARSON_SCANNER[] PROGMEM = "Scanner Dual@!,Fade rate;!,!,;!;mp12=0,1d"; + + /* * Firing comets from one end. "Lighthouse" */ -uint16_t WS2812FX::mode_comet(void) { - uint16_t counter = now * ((SEGMENT.speed >>2) +1); - uint16_t index = counter * SEGLEN >> 16; +uint16_t mode_comet(void) { + uint16_t counter = strip.now * ((SEGMENT.speed >>2) +1); + uint16_t index = (counter * SEGLEN) >> 16; if (SEGENV.call == 0) SEGENV.aux0 = index; - fade_out(SEGMENT.intensity); + SEGMENT.fade_out(SEGMENT.intensity); - setPixelColor( index, color_from_palette(index, true, PALETTE_SOLID_WRAP, 0)); + SEGMENT.setPixelColor( index, SEGMENT.color_from_palette(index, true, PALETTE_SOLID_WRAP, 0)); if (index > SEGENV.aux0) { - for (uint16_t i = SEGENV.aux0; i < index ; i++) { - setPixelColor( i, color_from_palette(i, true, PALETTE_SOLID_WRAP, 0)); + for (int i = SEGENV.aux0; i < index ; i++) { + SEGMENT.setPixelColor( i, SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0)); } } else if (index < SEGENV.aux0 && index < 10) { - for (uint16_t i = 0; i < index ; i++) { - setPixelColor( i, color_from_palette(i, true, PALETTE_SOLID_WRAP, 0)); + for (int i = 0; i < index ; i++) { + SEGMENT.setPixelColor( i, SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0)); } } SEGENV.aux0 = index++; return FRAMETIME; } +static const char _data_FX_MODE_COMET[] PROGMEM = "Lighthouse@!,Fade rate;!,!,!;!;1d"; /* * Fireworks function. */ -uint16_t WS2812FX::mode_fireworks() { - fade_out(0); +uint16_t mode_fireworks() { + const uint16_t width = strip.isMatrix ? SEGMENT.virtualWidth() : SEGMENT.virtualLength(); + const uint16_t height = SEGMENT.virtualHeight(); + + SEGMENT.fade_out(0); + if (SEGENV.call == 0) { SEGENV.aux0 = UINT16_MAX; SEGENV.aux1 = UINT16_MAX; } - bool valid1 = (SEGENV.aux0 < SEGLEN); - bool valid2 = (SEGENV.aux1 < SEGLEN); + bool valid1 = (SEGENV.aux0 < width*height); + bool valid2 = (SEGENV.aux1 < width*height); uint32_t sv1 = 0, sv2 = 0; - if (valid1) sv1 = getPixelColor(SEGENV.aux0); - if (valid2) sv2 = getPixelColor(SEGENV.aux1); - blur(255-SEGMENT.speed); - if (valid1) setPixelColor(SEGENV.aux0 , sv1); - if (valid2) setPixelColor(SEGENV.aux1, sv2); + if (valid1) sv1 = strip.isMatrix ? SEGMENT.getPixelColorXY(SEGENV.aux0%width, SEGENV.aux0/width) : SEGMENT.getPixelColor(SEGENV.aux0); // TODO get spark color + if (valid2) sv2 = strip.isMatrix ? SEGMENT.getPixelColorXY(SEGENV.aux1%width, SEGENV.aux1/width) : SEGMENT.getPixelColor(SEGENV.aux1); // TODO + if (!SEGENV.step) SEGMENT.blur(16); + if (valid1) { if (strip.isMatrix) SEGMENT.setPixelColorXY(SEGENV.aux0%width, SEGENV.aux0/width, sv1); else SEGMENT.setPixelColor(SEGENV.aux0, sv1); } // restore spark color after blur + if (valid2) { if (strip.isMatrix) SEGMENT.setPixelColorXY(SEGENV.aux1%width, SEGENV.aux1/width, sv2); else SEGMENT.setPixelColor(SEGENV.aux1, sv2); } // restore old spark color after blur - for(uint16_t i=0; i> 1)) == 0) { - uint16_t index = random(SEGLEN); - setPixelColor(index, color_from_palette(random8(), false, false, 0)); - SEGENV.aux1 = SEGENV.aux0; - SEGENV.aux0 = index; + for (int i=0; i> 1)) == 0) { + uint16_t index = random16(width*height); + uint16_t j = index % width, k = index / width; + uint32_t col = SEGMENT.color_from_palette(random8(), false, false, 0); + if (strip.isMatrix) SEGMENT.setPixelColorXY(j, k, col); + else SEGMENT.setPixelColor(index, col); + SEGENV.aux1 = SEGENV.aux0; // old spark + SEGENV.aux0 = index; // remember where spark occured } } return FRAMETIME; } +static const char _data_FX_MODE_FIREWORKS[] PROGMEM = "Fireworks@,Frequency;!,!,;!;ix=192,pal=11,1d,2d"; //Twinkling LEDs running. Inspired by https://github.com/kitesurfer1404/WS2812FX/blob/master/src/custom/Rain.h -uint16_t WS2812FX::mode_rain() +uint16_t mode_rain() { + const uint16_t width = SEGMENT.virtualWidth(); + const uint16_t height = SEGMENT.virtualHeight(); SEGENV.step += FRAMETIME; if (SEGENV.step > SPEED_FORMULA_L) { - SEGENV.step = 0; - //shift all leds left - uint32_t ctemp = getPixelColor(0); - for(uint16_t i = 0; i < SEGLEN - 1; i++) { - setPixelColor(i, getPixelColor(i+1)); + SEGENV.step = 1; + if (strip.isMatrix) { + uint32_t ctemp[width]; + for (int i = 0; i= width*height) SEGENV.aux0 = 0; // ignore + if (SEGENV.aux1 >= width*height) SEGENV.aux1 = 0; } return mode_fireworks(); } +static const char _data_FX_MODE_RAIN[] PROGMEM = "Rain@!,Spawning rate;!,!,;;ix=128,pal=0,1d,2d"; /* * Fire flicker function */ -uint16_t WS2812FX::mode_fire_flicker(void) { +uint16_t mode_fire_flicker(void) { uint32_t cycleTime = 40 + (255 - SEGMENT.speed); - uint32_t it = now / cycleTime; + uint32_t it = strip.now / cycleTime; if (SEGENV.step == it) return FRAMETIME; - + byte w = (SEGCOLOR(0) >> 24); byte r = (SEGCOLOR(0) >> 16); byte g = (SEGCOLOR(0) >> 8); byte b = (SEGCOLOR(0) ); byte lum = (SEGMENT.palette == 0) ? MAX(w, MAX(r, MAX(g, b))) : 255; lum /= (((256-SEGMENT.intensity)/16)+1); - for(uint16_t i = 0; i < SEGLEN; i++) { + for (int i = 0; i < SEGLEN; i++) { byte flicker = random8(lum); if (SEGMENT.palette == 0) { - setPixelColor(i, MAX(r - flicker, 0), MAX(g - flicker, 0), MAX(b - flicker, 0), MAX(w - flicker, 0)); + SEGMENT.setPixelColor(i, MAX(r - flicker, 0), MAX(g - flicker, 0), MAX(b - flicker, 0), MAX(w - flicker, 0)); } else { - setPixelColor(i, color_from_palette(i, true, PALETTE_SOLID_WRAP, 0, 255 - flicker)); + SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0, 255 - flicker)); } } SEGENV.step = it; return FRAMETIME; } +static const char _data_FX_MODE_FIRE_FLICKER[] PROGMEM = "Fire Flicker@!,!;!,,;!;1d"; /* * Gradient run base function */ -uint16_t WS2812FX::gradient_base(bool loading) { - uint16_t counter = now * ((SEGMENT.speed >> 2) + 1); +uint16_t gradient_base(bool loading) { + uint16_t counter = strip.now * ((SEGMENT.speed >> 2) + 1); uint16_t pp = counter * SEGLEN >> 16; if (SEGENV.call == 0) pp = 0; float val; //0.0 = sec 1.0 = pri @@ -1191,7 +1301,7 @@ uint16_t WS2812FX::gradient_base(bool loading) { int p1 = pp-SEGLEN; int p2 = pp+SEGLEN; - for(uint16_t i = 0; i < SEGLEN; i++) + for (int i = 0; i < SEGLEN; i++) { if (loading) { @@ -1200,7 +1310,7 @@ uint16_t WS2812FX::gradient_base(bool loading) { val = MIN(abs(pp-i),MIN(abs(p1-i),abs(p2-i))); } val = (brd > val) ? val/brd * 255 : 255; - setPixelColor(i, color_blend(SEGCOLOR(0), color_from_palette(i, true, PALETTE_SOLID_WRAP, 1), val)); + SEGMENT.setPixelColor(i, color_blend(SEGCOLOR(0), SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 1), val)); } return FRAMETIME; @@ -1210,54 +1320,58 @@ uint16_t WS2812FX::gradient_base(bool loading) { /* * Gradient run */ -uint16_t WS2812FX::mode_gradient(void) { +uint16_t mode_gradient(void) { return gradient_base(false); } +static const char _data_FX_MODE_GRADIENT[] PROGMEM = "Gradient@!,Spread;!,!,;!;ix=16,1d"; /* * Gradient run with hard transition */ -uint16_t WS2812FX::mode_loading(void) { +uint16_t mode_loading(void) { return gradient_base(true); } +static const char _data_FX_MODE_LOADING[] PROGMEM = "Loading@!,Fade;!,!,;!;ix=16,1d"; //American Police Light with all LEDs Red and Blue -uint16_t WS2812FX::police_base(uint32_t color1, uint32_t color2) +uint16_t police_base(uint32_t color1, uint32_t color2) { uint16_t delay = 1 + (FRAMETIME<<3) / SEGLEN; // longer segments should change faster - uint32_t it = now / map(SEGMENT.speed, 0, 255, delay<<4, delay); + uint32_t it = strip.now / map(SEGMENT.speed, 0, 255, delay<<4, delay); uint16_t offset = it % SEGLEN; - uint16_t width = ((SEGLEN*(SEGMENT.intensity+1))>>9); //max width is half the strip + uint16_t width = ((SEGLEN*(SEGMENT.intensity+1))>>9); //max width is half the strip if (!width) width = 1; - for (uint16_t i = 0; i < width; i++) { + for (int i = 0; i < width; i++) { uint16_t indexR = (offset + i) % SEGLEN; uint16_t indexB = (offset + i + (SEGLEN>>1)) % SEGLEN; - setPixelColor(indexR, color1); - setPixelColor(indexB, color2); + SEGMENT.setPixelColor(indexR, color1); + SEGMENT.setPixelColor(indexB, color2); } return FRAMETIME; } //Police Lights Red and Blue -uint16_t WS2812FX::mode_police() -{ - fill(SEGCOLOR(1)); - return police_base(RED, BLUE); -} +//uint16_t mode_police() +//{ +// SEGMENT.fill(SEGCOLOR(1)); +// return police_base(RED, BLUE); +//} +//static const char _data_FX_MODE_POLICE[] PROGMEM = "Police@!,Width;,Bg,;0"; //Police Lights with custom colors -uint16_t WS2812FX::mode_two_dots() +uint16_t mode_two_dots() { - fill(SEGCOLOR(2)); + SEGMENT.fill(SEGCOLOR(2)); uint32_t color2 = (SEGCOLOR(1) == SEGCOLOR(2)) ? SEGCOLOR(0) : SEGCOLOR(1); return police_base(SEGCOLOR(0), color2); } +static const char _data_FX_MODE_TWO_DOTS[] PROGMEM = "Two Dots@!,Dot size;1,2,Bg;!;1d"; /* @@ -1267,151 +1381,153 @@ uint16_t WS2812FX::mode_two_dots() typedef struct Flasher { uint16_t stateStart; uint8_t stateDur; - bool stateOn; + bool stateOn; } flasher; #define FLASHERS_PER_ZONE 6 #define MAX_SHIMMER 92 -uint16_t WS2812FX::mode_fairy() { - //set every pixel to a 'random' color from palette (using seed so it doesn't change between frames) - uint16_t PRNG16 = 5100 + _segment_index; - for (uint16_t i = 0; i < SEGLEN; i++) { - PRNG16 = (uint16_t)(PRNG16 * 2053) + 1384; //next 'random' number - setPixelColor(i, color_from_palette(PRNG16 >> 8, false, false, 0)); - } +uint16_t mode_fairy() { + //set every pixel to a 'random' color from palette (using seed so it doesn't change between frames) + uint16_t PRNG16 = 5100 + strip.getCurrSegmentId(); + for (int i = 0; i < SEGLEN; i++) { + PRNG16 = (uint16_t)(PRNG16 * 2053) + 1384; //next 'random' number + SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(PRNG16 >> 8, false, false, 0)); + } - //amount of flasher pixels depending on intensity (0: none, 255: every LED) - if (SEGMENT.intensity == 0) return FRAMETIME; - uint8_t flasherDistance = ((255 - SEGMENT.intensity) / 28) +1; //1-10 - uint16_t numFlashers = (SEGLEN / flasherDistance) +1; - - uint16_t dataSize = sizeof(flasher) * numFlashers; + //amount of flasher pixels depending on intensity (0: none, 255: every LED) + if (SEGMENT.intensity == 0) return FRAMETIME; + uint8_t flasherDistance = ((255 - SEGMENT.intensity) / 28) +1; //1-10 + uint16_t numFlashers = (SEGLEN / flasherDistance) +1; + + uint16_t dataSize = sizeof(flasher) * numFlashers; if (!SEGENV.allocateData(dataSize)) return FRAMETIME; //allocation failed - Flasher* flashers = reinterpret_cast(SEGENV.data); - uint16_t now16 = now & 0xFFFF; + Flasher* flashers = reinterpret_cast(SEGENV.data); + uint16_t now16 = strip.now & 0xFFFF; - //Up to 11 flashers in one brightness zone, afterwards a new zone for every 6 flashers - uint16_t zones = numFlashers/FLASHERS_PER_ZONE; - if (!zones) zones = 1; - uint8_t flashersInZone = numFlashers/zones; - uint8_t flasherBri[FLASHERS_PER_ZONE*2 -1]; + //Up to 11 flashers in one brightness zone, afterwards a new zone for every 6 flashers + uint16_t zones = numFlashers/FLASHERS_PER_ZONE; + if (!zones) zones = 1; + uint8_t flashersInZone = numFlashers/zones; + uint8_t flasherBri[FLASHERS_PER_ZONE*2 -1]; - for (uint16_t z = 0; z < zones; z++) { - uint16_t flasherBriSum = 0; - uint16_t firstFlasher = z*flashersInZone; - if (z == zones-1) flashersInZone = numFlashers-(flashersInZone*(zones-1)); + for (int z = 0; z < zones; z++) { + uint16_t flasherBriSum = 0; + uint16_t firstFlasher = z*flashersInZone; + if (z == zones-1) flashersInZone = numFlashers-(flashersInZone*(zones-1)); - for (uint16_t f = firstFlasher; f < firstFlasher + flashersInZone; f++) { - uint16_t stateTime = now16 - flashers[f].stateStart; - //random on/off time reached, switch state - if (stateTime > flashers[f].stateDur * 10) { - flashers[f].stateOn = !flashers[f].stateOn; - if (flashers[f].stateOn) { - flashers[f].stateDur = 12 + random8(12 + ((255 - SEGMENT.speed) >> 2)); //*10, 250ms to 1250ms - } else { - flashers[f].stateDur = 20 + random8(6 + ((255 - SEGMENT.speed) >> 2)); //*10, 250ms to 1250ms - } - //flashers[f].stateDur = 51 + random8(2 + ((255 - SEGMENT.speed) >> 1)); - flashers[f].stateStart = now16; - if (stateTime < 255) { - flashers[f].stateStart -= 255 -stateTime; //start early to get correct bri - flashers[f].stateDur += 26 - stateTime/10; - stateTime = 255 - stateTime; - } else { - stateTime = 0; - } - } - if (stateTime > 255) stateTime = 255; //for flasher brightness calculation, fades in first 255 ms of state - //flasherBri[f - firstFlasher] = (flashers[f].stateOn) ? 255-gamma8((510 - stateTime) >> 1) : gamma8((510 - stateTime) >> 1); - flasherBri[f - firstFlasher] = (flashers[f].stateOn) ? stateTime : 255 - (stateTime >> 0); - flasherBriSum += flasherBri[f - firstFlasher]; - } - //dim factor, to create "shimmer" as other pixels get less voltage if a lot of flashers are on - uint8_t avgFlasherBri = flasherBriSum / flashersInZone; - uint8_t globalPeakBri = 255 - ((avgFlasherBri * MAX_SHIMMER) >> 8); //183-255, suitable for 1/5th of LEDs flashers + for (int f = firstFlasher; f < firstFlasher + flashersInZone; f++) { + uint16_t stateTime = now16 - flashers[f].stateStart; + //random on/off time reached, switch state + if (stateTime > flashers[f].stateDur * 10) { + flashers[f].stateOn = !flashers[f].stateOn; + if (flashers[f].stateOn) { + flashers[f].stateDur = 12 + random8(12 + ((255 - SEGMENT.speed) >> 2)); //*10, 250ms to 1250ms + } else { + flashers[f].stateDur = 20 + random8(6 + ((255 - SEGMENT.speed) >> 2)); //*10, 250ms to 1250ms + } + //flashers[f].stateDur = 51 + random8(2 + ((255 - SEGMENT.speed) >> 1)); + flashers[f].stateStart = now16; + if (stateTime < 255) { + flashers[f].stateStart -= 255 -stateTime; //start early to get correct bri + flashers[f].stateDur += 26 - stateTime/10; + stateTime = 255 - stateTime; + } else { + stateTime = 0; + } + } + if (stateTime > 255) stateTime = 255; //for flasher brightness calculation, fades in first 255 ms of state + //flasherBri[f - firstFlasher] = (flashers[f].stateOn) ? 255-SEGMENT.gamma8((510 - stateTime) >> 1) : SEGMENT.gamma8((510 - stateTime) >> 1); + flasherBri[f - firstFlasher] = (flashers[f].stateOn) ? stateTime : 255 - (stateTime >> 0); + flasherBriSum += flasherBri[f - firstFlasher]; + } + //dim factor, to create "shimmer" as other pixels get less voltage if a lot of flashers are on + uint8_t avgFlasherBri = flasherBriSum / flashersInZone; + uint8_t globalPeakBri = 255 - ((avgFlasherBri * MAX_SHIMMER) >> 8); //183-255, suitable for 1/5th of LEDs flashers - for (uint16_t f = firstFlasher; f < firstFlasher + flashersInZone; f++) { - uint8_t bri = (flasherBri[f - firstFlasher] * globalPeakBri) / 255; - PRNG16 = (uint16_t)(PRNG16 * 2053) + 1384; //next 'random' number - uint16_t flasherPos = f*flasherDistance; - setPixelColor(flasherPos, color_blend(SEGCOLOR(1), color_from_palette(PRNG16 >> 8, false, false, 0), bri)); - for (uint16_t i = flasherPos+1; i < flasherPos+flasherDistance && i < SEGLEN; i++) { - PRNG16 = (uint16_t)(PRNG16 * 2053) + 1384; //next 'random' number - setPixelColor(i, color_from_palette(PRNG16 >> 8, false, false, 0, globalPeakBri)); - } - } - } - return FRAMETIME; + for (int f = firstFlasher; f < firstFlasher + flashersInZone; f++) { + uint8_t bri = (flasherBri[f - firstFlasher] * globalPeakBri) / 255; + PRNG16 = (uint16_t)(PRNG16 * 2053) + 1384; //next 'random' number + uint16_t flasherPos = f*flasherDistance; + SEGMENT.setPixelColor(flasherPos, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(PRNG16 >> 8, false, false, 0), bri)); + for (int i = flasherPos+1; i < flasherPos+flasherDistance && i < SEGLEN; i++) { + PRNG16 = (uint16_t)(PRNG16 * 2053) + 1384; //next 'random' number + SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(PRNG16 >> 8, false, false, 0, globalPeakBri)); + } + } + } + return FRAMETIME; } +static const char _data_FX_MODE_FAIRY[] PROGMEM = "Fairy"; /* - * Fairytwinkle. Like Colortwinkle, but starting from all lit and not relying on getPixelColor + * Fairytwinkle. Like Colortwinkle, but starting from all lit and not relying on strip.getPixelColor * Warning: Uses 4 bytes of segment data per pixel */ -uint16_t WS2812FX::mode_fairytwinkle() { - uint16_t dataSize = sizeof(flasher) * SEGLEN; +uint16_t mode_fairytwinkle() { + uint16_t dataSize = sizeof(flasher) * SEGLEN; if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed - Flasher* flashers = reinterpret_cast(SEGENV.data); - uint16_t now16 = now & 0xFFFF; - uint16_t PRNG16 = 5100 + _segment_index; + Flasher* flashers = reinterpret_cast(SEGENV.data); + uint16_t now16 = strip.now & 0xFFFF; + uint16_t PRNG16 = 5100 + strip.getCurrSegmentId(); - uint16_t riseFallTime = 400 + (255-SEGMENT.speed)*3; - uint16_t maxDur = riseFallTime/100 + ((255 - SEGMENT.intensity) >> 2) + 13 + ((255 - SEGMENT.intensity) >> 1); + uint16_t riseFallTime = 400 + (255-SEGMENT.speed)*3; + uint16_t maxDur = riseFallTime/100 + ((255 - SEGMENT.intensity) >> 2) + 13 + ((255 - SEGMENT.intensity) >> 1); - for (uint16_t f = 0; f < SEGLEN; f++) { - uint16_t stateTime = now16 - flashers[f].stateStart; - //random on/off time reached, switch state - if (stateTime > flashers[f].stateDur * 100) { - flashers[f].stateOn = !flashers[f].stateOn; - bool init = !flashers[f].stateDur; - if (flashers[f].stateOn) { - flashers[f].stateDur = riseFallTime/100 + ((255 - SEGMENT.intensity) >> 2) + random8(12 + ((255 - SEGMENT.intensity) >> 1)) +1; - } else { - flashers[f].stateDur = riseFallTime/100 + random8(3 + ((255 - SEGMENT.speed) >> 6)) +1; - } - flashers[f].stateStart = now16; - stateTime = 0; - if (init) { - flashers[f].stateStart -= riseFallTime; //start lit - flashers[f].stateDur = riseFallTime/100 + random8(12 + ((255 - SEGMENT.intensity) >> 1)) +5; //fire up a little quicker - stateTime = riseFallTime; - } - } - if (flashers[f].stateOn && flashers[f].stateDur > maxDur) flashers[f].stateDur = maxDur; //react more quickly on intensity change - if (stateTime > riseFallTime) stateTime = riseFallTime; //for flasher brightness calculation, fades in first 255 ms of state - uint8_t fadeprog = 255 - ((stateTime * 255) / riseFallTime); - uint8_t flasherBri = (flashers[f].stateOn) ? 255-gamma8(fadeprog) : gamma8(fadeprog); - uint16_t lastR = PRNG16; - uint16_t diff = 0; - while (diff < 0x4000) { //make sure colors of two adjacent LEDs differ enough - PRNG16 = (uint16_t)(PRNG16 * 2053) + 1384; //next 'random' number - diff = (PRNG16 > lastR) ? PRNG16 - lastR : lastR - PRNG16; - } - setPixelColor(f, color_blend(SEGCOLOR(1), color_from_palette(PRNG16 >> 8, false, false, 0), flasherBri)); - } + for (int f = 0; f < SEGLEN; f++) { + uint16_t stateTime = now16 - flashers[f].stateStart; + //random on/off time reached, switch state + if (stateTime > flashers[f].stateDur * 100) { + flashers[f].stateOn = !flashers[f].stateOn; + bool init = !flashers[f].stateDur; + if (flashers[f].stateOn) { + flashers[f].stateDur = riseFallTime/100 + ((255 - SEGMENT.intensity) >> 2) + random8(12 + ((255 - SEGMENT.intensity) >> 1)) +1; + } else { + flashers[f].stateDur = riseFallTime/100 + random8(3 + ((255 - SEGMENT.speed) >> 6)) +1; + } + flashers[f].stateStart = now16; + stateTime = 0; + if (init) { + flashers[f].stateStart -= riseFallTime; //start lit + flashers[f].stateDur = riseFallTime/100 + random8(12 + ((255 - SEGMENT.intensity) >> 1)) +5; //fire up a little quicker + stateTime = riseFallTime; + } + } + if (flashers[f].stateOn && flashers[f].stateDur > maxDur) flashers[f].stateDur = maxDur; //react more quickly on intensity change + if (stateTime > riseFallTime) stateTime = riseFallTime; //for flasher brightness calculation, fades in first 255 ms of state + uint8_t fadeprog = 255 - ((stateTime * 255) / riseFallTime); + uint8_t flasherBri = (flashers[f].stateOn) ? 255-gamma8(fadeprog) : gamma8(fadeprog); + uint16_t lastR = PRNG16; + uint16_t diff = 0; + while (diff < 0x4000) { //make sure colors of two adjacent LEDs differ enough + PRNG16 = (uint16_t)(PRNG16 * 2053) + 1384; //next 'random' number + diff = (PRNG16 > lastR) ? PRNG16 - lastR : lastR - PRNG16; + } + SEGMENT.setPixelColor(f, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(PRNG16 >> 8, false, false, 0), flasherBri)); + } return FRAMETIME; } +static const char _data_FX_MODE_FAIRYTWINKLE[] PROGMEM = "Fairy Twinkle@;;;mp12=0,1d"; //pixels /* * Tricolor chase function */ -uint16_t WS2812FX::tricolor_chase(uint32_t color1, uint32_t color2) { +uint16_t tricolor_chase(uint32_t color1, uint32_t color2) { uint32_t cycleTime = 50 + ((255 - SEGMENT.speed)<<1); - uint32_t it = now / cycleTime; // iterator + uint32_t it = strip.now / cycleTime; // iterator uint8_t width = (1 + (SEGMENT.intensity>>4)); // value of 1-16 for each colour uint8_t index = it % (width*3); - for (uint16_t i = 0; i < SEGLEN; i++, index++) { + for (int i = 0; i < SEGLEN; i++, index++) { if (index > (width*3)-1) index = 0; uint32_t color = color1; - if (index > (width<<1)-1) color = color_from_palette(i, true, PALETTE_SOLID_WRAP, 1); + if (index > (width<<1)-1) color = SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 1); else if (index > width-1) color = color2; - setPixelColor(SEGLEN - i -1, color); + SEGMENT.setPixelColor(SEGLEN - i -1, color); } return FRAMETIME; } @@ -1420,30 +1536,31 @@ uint16_t WS2812FX::tricolor_chase(uint32_t color1, uint32_t color2) { /* * Tricolor chase mode */ -uint16_t WS2812FX::mode_tricolor_chase(void) { +uint16_t mode_tricolor_chase(void) { return tricolor_chase(SEGCOLOR(2), SEGCOLOR(0)); } +static const char _data_FX_MODE_TRICOLOR_CHASE[] PROGMEM = "Chase 3@!,Size;1,2,3;0;1d"; /* * ICU mode */ -uint16_t WS2812FX::mode_icu(void) { +uint16_t mode_icu(void) { uint16_t dest = SEGENV.step & 0xFFFF; uint8_t space = (SEGMENT.intensity >> 3) +2; - fill(SEGCOLOR(1)); + SEGMENT.fill(SEGCOLOR(1)); byte pindex = map(dest, 0, SEGLEN-SEGLEN/space, 0, 255); - uint32_t col = color_from_palette(pindex, false, false, 0); + uint32_t col = SEGMENT.color_from_palette(pindex, false, false, 0); - setPixelColor(dest, col); - setPixelColor(dest + SEGLEN/space, col); + SEGMENT.setPixelColor(dest, col); + SEGMENT.setPixelColor(dest + SEGLEN/space, col); if(SEGENV.aux0 == dest) { // pause between eye movements if(random8(6) == 0) { // blink once in a while - setPixelColor(dest, SEGCOLOR(1)); - setPixelColor(dest + SEGLEN/space, SEGCOLOR(1)); + SEGMENT.setPixelColor(dest, SEGCOLOR(1)); + SEGMENT.setPixelColor(dest + SEGLEN/space, SEGCOLOR(1)); return 200; } SEGENV.aux0 = random16(SEGLEN-SEGLEN/space); @@ -1458,51 +1575,53 @@ uint16_t WS2812FX::mode_icu(void) { dest--; } - setPixelColor(dest, col); - setPixelColor(dest + SEGLEN/space, col); + SEGMENT.setPixelColor(dest, col); + SEGMENT.setPixelColor(dest + SEGLEN/space, col); return SPEED_FORMULA_L; } +static const char _data_FX_MODE_ICU[] PROGMEM = "ICU"; /* * Custom mode by Aircoookie. Color Wipe, but with 3 colors */ -uint16_t WS2812FX::mode_tricolor_wipe(void) +uint16_t mode_tricolor_wipe(void) { uint32_t cycleTime = 1000 + (255 - SEGMENT.speed)*200; - uint32_t perc = now % cycleTime; + uint32_t perc = strip.now % cycleTime; uint16_t prog = (perc * 65535) / cycleTime; uint16_t ledIndex = (prog * SEGLEN * 3) >> 16; uint16_t ledOffset = ledIndex; - for (uint16_t i = 0; i < SEGLEN; i++) + for (int i = 0; i < SEGLEN; i++) { - setPixelColor(i, color_from_palette(i, true, PALETTE_SOLID_WRAP, 2)); + SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 2)); } if(ledIndex < SEGLEN) { //wipe from 0 to 1 - for (uint16_t i = 0; i < SEGLEN; i++) + for (int i = 0; i < SEGLEN; i++) { - setPixelColor(i, (i > ledOffset)? SEGCOLOR(0) : SEGCOLOR(1)); + SEGMENT.setPixelColor(i, (i > ledOffset)? SEGCOLOR(0) : SEGCOLOR(1)); } } else if (ledIndex < SEGLEN*2) { //wipe from 1 to 2 ledOffset = ledIndex - SEGLEN; - for (uint16_t i = ledOffset +1; i < SEGLEN; i++) + for (int i = ledOffset +1; i < SEGLEN; i++) { - setPixelColor(i, SEGCOLOR(1)); + SEGMENT.setPixelColor(i, SEGCOLOR(1)); } } else //wipe from 2 to 0 { ledOffset = ledIndex - SEGLEN*2; - for (uint16_t i = 0; i <= ledOffset; i++) + for (int i = 0; i <= ledOffset; i++) { - setPixelColor(i, SEGCOLOR(0)); + SEGMENT.setPixelColor(i, SEGCOLOR(0)); } } return FRAMETIME; } +static const char _data_FX_MODE_TRICOLOR_WIPE[] PROGMEM = "Tri Wipe@!,;1,2,3;0;1d"; /* @@ -1510,9 +1629,9 @@ uint16_t WS2812FX::mode_tricolor_wipe(void) * Custom mode by Keith Lord: https://github.com/kitesurfer1404/WS2812FX/blob/master/src/custom/TriFade.h * Modified by Aircoookie */ -uint16_t WS2812FX::mode_tricolor_fade(void) +uint16_t mode_tricolor_fade(void) { - uint16_t counter = now * ((SEGMENT.speed >> 3) +1); + uint16_t counter = strip.now * ((SEGMENT.speed >> 3) +1); uint32_t prog = (counter * 768) >> 16; uint32_t color1 = 0, color2 = 0; @@ -1533,46 +1652,47 @@ uint16_t WS2812FX::mode_tricolor_fade(void) } byte stp = prog; // % 256 - for(uint16_t i = 0; i < SEGLEN; i++) { + for (int i = 0; i < SEGLEN; i++) { uint32_t color; if (stage == 2) { - color = color_blend(color_from_palette(i, true, PALETTE_SOLID_WRAP, 2), color2, stp); + color = color_blend(SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 2), color2, stp); } else if (stage == 1) { - color = color_blend(color1, color_from_palette(i, true, PALETTE_SOLID_WRAP, 2), stp); + color = color_blend(color1, SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 2), stp); } else { color = color_blend(color1, color2, stp); } - setPixelColor(i, color); + SEGMENT.setPixelColor(i, color); } return FRAMETIME; } +static const char _data_FX_MODE_TRICOLOR_FADE[] PROGMEM = "Tri Fade"; /* * Creates random comets * Custom mode by Keith Lord: https://github.com/kitesurfer1404/WS2812FX/blob/master/src/custom/MultiComet.h */ -uint16_t WS2812FX::mode_multi_comet(void) +uint16_t mode_multi_comet(void) { uint32_t cycleTime = 10 + (uint32_t)(255 - SEGMENT.speed); - uint32_t it = now / cycleTime; + uint32_t it = strip.now / cycleTime; if (SEGENV.step == it) return FRAMETIME; if (!SEGENV.allocateData(sizeof(uint16_t) * 8)) return mode_static(); //allocation failed - fade_out(SEGMENT.intensity); + SEGMENT.fade_out(SEGMENT.intensity); uint16_t* comets = reinterpret_cast(SEGENV.data); - for(uint8_t i=0; i < 8; i++) { + for (int i=0; i < 8; i++) { if(comets[i] < SEGLEN) { uint16_t index = comets[i]; if (SEGCOLOR(2) != 0) { - setPixelColor(index, i % 2 ? color_from_palette(index, true, PALETTE_SOLID_WRAP, 0) : SEGCOLOR(2)); + SEGMENT.setPixelColor(index, i % 2 ? SEGMENT.color_from_palette(index, true, PALETTE_SOLID_WRAP, 0) : SEGCOLOR(2)); } else { - setPixelColor(index, color_from_palette(index, true, PALETTE_SOLID_WRAP, 0)); + SEGMENT.setPixelColor(index, SEGMENT.color_from_palette(index, true, PALETTE_SOLID_WRAP, 0)); } comets[i]++; } else { @@ -1585,22 +1705,14 @@ uint16_t WS2812FX::mode_multi_comet(void) SEGENV.step = it; return FRAMETIME; } - - -/* - * Creates two Larson scanners moving in opposite directions - * Custom mode by Keith Lord: https://github.com/kitesurfer1404/WS2812FX/blob/master/src/custom/DualLarson.h - */ -uint16_t WS2812FX::mode_dual_larson_scanner(void){ - return larson_scanner(true); -} +static const char _data_FX_MODE_MULTI_COMET[] PROGMEM = "Multi Comet"; /* * Running random pixels ("Stream 2") * Custom mode by Keith Lord: https://github.com/kitesurfer1404/WS2812FX/blob/master/src/custom/RandomChase.h */ -uint16_t WS2812FX::mode_random_chase(void) +uint16_t mode_random_chase(void) { if (SEGENV.call == 0) { SEGENV.step = RGBW32(random8(), random8(), random8(), 0); @@ -1608,16 +1720,16 @@ uint16_t WS2812FX::mode_random_chase(void) } uint16_t prevSeed = random16_get_seed(); // save seed so we can restore it at the end of the function uint32_t cycleTime = 25 + (3 * (uint32_t)(255 - SEGMENT.speed)); - uint32_t it = now / cycleTime; + uint32_t it = strip.now / cycleTime; uint32_t color = SEGENV.step; random16_set_seed(SEGENV.aux0); - for(uint16_t i = SEGLEN -1; i > 0; i--) { + for (int i = SEGLEN -1; i > 0; i--) { uint8_t r = random8(6) != 0 ? (color >> 16 & 0xFF) : random8(); uint8_t g = random8(6) != 0 ? (color >> 8 & 0xFF) : random8(); uint8_t b = random8(6) != 0 ? (color & 0xFF) : random8(); color = RGBW32(r, g, b, 0); - setPixelColor(i, r, g, b); + SEGMENT.setPixelColor(i, r, g, b); if (i == SEGLEN -1 && SEGENV.aux1 != (it & 0xFFFF)) { //new first color in next frame SEGENV.step = color; SEGENV.aux0 = random16_get_seed(); @@ -1629,6 +1741,8 @@ uint16_t WS2812FX::mode_random_chase(void) random16_set_seed(prevSeed); // restore original seed so other effects can use "random" PRNG return FRAMETIME; } +static const char _data_FX_MODE_RANDOM_CHASE[] PROGMEM = "Stream 2"; + //7 bytes typedef struct Oscillator { @@ -1641,7 +1755,7 @@ typedef struct Oscillator { /* / Oscillating bars of color, updated with standard framerate */ -uint16_t WS2812FX::mode_oscillate(void) +uint16_t mode_oscillate(void) { uint8_t numOscillators = 3; uint16_t dataSize = sizeof(oscillator) * numOscillators; @@ -1658,9 +1772,9 @@ uint16_t WS2812FX::mode_oscillate(void) } uint32_t cycleTime = 20 + (2 * (uint32_t)(255 - SEGMENT.speed)); - uint32_t it = now / cycleTime; + uint32_t it = strip.now / cycleTime; - for(uint8_t i = 0; i < numOscillators; i++) { + for (int i = 0; i < numOscillators; i++) { // if the counter has increased, move the oscillator by the random step if (it != SEGENV.step) oscillators[i].pos += oscillators[i].dir * oscillators[i].speed; oscillators[i].size = SEGLEN/(3+SEGMENT.intensity/8); @@ -1677,22 +1791,24 @@ uint16_t WS2812FX::mode_oscillate(void) } } - for(uint16_t i=0; i < SEGLEN; i++) { + for (int i=0; i < SEGLEN; i++) { uint32_t color = BLACK; - for(uint8_t j=0; j < numOscillators; j++) { + for (int j=0; j < numOscillators; j++) { if(i >= oscillators[j].pos - oscillators[j].size && i <= oscillators[j].pos + oscillators[j].size) { color = (color == BLACK) ? SEGCOLOR(j) : color_blend(color, SEGCOLOR(j), 128); } } - setPixelColor(i, color); + SEGMENT.setPixelColor(i, color); } SEGENV.step = it; return FRAMETIME; } +static const char _data_FX_MODE_OSCILLATE[] PROGMEM = "Oscillate"; -uint16_t WS2812FX::mode_lightning(void) +//TODO +uint16_t mode_lightning(void) { uint16_t ledstart = random16(SEGLEN); // Determine starting location of flash uint16_t ledlen = 1 + random16(SEGLEN -ledstart); // Determine length of flash (not to go beyond NUM_LEDS-1) @@ -1707,12 +1823,12 @@ uint16_t WS2812FX::mode_lightning(void) SEGENV.aux0 = 200; //200ms delay after leader } - fill(SEGCOLOR(1)); + SEGMENT.fill(SEGCOLOR(1)); if (SEGENV.aux1 > 3 && !(SEGENV.aux1 & 0x01)) { //flash on even number >2 for (int i = ledstart; i < ledstart + ledlen; i++) { - setPixelColor(i,color_from_palette(i, true, PALETTE_SOLID_WRAP, 0, bri)); + SEGMENT.setPixelColor(i,SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0, bri)); } SEGENV.aux1--; @@ -1732,12 +1848,13 @@ uint16_t WS2812FX::mode_lightning(void) } return FRAMETIME; } +static const char _data_FX_MODE_LIGHTNING[] PROGMEM = "Lightning"; // Pride2015 // Animated, ever-changing rainbows. // by Mark Kriegsman: https://gist.github.com/kriegsman/964de772d64c502760e5 -uint16_t WS2812FX::mode_pride_2015(void) +uint16_t mode_pride_2015(void) { uint16_t duration = 10 + SEGMENT.speed; uint16_t sPseudotime = SEGENV.step; @@ -1756,7 +1873,7 @@ uint16_t WS2812FX::mode_pride_2015(void) uint16_t brightnesstheta16 = sPseudotime; CRGB fastled_col; - for (uint16_t i = 0 ; i < SEGLEN; i++) { + for (int i = 0 ; i < SEGLEN; i++) { hue16 += hueinc16; uint8_t hue8 = hue16 >> 8; @@ -1768,53 +1885,53 @@ uint16_t WS2812FX::mode_pride_2015(void) bri8 += (255 - brightdepth); CRGB newcolor = CHSV( hue8, sat8, bri8); - fastled_col = col_to_crgb(getPixelColor(i)); + fastled_col = CRGB(SEGMENT.getPixelColor(i)); // TODO nblend(fastled_col, newcolor, 64); - setPixelColor(i, fastled_col.red, fastled_col.green, fastled_col.blue); + SEGMENT.setPixelColor(i, fastled_col.red, fastled_col.green, fastled_col.blue); } SEGENV.step = sPseudotime; SEGENV.aux0 = sHue16; return FRAMETIME; } +static const char _data_FX_MODE_PRIDE_2015[] PROGMEM = "Pride 2015@!,;;;1d"; //eight colored dots, weaving in and out of sync with each other -uint16_t WS2812FX::mode_juggle(void){ - fade_out(SEGMENT.intensity); +uint16_t mode_juggle(void){ + SEGMENT.fade_out(SEGMENT.intensity); CRGB fastled_col; byte dothue = 0; - for ( byte i = 0; i < 8; i++) { + for (int i = 0; i < 8; i++) { uint16_t index = 0 + beatsin88((128 + SEGMENT.speed)*(i + 7), 0, SEGLEN -1); - fastled_col = col_to_crgb(getPixelColor(index)); - fastled_col |= (SEGMENT.palette==0)?CHSV(dothue, 220, 255):ColorFromPalette(currentPalette, dothue, 255); - setPixelColor(index, fastled_col.red, fastled_col.green, fastled_col.blue); + fastled_col = CRGB(SEGMENT.getPixelColor(index)); // TODO + fastled_col |= (SEGMENT.palette==0)?CHSV(dothue, 220, 255):ColorFromPalette(SEGPALETTE, dothue, 255); + SEGMENT.setPixelColor(index, fastled_col.red, fastled_col.green, fastled_col.blue); dothue += 32; } return FRAMETIME; } +static const char _data_FX_MODE_JUGGLE[] PROGMEM = "Juggle@!,Trail;!,!,;!;sx=16,ix=240,1d"; -uint16_t WS2812FX::mode_palette() +uint16_t mode_palette() { uint16_t counter = 0; if (SEGMENT.speed != 0) { - counter = (now * ((SEGMENT.speed >> 3) +1)) & 0xFFFF; + counter = (strip.now * ((SEGMENT.speed >> 3) +1)) & 0xFFFF; counter = counter >> 8; } - bool noWrap = (paletteBlend == 2 || (paletteBlend == 0 && SEGMENT.speed == 0)); - for (uint16_t i = 0; i < SEGLEN; i++) + bool noWrap = (strip.paletteBlend == 2 || (strip.paletteBlend == 0 && SEGMENT.speed == 0)); + for (int i = 0; i < SEGLEN; i++) { uint8_t colorIndex = (i * 255 / SEGLEN) - counter; - - if (noWrap) colorIndex = map(colorIndex, 0, 255, 0, 240); //cut off blend at palette "end" - - setPixelColor(i, color_from_palette(colorIndex, false, true, 255)); + SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(colorIndex, false, noWrap, 255)); } return FRAMETIME; } +static const char _data_FX_MODE_PALETTE[] PROGMEM = "Palette@Cycle speed,;1,2,3;!;1d"; // WLED limitation: Analog Clock overlay will NOT work when Fire2012 is active @@ -1833,7 +1950,7 @@ uint16_t WS2812FX::mode_palette() // // Temperature is in arbitrary units from 0 (cold black) to 255 (white hot). // -// This simulation scales it self a bit depending on NUM_LEDS; it should look +// This simulation scales it self a bit depending on SEGLEN; it should look // "OK" on anywhere from 20 to 100 LEDs without too much tweaking. // // I recommend running this simulation at anywhere from 30-100 frames per second, @@ -1845,52 +1962,67 @@ uint16_t WS2812FX::mode_palette() // There are two main parameters you can play with to control the look and // feel of your fire: COOLING (used in step 1 above) (Speed = COOLING), and SPARKING (used // in step 3 above) (Effect Intensity = Sparking). - - -uint16_t WS2812FX::mode_fire_2012() +uint16_t mode_fire_2012() { - uint32_t it = now >> 5; //div 32 - - if (!SEGENV.allocateData(SEGLEN)) return mode_static(); //allocation failed - + uint16_t strips = SEGMENT.nrOfVStrips(); + if (!SEGENV.allocateData(strips * SEGLEN)) return mode_static(); //allocation failed byte* heat = SEGENV.data; - if (it != SEGENV.step) - { - uint8_t ignition = max(7,SEGLEN/10); // ignition area: 10% of segment length or minimum 7 pixels - - // Step 1. Cool down every cell a little - for (uint16_t i = 0; i < SEGLEN; i++) { - uint8_t temp = qsub8(heat[i], random8(0, (((20 + SEGMENT.speed /3) * 10) / SEGLEN) + 2)); - heat[i] = (temp==0 && i 1; k--) { - heat[k] = (heat[k - 1] + (heat[k - 2]<<1) ) / 3; // heat[k-2] multiplied by 2 - } - - // Step 3. Randomly ignite new 'sparks' of heat near the bottom - if (random8() <= SEGMENT.intensity) { - uint8_t y = random8(ignition); - if (y < SEGLEN) heat[y] = qadd8(heat[y], random8(160,255)); - } - SEGENV.step = it; - } + uint32_t it = strip.now >> 5; //div 32 + + struct virtualStrip { + static void runStrip(uint16_t stripNr, byte* heat, uint32_t it) { + + if (it != SEGENV.step) + { + uint8_t ignition = max(3,SEGLEN/10); // ignition area: 10% of segment length or minimum 3 pixels + + // Step 1. Cool down every cell a little + for (int i = 0; i < SEGLEN; i++) { + uint8_t cool = random8((((20 + SEGMENT.speed/3) * 16) / SEGLEN)+2); + uint8_t minTemp = 0; + if (i 1; k--) { + heat[k] = (heat[k - 1] + (heat[k - 2]<<1) ) / 3; // heat[k-2] multiplied by 2 + } + + // Step 3. Randomly ignite new 'sparks' of heat near the bottom + if (random8() <= SEGMENT.intensity) { + uint8_t y = random8(ignition); + heat[y] = qadd8(heat[y], random8(160,255)); + } + } + + // Step 4. Map from heat cells to LED colors + for (int j = 0; j < SEGLEN; j++) { + SEGMENT.setPixelColor(indexToVStrip(j, stripNr), ColorFromPalette(SEGPALETTE, MIN(heat[j],240), 255, NOBLEND)); + } + } + }; + + for (int stripNr=0; stripNr> 8; uint16_t h16_128 = hue16 >> 7; @@ -1926,149 +2060,152 @@ uint16_t WS2812FX::mode_colorwaves() uint8_t bri8 = (uint32_t)(((uint32_t)bri16) * brightdepth) / 65536; bri8 += (255 - brightdepth); - CRGB newcolor = ColorFromPalette(currentPalette, hue8, bri8); - fastled_col = col_to_crgb(getPixelColor(i)); + //CRGB newcolor = ColorFromPalette(SEGPALETTE, hue8, bri8); + //fastled_col = SEGMENT.getPixelColor(i); // TODO - nblend(fastled_col, newcolor, 128); - setPixelColor(i, fastled_col.red, fastled_col.green, fastled_col.blue); + //nblend(fastled_col, newcolor, 128); + //SEGMENT.setPixelColor(i, fastled_col.red, fastled_col.green, fastled_col.blue); + SEGMENT.blendPixelColor(i, SEGMENT.color_from_palette(hue8, false, PALETTE_SOLID_WRAP, 0, bri8), 128); // 50/50 mix } SEGENV.step = sPseudotime; SEGENV.aux0 = sHue16; return FRAMETIME; } +static const char _data_FX_MODE_COLORWAVES[] PROGMEM = "Colorwaves@!,!;!,!,!;!;1d"; // colored stripes pulsing at a defined Beats-Per-Minute (BPM) -uint16_t WS2812FX::mode_bpm() +uint16_t mode_bpm() { - CRGB fastled_col; - uint32_t stp = (now / 20) & 0xFF; + //CRGB fastled_col; + uint32_t stp = (strip.now / 20) & 0xFF; uint8_t beat = beatsin8(SEGMENT.speed, 64, 255); - for (uint16_t i = 0; i < SEGLEN; i++) { - fastled_col = ColorFromPalette(currentPalette, stp + (i * 2), beat - stp + (i * 10)); - setPixelColor(i, fastled_col.red, fastled_col.green, fastled_col.blue); + for (int i = 0; i < SEGLEN; i++) { + //fastled_col = ColorFromPalette(SEGPALETTE, stp + (i * 2), beat - stp + (i * 10)); + //SEGMENT.setPixelColor(i, fastled_col.red, fastled_col.green, fastled_col.blue); + SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(stp + (i * 2), false, PALETTE_SOLID_WRAP, 0, beat - stp + (i * 10))); } return FRAMETIME; } +static const char _data_FX_MODE_BPM[] PROGMEM = "Bpm@!,;1,2,3;!;sx=64,1d"; -uint16_t WS2812FX::mode_fillnoise8() +uint16_t mode_fillnoise8() { if (SEGENV.call == 0) SEGENV.step = random16(12345); - CRGB fastled_col; - for (uint16_t i = 0; i < SEGLEN; i++) { + //CRGB fastled_col; + for (int i = 0; i < SEGLEN; i++) { uint8_t index = inoise8(i * SEGLEN, SEGENV.step + i * SEGLEN); - fastled_col = ColorFromPalette(currentPalette, index, 255, LINEARBLEND); - setPixelColor(i, fastled_col.red, fastled_col.green, fastled_col.blue); + //fastled_col = ColorFromPalette(SEGPALETTE, index, 255, LINEARBLEND); + //SEGMENT.setPixelColor(i, fastled_col.red, fastled_col.green, fastled_col.blue); + SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(index, false, PALETTE_SOLID_WRAP, 0)); } SEGENV.step += beatsin8(SEGMENT.speed, 1, 6); //10,1,4 return FRAMETIME; } +static const char _data_FX_MODE_FILLNOISE8[] PROGMEM = "Fill Noise@!,!;!,!,!;!;1d"; -uint16_t WS2812FX::mode_noise16_1() + +uint16_t mode_noise16_1() { - uint16_t scale = 320; // the "zoom factor" for the noise - CRGB fastled_col; + uint16_t scale = 320; // the "zoom factor" for the noise + //CRGB fastled_col; SEGENV.step += (1 + SEGMENT.speed/16); - for (uint16_t i = 0; i < SEGLEN; i++) { - - uint16_t shift_x = beatsin8(11); // the x position of the noise field swings @ 17 bpm - uint16_t shift_y = SEGENV.step/42; // the y position becomes slowly incremented - - + for (int i = 0; i < SEGLEN; i++) { + uint16_t shift_x = beatsin8(11); // the x position of the noise field swings @ 17 bpm + uint16_t shift_y = SEGENV.step/42; // the y position becomes slowly incremented uint16_t real_x = (i + shift_x) * scale; // the x position of the noise field swings @ 17 bpm uint16_t real_y = (i + shift_y) * scale; // the y position becomes slowly incremented - uint32_t real_z = SEGENV.step; // the z position becomes quickly incremented + uint32_t real_z = SEGENV.step; // the z position becomes quickly incremented + uint8_t noise = inoise16(real_x, real_y, real_z) >> 8; // get the noise data and scale it down + uint8_t index = sin8(noise * 3); // map LED color based on noise data - uint8_t noise = inoise16(real_x, real_y, real_z) >> 8; // get the noise data and scale it down - - uint8_t index = sin8(noise * 3); // map LED color based on noise data - - fastled_col = ColorFromPalette(currentPalette, index, 255, LINEARBLEND); // With that value, look up the 8 bit colour palette value and assign it to the current LED. - setPixelColor(i, fastled_col.red, fastled_col.green, fastled_col.blue); + //fastled_col = ColorFromPalette(SEGPALETTE, index, 255, LINEARBLEND); // With that value, look up the 8 bit colour palette value and assign it to the current LED. + //SEGMENT.setPixelColor(i, fastled_col.red, fastled_col.green, fastled_col.blue); + SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(index, false, PALETTE_SOLID_WRAP, 0)); } return FRAMETIME; } +static const char _data_FX_MODE_NOISE16_1[] PROGMEM = "Noise 1@!,!;!,!,!;!;1d"; -uint16_t WS2812FX::mode_noise16_2() +uint16_t mode_noise16_2() { - uint16_t scale = 1000; // the "zoom factor" for the noise - CRGB fastled_col; + uint16_t scale = 1000; // the "zoom factor" for the noise + //CRGB fastled_col; SEGENV.step += (1 + (SEGMENT.speed >> 1)); - for (uint16_t i = 0; i < SEGLEN; i++) { + for (int i = 0; i < SEGLEN; i++) { + uint16_t shift_x = SEGENV.step >> 6; // x as a function of time + uint32_t real_x = (i + shift_x) * scale; // calculate the coordinates within the noise field + uint8_t noise = inoise16(real_x, 0, 4223) >> 8; // get the noise data and scale it down + uint8_t index = sin8(noise * 3); // map led color based on noise data - uint16_t shift_x = SEGENV.step >> 6; // x as a function of time - - uint32_t real_x = (i + shift_x) * scale; // calculate the coordinates within the noise field - - uint8_t noise = inoise16(real_x, 0, 4223) >> 8; // get the noise data and scale it down - - uint8_t index = sin8(noise * 3); // map led color based on noise data - - fastled_col = ColorFromPalette(currentPalette, index, noise, LINEARBLEND); // With that value, look up the 8 bit colour palette value and assign it to the current LED. - setPixelColor(i, fastled_col.red, fastled_col.green, fastled_col.blue); + //fastled_col = ColorFromPalette(SEGPALETTE, index, noise, LINEARBLEND); // With that value, look up the 8 bit colour palette value and assign it to the current LED. + //SEGMENT.setPixelColor(i, fastled_col.red, fastled_col.green, fastled_col.blue); + SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(index, false, PALETTE_SOLID_WRAP, 0, noise)); } return FRAMETIME; } +static const char _data_FX_MODE_NOISE16_2[] PROGMEM = "Noise 2@!,!;!,!,!;!;1d"; -uint16_t WS2812FX::mode_noise16_3() +uint16_t mode_noise16_3() { uint16_t scale = 800; // the "zoom factor" for the noise - CRGB fastled_col; + //CRGB fastled_col; SEGENV.step += (1 + SEGMENT.speed); - for (uint16_t i = 0; i < SEGLEN; i++) { - + for (int i = 0; i < SEGLEN; i++) { uint16_t shift_x = 4223; // no movement along x and y uint16_t shift_y = 1234; - uint32_t real_x = (i + shift_x) * scale; // calculate the coordinates within the noise field uint32_t real_y = (i + shift_y) * scale; // based on the precalculated positions uint32_t real_z = SEGENV.step*8; - uint8_t noise = inoise16(real_x, real_y, real_z) >> 8; // get the noise data and scale it down - uint8_t index = sin8(noise * 3); // map led color based on noise data - fastled_col = ColorFromPalette(currentPalette, index, noise, LINEARBLEND); // With that value, look up the 8 bit colour palette value and assign it to the current LED. - setPixelColor(i, fastled_col.red, fastled_col.green, fastled_col.blue); + //fastled_col = ColorFromPalette(SEGPALETTE, index, noise, LINEARBLEND); // With that value, look up the 8 bit colour palette value and assign it to the current LED. + //SEGMENT.setPixelColor(i, fastled_col.red, fastled_col.green, fastled_col.blue); + SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(index, false, PALETTE_SOLID_WRAP, 0, noise)); } return FRAMETIME; } +static const char _data_FX_MODE_NOISE16_3[] PROGMEM = "Noise 3@!,!;!,!,!;!;1d"; //https://github.com/aykevl/ledstrip-spark/blob/master/ledstrip.ino -uint16_t WS2812FX::mode_noise16_4() +uint16_t mode_noise16_4() { - CRGB fastled_col; - uint32_t stp = (now * SEGMENT.speed) >> 7; - for (uint16_t i = 0; i < SEGLEN; i++) { + //CRGB fastled_col; + uint32_t stp = (strip.now * SEGMENT.speed) >> 7; + for (int i = 0; i < SEGLEN; i++) { int16_t index = inoise16(uint32_t(i) << 12, stp); - fastled_col = ColorFromPalette(currentPalette, index); - setPixelColor(i, fastled_col.red, fastled_col.green, fastled_col.blue); + //fastled_col = ColorFromPalette(SEGPALETTE, index); + //SEGMENT.setPixelColor(i, fastled_col.red, fastled_col.green, fastled_col.blue); + SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(index, false, PALETTE_SOLID_WRAP, 0)); } return FRAMETIME; } +static const char _data_FX_MODE_NOISE16_4[] PROGMEM = "Noise 4@!,!;!,!,!;!;1d"; //based on https://gist.github.com/kriegsman/5408ecd397744ba0393e -uint16_t WS2812FX::mode_colortwinkle() +uint16_t mode_colortwinkle() { uint16_t dataSize = (SEGLEN+7) >> 3; //1 bit per LED if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed CRGB fastled_col, prev; - fract8 fadeUpAmount = _brightness>28 ? 8 + (SEGMENT.speed>>2) : 68-_brightness, fadeDownAmount = _brightness>28 ? 8 + (SEGMENT.speed>>3) : 68-_brightness; + fract8 fadeUpAmount = strip.getBrightness()>28 ? 8 + (SEGMENT.speed>>2) : 68-strip.getBrightness(); + fract8 fadeDownAmount = strip.getBrightness()>28 ? 8 + (SEGMENT.speed>>3) : 68-strip.getBrightness(); for (uint16_t i = 0; i < SEGLEN; i++) { - fastled_col = col_to_crgb(getPixelColor(i)); + fastled_col = SEGMENT.getPixelColor(i); prev = fastled_col; uint16_t index = i >> 3; uint8_t bitNum = i & 0x07; @@ -2082,15 +2219,15 @@ uint16_t WS2812FX::mode_colortwinkle() if (fastled_col.red == 255 || fastled_col.green == 255 || fastled_col.blue == 255) { bitWrite(SEGENV.data[index], bitNum, false); } - setPixelColor(i, fastled_col.red, fastled_col.green, fastled_col.blue); + SEGMENT.setPixelColor(i, fastled_col.red, fastled_col.green, fastled_col.blue); - if (col_to_crgb(getPixelColor(i)) == prev) { //fix "stuck" pixels + if (SEGMENT.getPixelColor(i) == RGBW32(prev.r, prev.g, prev.b, 0)) { //fix "stuck" pixels fastled_col += fastled_col; - setPixelColor(i, fastled_col.red, fastled_col.green, fastled_col.blue); + SEGMENT.setPixelColor(i, fastled_col); } } else { fastled_col.nscale8(255 - fadeDownAmount); - setPixelColor(i, fastled_col.red, fastled_col.green, fastled_col.blue); + SEGMENT.setPixelColor(i, fastled_col); } } @@ -2098,12 +2235,12 @@ uint16_t WS2812FX::mode_colortwinkle() if (random8() <= SEGMENT.intensity) { for (uint8_t times = 0; times < 5; times++) { //attempt to spawn a new pixel 5 times int i = random16(SEGLEN); - if (getPixelColor(i) == 0) { - fastled_col = ColorFromPalette(currentPalette, random8(), 64, NOBLEND); + if (SEGMENT.getPixelColor(i) == 0) { + fastled_col = ColorFromPalette(SEGPALETTE, random8(), 64, NOBLEND); uint16_t index = i >> 3; uint8_t bitNum = i & 0x07; bitWrite(SEGENV.data[index], bitNum, true); - setPixelColor(i, fastled_col.red, fastled_col.green, fastled_col.blue); + SEGMENT.setPixelColor(i, fastled_col); break; //only spawn 1 new pixel per frame per 50 LEDs } } @@ -2111,68 +2248,71 @@ uint16_t WS2812FX::mode_colortwinkle() } return FRAMETIME_FIXED; } +static const char _data_FX_MODE_COLORTWINKLE[] PROGMEM = "Colortwinkles@Fade speed,Spawn speed;1,2,3;!;mp12=0,1d"; //pixels //Calm effect, like a lake at night -uint16_t WS2812FX::mode_lake() { +uint16_t mode_lake() { uint8_t sp = SEGMENT.speed/10; int wave1 = beatsin8(sp +2, -64,64); int wave2 = beatsin8(sp +1, -64,64); uint8_t wave3 = beatsin8(sp +2, 0,80); - CRGB fastled_col; + //CRGB fastled_col; - for (uint16_t i = 0; i < SEGLEN; i++) + for (int i = 0; i < SEGLEN; i++) { int index = cos8((i*15)+ wave1)/2 + cubicwave8((i*23)+ wave2)/2; uint8_t lum = (index > wave3) ? index - wave3 : 0; - fastled_col = ColorFromPalette(currentPalette, map(index,0,255,0,240), lum, LINEARBLEND); - setPixelColor(i, fastled_col.red, fastled_col.green, fastled_col.blue); + //fastled_col = ColorFromPalette(SEGPALETTE, map(index,0,255,0,240), lum, LINEARBLEND); + //SEGMENT.setPixelColor(i, fastled_col.red, fastled_col.green, fastled_col.blue); + SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(index, false, false, 0, lum)); } return FRAMETIME; } +static const char _data_FX_MODE_LAKE[] PROGMEM = "Lake@!,;1,2,3;!;1d"; // meteor effect // send a meteor from begining to to the end of the strip with a trail that randomly decays. // adapted from https://www.tweaking4all.com/hardware/arduino/adruino-led-strip-effects/#LEDStripEffectMeteorRain -uint16_t WS2812FX::mode_meteor() { +uint16_t mode_meteor() { if (!SEGENV.allocateData(SEGLEN)) return mode_static(); //allocation failed byte* trail = SEGENV.data; byte meteorSize= 1+ SEGLEN / 10; - uint16_t counter = now * ((SEGMENT.speed >> 2) +8); + uint16_t counter = strip.now * ((SEGMENT.speed >> 2) +8); uint16_t in = counter * SEGLEN >> 16; // fade all leds to colors[1] in LEDs one step - for (uint16_t i = 0; i < SEGLEN; i++) { + for (int i = 0; i < SEGLEN; i++) { if (random8() <= 255 - SEGMENT.intensity) { byte meteorTrailDecay = 128 + random8(127); trail[i] = scale8(trail[i], meteorTrailDecay); - setPixelColor(i, color_from_palette(trail[i], false, true, 255)); + SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, false, 0, trail[i])); } } // draw meteor - for(int j = 0; j < meteorSize; j++) { + for (int j = 0; j < meteorSize; j++) { uint16_t index = in + j; - if(index >= SEGLEN) { - index = (in + j - SEGLEN); + if (index >= SEGLEN) { + index -= SEGLEN; } - trail[index] = 240; - setPixelColor(index, color_from_palette(trail[index], false, true, 255)); + SEGMENT.setPixelColor(index, SEGMENT.color_from_palette(index, true, false, 0, 255)); } return FRAMETIME; } +static const char _data_FX_MODE_METEOR[] PROGMEM = "Meteor@!,Trail length;!,,;!;1d"; // smooth meteor effect // send a meteor from begining to to the end of the strip with a trail that randomly decays. // adapted from https://www.tweaking4all.com/hardware/arduino/adruino-led-strip-effects/#LEDStripEffectMeteorRain -uint16_t WS2812FX::mode_meteor_smooth() { +uint16_t mode_meteor_smooth() { if (!SEGENV.allocateData(SEGLEN)) return mode_static(); //allocation failed byte* trail = SEGENV.data; @@ -2181,34 +2321,35 @@ uint16_t WS2812FX::mode_meteor_smooth() { uint16_t in = map((SEGENV.step >> 6 & 0xFF), 0, 255, 0, SEGLEN -1); // fade all leds to colors[1] in LEDs one step - for (uint16_t i = 0; i < SEGLEN; i++) { + for (int i = 0; i < SEGLEN; i++) { if (trail[i] != 0 && random8() <= 255 - SEGMENT.intensity) { int change = 3 - random8(12); //change each time between -8 and +3 trail[i] += change; if (trail[i] > 245) trail[i] = 0; if (trail[i] > 240) trail[i] = 240; - setPixelColor(i, color_from_palette(trail[i], false, true, 255)); + SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, false, 0, trail[i])); } } // draw meteor - for(int j = 0; j < meteorSize; j++) { - uint16_t index = in + j; - if(in + j >= SEGLEN) { - index = (in + j - SEGLEN); + for (int j = 0; j < meteorSize; j++) { + uint16_t index = in + j; + if (index >= SEGLEN) { + index -= SEGLEN; } - setPixelColor(index, color_blend(getPixelColor(index), color_from_palette(240, false, true, 255), 48)); trail[index] = 240; + SEGMENT.setPixelColor(index, SEGMENT.color_from_palette(index, true, false, 0, 255)); } SEGENV.step += SEGMENT.speed +1; return FRAMETIME; } +static const char _data_FX_MODE_METEOR_SMOOTH[] PROGMEM = "Meteor Smooth@!,Trail length;!,,;!;1d"; //Railway Crossing / Christmas Fairy lights -uint16_t WS2812FX::mode_railway() +uint16_t mode_railway() { uint16_t dur = 40 + (255 - SEGMENT.speed) * 10; uint16_t rampdur = (dur * SEGMENT.intensity) >> 8; @@ -2225,17 +2366,18 @@ uint16_t WS2812FX::mode_railway() if (p0 < 255) pos = p0; } if (SEGENV.aux0) pos = 255 - pos; - for (uint16_t i = 0; i < SEGLEN; i += 2) + for (int i = 0; i < SEGLEN; i += 2) { - setPixelColor(i, color_from_palette(255 - pos, false, false, 255)); + SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(255 - pos, false, false, 255)); if (i < SEGLEN -1) { - setPixelColor(i + 1, color_from_palette(pos, false, false, 255)); + SEGMENT.setPixelColor(i + 1, SEGMENT.color_from_palette(pos, false, false, 255)); } } SEGENV.step += FRAMETIME; return FRAMETIME; } +static const char _data_FX_MODE_RAILWAY[] PROGMEM = "Railway"; //Water ripple @@ -2254,7 +2396,7 @@ typedef struct Ripple { #else #define MAX_RIPPLES 100 #endif -uint16_t WS2812FX::ripple_base(bool rainbow) +uint16_t ripple_base(bool rainbow) { uint16_t maxRipples = min(1 + (SEGLEN >> 2), MAX_RIPPLES); // 56 max for 16 segment ESP8266 uint16_t dataSize = sizeof(ripple) * maxRipples; @@ -2277,20 +2419,20 @@ uint16_t WS2812FX::ripple_base(bool rainbow) } else { SEGENV.aux0--; } - fill(color_blend(color_wheel(SEGENV.aux0),BLACK,235)); + SEGMENT.fill(color_blend(SEGMENT.color_wheel(SEGENV.aux0),BLACK,235)); } else { - fill(SEGCOLOR(1)); + SEGMENT.fill(SEGCOLOR(1)); } //draw wave - for (uint16_t i = 0; i < maxRipples; i++) + for (int i = 0; i < maxRipples; i++) { uint16_t ripplestate = ripples[i].state; if (ripplestate) { uint8_t rippledecay = (SEGMENT.speed >> 4) +1; //faster decay if faster propagation uint16_t rippleorigin = ripples[i].pos; - uint32_t col = color_from_palette(ripples[i].color, false, false, 255); + uint32_t col = SEGMENT.color_from_palette(ripples[i].color, false, false, 255); uint16_t propagation = ((ripplestate/rippledecay -1) * SEGMENT.speed); int16_t propI = propagation >> 8; uint8_t propF = propagation & 0xFF; @@ -2302,12 +2444,12 @@ uint16_t WS2812FX::ripple_base(bool rainbow) uint8_t mag = scale8(cubicwave8((propF>>2)+(v-left)*64), amp); if (v < SEGLEN && v >= 0) { - setPixelColor(v, color_blend(getPixelColor(v), col, mag)); + SEGMENT.setPixelColor(v, color_blend(SEGMENT.getPixelColor(v), col, mag)); // TODO } int16_t w = left + propI*2 + 3 -(v-left); if (w < SEGLEN && w >= 0) { - setPixelColor(w, color_blend(getPixelColor(w), col, mag)); + SEGMENT.setPixelColor(w, color_blend(SEGMENT.getPixelColor(w), col, mag)); // TODO } } ripplestate += rippledecay; @@ -2326,14 +2468,17 @@ uint16_t WS2812FX::ripple_base(bool rainbow) } #undef MAX_RIPPLES -uint16_t WS2812FX::mode_ripple(void) { + +uint16_t mode_ripple(void) { return ripple_base(false); } +static const char _data_FX_MODE_RIPPLE[] PROGMEM = "Ripple"; -uint16_t WS2812FX::mode_ripple_rainbow(void) { + +uint16_t mode_ripple_rainbow(void) { return ripple_base(true); } - +static const char _data_FX_MODE_RIPPLE_RAINBOW[] PROGMEM = "Ripple Rainbow"; // TwinkleFOX by Mark Kriegsman: https://gist.github.com/kriegsman/756ea6dcae8e30845b5a @@ -2346,7 +2491,7 @@ uint16_t WS2812FX::mode_ripple_rainbow(void) { // incandescent bulbs change color as they get dim down. #define COOL_LIKE_INCANDESCENT 1 -CRGB IRAM_ATTR WS2812FX::twinklefox_one_twinkle(uint32_t ms, uint8_t salt, bool cat) +CRGB twinklefox_one_twinkle(uint32_t ms, uint8_t salt, bool cat) { // Overall twinkle speed (changed) uint16_t ticks = ms / SEGENV.aux0; @@ -2383,7 +2528,7 @@ CRGB IRAM_ATTR WS2812FX::twinklefox_one_twinkle(uint32_t ms, uint8_t salt, bool uint8_t hue = slowcycle8 - salt; CRGB c; if (bright > 0) { - c = ColorFromPalette(currentPalette, hue, bright, NOBLEND); + c = ColorFromPalette(SEGPALETTE, hue, bright, NOBLEND); if(COOL_LIKE_INCANDESCENT == 1) { // This code takes a pixel, and if its in the 'fading down' // part of the cycle, it adjusts the color a little bit like the @@ -2406,7 +2551,7 @@ CRGB IRAM_ATTR WS2812FX::twinklefox_one_twinkle(uint32_t ms, uint8_t salt, bool // "CalculateOneTwinkle" on each pixel. It then displays // either the twinkle color of the background color, // whichever is brighter. -uint16_t WS2812FX::twinklefox_base(bool cat) +uint16_t twinklefox_base(bool cat) { // "PRNG16" is the pseudorandom number generator // It MUST be reset to the same starting value each time @@ -2419,8 +2564,7 @@ uint16_t WS2812FX::twinklefox_base(bool cat) else SEGENV.aux0 = 22 + ((100 - SEGMENT.speed) >> 1); // Set up the background color, "bg". - CRGB bg; - bg = col_to_crgb(SEGCOLOR(1)); + CRGB bg = CRGB(SEGCOLOR(1)); uint8_t bglight = bg.getAverageLight(); if (bglight > 64) { bg.nscale8_video(16); // very bright, so scale to 1/16th @@ -2432,14 +2576,14 @@ uint16_t WS2812FX::twinklefox_base(bool cat) uint8_t backgroundBrightness = bg.getAverageLight(); - for (uint16_t i = 0; i < SEGLEN; i++) { + for (int i = 0; i < SEGLEN; i++) { PRNG16 = (uint16_t)(PRNG16 * 2053) + 1384; // next 'random' number uint16_t myclockoffset16= PRNG16; // use that number as clock offset PRNG16 = (uint16_t)(PRNG16 * 2053) + 1384; // next 'random' number // use that number as clock speed adjustment factor (in 8ths, from 8/8ths to 23/8ths) uint8_t myspeedmultiplierQ5_3 = ((((PRNG16 & 0xFF)>>4) + (PRNG16 & 0x0F)) & 0x0F) + 0x08; - uint32_t myclock30 = (uint32_t)((now * myspeedmultiplierQ5_3) >> 3) + myclockoffset16; + uint32_t myclock30 = (uint32_t)((strip.now * myspeedmultiplierQ5_3) >> 3) + myclockoffset16; uint8_t myunique8 = PRNG16 >> 8; // get 'salt' value for this pixel // We now have the adjusted 'clock' for this pixel, now we call @@ -2452,49 +2596,54 @@ uint16_t WS2812FX::twinklefox_base(bool cat) if (deltabright >= 32 || (!bg)) { // If the new pixel is significantly brighter than the background color, // use the new color. - setPixelColor(i, c.red, c.green, c.blue); + SEGMENT.setPixelColor(i, c.red, c.green, c.blue); } else if (deltabright > 0) { // If the new pixel is just slightly brighter than the background color, // mix a blend of the new color and the background color - setPixelColor(i, color_blend(crgb_to_col(bg), crgb_to_col(c), deltabright * 8)); + SEGMENT.setPixelColor(i, color_blend(RGBW32(bg.r,bg.g,bg.b,0), RGBW32(c.r,c.g,c.b,0), deltabright * 8)); } else { // if the new pixel is not at all brighter than the background color, // just use the background color. - setPixelColor(i, bg.r, bg.g, bg.b); + SEGMENT.setPixelColor(i, bg.r, bg.g, bg.b); } } return FRAMETIME; } -uint16_t WS2812FX::mode_twinklefox() + +uint16_t mode_twinklefox() { return twinklefox_base(false); } +static const char _data_FX_MODE_TWINKLEFOX[] PROGMEM = "Twinklefox"; -uint16_t WS2812FX::mode_twinklecat() + +uint16_t mode_twinklecat() { return twinklefox_base(true); } +static const char _data_FX_MODE_TWINKLECAT[] PROGMEM = "Twinklecat"; //inspired by https://www.tweaking4all.com/hardware/arduino/adruino-led-strip-effects/#LEDStripEffectBlinkingHalloweenEyes -#define HALLOWEEN_EYE_SPACE 3 -#define HALLOWEEN_EYE_WIDTH 1 - -uint16_t WS2812FX::mode_halloween_eyes() -{ +uint16_t mode_halloween_eyes() +{ + const uint16_t maxWidth = strip.isMatrix ? SEGMENT.virtualWidth() : SEGLEN; + const uint16_t HALLOWEEN_EYE_SPACE = MAX(2, strip.isMatrix ? SEGMENT.virtualWidth()>>4: SEGLEN>>5); + const uint16_t HALLOWEEN_EYE_WIDTH = HALLOWEEN_EYE_SPACE/2; uint16_t eyeLength = (2*HALLOWEEN_EYE_WIDTH) + HALLOWEEN_EYE_SPACE; - if (eyeLength > SEGLEN) return mode_static(); //bail if segment too short + if (eyeLength >= maxWidth) return mode_static(); //bail if segment too short - fill(SEGCOLOR(1)); //fill background + SEGMENT.fill(SEGCOLOR(1)); //fill background uint8_t state = SEGENV.aux1 >> 8; uint16_t stateTime = SEGENV.call; if (stateTime == 0) stateTime = 2000; if (state == 0) { //spawn eyes - SEGENV.aux0 = random16(0, SEGLEN - eyeLength); //start pos + SEGENV.aux0 = random16(0, maxWidth - eyeLength - 1); //start pos SEGENV.aux1 = random8(); //color + if (strip.isMatrix) SEGMENT.offset = random16(SEGMENT.virtualHeight()-1); // a hack: reuse offset since it is not used in matrices state = 1; } @@ -2502,30 +2651,32 @@ uint16_t WS2812FX::mode_halloween_eyes() uint16_t startPos = SEGENV.aux0; uint16_t start2ndEye = startPos + HALLOWEEN_EYE_WIDTH + HALLOWEEN_EYE_SPACE; - uint32_t fadestage = (now - SEGENV.step)*255 / stateTime; + uint32_t fadestage = (strip.now - SEGENV.step)*255 / stateTime; if (fadestage > 255) fadestage = 255; - uint32_t c = color_blend(color_from_palette(SEGENV.aux1 & 0xFF, false, false, 0), SEGCOLOR(1), fadestage); + uint32_t c = color_blend(SEGMENT.color_from_palette(SEGENV.aux1 & 0xFF, false, false, 0), SEGCOLOR(1), fadestage); - for (uint16_t i = 0; i < HALLOWEEN_EYE_WIDTH; i++) - { - setPixelColor(startPos + i, c); - setPixelColor(start2ndEye + i, c); + for (int i = 0; i < HALLOWEEN_EYE_WIDTH; i++) { + if (strip.isMatrix) { + SEGMENT.setPixelColorXY(startPos + i, SEGMENT.offset, c); + SEGMENT.setPixelColorXY(start2ndEye + i, SEGMENT.offset, c); + } else { + SEGMENT.setPixelColor(startPos + i, c); + SEGMENT.setPixelColor(start2ndEye + i, c); + } } } - if (now - SEGENV.step > stateTime) - { + if (strip.now - SEGENV.step > stateTime) { state++; if (state > 2) state = 0; - if (state < 2) - { - stateTime = 100 + (255 - SEGMENT.intensity)*10; //eye fade time + if (state < 2) { + stateTime = 100 + SEGMENT.intensity*10; //eye fade time } else { - uint16_t eyeOffTimeBase = (255 - SEGMENT.speed)*10; + uint16_t eyeOffTimeBase = (256 - SEGMENT.speed)*10; stateTime = eyeOffTimeBase + random16(eyeOffTimeBase); } - SEGENV.step = now; + SEGENV.step = strip.now; SEGENV.call = stateTime; } @@ -2533,18 +2684,19 @@ uint16_t WS2812FX::mode_halloween_eyes() return FRAMETIME; } +static const char _data_FX_MODE_HALLOWEEN_EYES[] PROGMEM = "Halloween Eyes@Duration,Eye fade time;!,!,;!;1d,2d"; //Speed slider sets amount of LEDs lit, intensity sets unlit -uint16_t WS2812FX::mode_static_pattern() +uint16_t mode_static_pattern() { uint16_t lit = 1 + SEGMENT.speed; uint16_t unlit = 1 + SEGMENT.intensity; bool drawingLit = true; uint16_t cnt = 0; - for (uint16_t i = 0; i < SEGLEN; i++) { - setPixelColor(i, (drawingLit) ? color_from_palette(i, true, PALETTE_SOLID_WRAP, 0) : SEGCOLOR(1)); + for (int i = 0; i < SEGLEN; i++) { + SEGMENT.setPixelColor(i, (drawingLit) ? SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0) : SEGCOLOR(1)); cnt++; if (cnt >= ((drawingLit) ? lit : unlit)) { cnt = 0; @@ -2554,20 +2706,22 @@ uint16_t WS2812FX::mode_static_pattern() return FRAMETIME; } +static const char _data_FX_MODE_STATIC_PATTERN[] PROGMEM = "Solid Pattern@Fg size,Bg size;Fg,Bg,;!;pal=0,1d"; -uint16_t WS2812FX::mode_tri_static_pattern() + +uint16_t mode_tri_static_pattern() { uint8_t segSize = (SEGMENT.intensity >> 5) +1; uint8_t currSeg = 0; uint16_t currSegCount = 0; - for (uint16_t i = 0; i < SEGLEN; i++) { + for (int i = 0; i < SEGLEN; i++) { if ( currSeg % 3 == 0 ) { - setPixelColor(i, SEGCOLOR(0)); + SEGMENT.setPixelColor(i, SEGCOLOR(0)); } else if( currSeg % 3 == 1) { - setPixelColor(i, SEGCOLOR(1)); + SEGMENT.setPixelColor(i, SEGCOLOR(1)); } else { - setPixelColor(i, (SEGCOLOR(2) > 0 ? SEGCOLOR(2) : WHITE)); + SEGMENT.setPixelColor(i, (SEGCOLOR(2) > 0 ? SEGCOLOR(2) : WHITE)); } currSegCount += 1; if (currSegCount >= segSize) { @@ -2578,27 +2732,28 @@ uint16_t WS2812FX::mode_tri_static_pattern() return FRAMETIME; } +static const char _data_FX_MODE_TRI_STATIC_PATTERN[] PROGMEM = "Solid Pattern Tri@,Size;1,2,3;!;1d,pal=0"; -uint16_t WS2812FX::spots_base(uint16_t threshold) +uint16_t spots_base(uint16_t threshold) { - fill(SEGCOLOR(1)); + SEGMENT.fill(SEGCOLOR(1)); uint16_t maxZones = SEGLEN >> 2; uint16_t zones = 1 + ((SEGMENT.intensity * maxZones) >> 8); uint16_t zoneLen = SEGLEN / zones; uint16_t offset = (SEGLEN - zones * zoneLen) >> 1; - for (uint16_t z = 0; z < zones; z++) + for (int z = 0; z < zones; z++) { uint16_t pos = offset + z * zoneLen; - for (uint16_t i = 0; i < zoneLen; i++) + for (int i = 0; i < zoneLen; i++) { uint16_t wave = triwave16((i * 0xFFFF) / zoneLen); if (wave > threshold) { uint16_t index = 0 + pos + i; uint8_t s = (wave - threshold)*255 / (0xFFFF - threshold); - setPixelColor(index, color_blend(color_from_palette(index, true, PALETTE_SOLID_WRAP, 0), SEGCOLOR(1), 255-s)); + SEGMENT.setPixelColor(index, color_blend(SEGMENT.color_from_palette(index, true, PALETTE_SOLID_WRAP, 0), SEGCOLOR(1), 255-s)); } } } @@ -2608,20 +2763,22 @@ uint16_t WS2812FX::spots_base(uint16_t threshold) //Intensity slider sets number of "lights", speed sets LEDs per light -uint16_t WS2812FX::mode_spots() +uint16_t mode_spots() { return spots_base((255 - SEGMENT.speed) << 8); } +static const char _data_FX_MODE_SPOTS[] PROGMEM = "Spots@Spread,Width;!,!,;!;1d"; //Intensity slider sets number of "lights", LEDs per light fade in and out -uint16_t WS2812FX::mode_spots_fade() +uint16_t mode_spots_fade() { - uint16_t counter = now * ((SEGMENT.speed >> 2) +8); + uint16_t counter = strip.now * ((SEGMENT.speed >> 2) +8); uint16_t t = triwave16(counter); uint16_t tr = (t >> 1) + (t >> 2); return spots_base(tr); } +static const char _data_FX_MODE_SPOTS_FADE[] PROGMEM = "Spots Fade@Spread,Width;!,!,;!;1d"; //each needs 12 bytes @@ -2634,89 +2791,103 @@ typedef struct Ball { /* * Bouncing Balls Effect */ -uint16_t WS2812FX::mode_bouncing_balls(void) { +uint16_t mode_bouncing_balls(void) { //allocate segment data - uint16_t maxNumBalls = 16; + const uint16_t strips = SEGMENT.nrOfVStrips(); // adapt for 2D + const size_t maxNumBalls = 16; uint16_t dataSize = sizeof(ball) * maxNumBalls; - if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed + if (!SEGENV.allocateData(dataSize * strips)) return mode_static(); //allocation failed Ball* balls = reinterpret_cast(SEGENV.data); - - // number of balls based on intensity setting to max of 7 (cycles colors) - // non-chosen color is a random color - uint8_t numBalls = int(((SEGMENT.intensity * (maxNumBalls - 0.8f)) / 255) + 1); - - float gravity = -9.81; // standard value of gravity - float impactVelocityStart = sqrt( -2 * gravity); - unsigned long time = millis(); + SEGMENT.fill(SEGCOLOR(2) ? BLACK : SEGCOLOR(1)); - if (SEGENV.call == 0) { - for (uint8_t i = 0; i < maxNumBalls; i++) balls[i].lastBounceTime = time; - } - - bool hasCol2 = SEGCOLOR(2); - fill(hasCol2 ? BLACK : SEGCOLOR(1)); - - for (uint8_t i = 0; i < numBalls; i++) { - float timeSinceLastBounce = (time - balls[i].lastBounceTime)/((255-SEGMENT.speed)*8/256 +1); - balls[i].height = 0.5 * gravity * pow(timeSinceLastBounce/1000 , 2.0) + balls[i].impactVelocity * timeSinceLastBounce/1000; + // virtualStrip idea by @ewowi (Ewoud Wijma) + // requires virtual strip # to be embedded into upper 16 bits of index in setPixelColor() + // the following functions will not work on virtual strips: fill(), fade_out(), fadeToBlack(), blur() + struct virtualStrip { + static void runStrip(size_t stripNr, Ball* balls) { + // number of balls based on intensity setting to max of 7 (cycles colors) + // non-chosen color is a random color + uint16_t numBalls = (SEGMENT.intensity * (maxNumBalls - 1)) / 255 + 1; // minimum 1 ball + const float gravity = -9.81; // standard value of gravity + const bool hasCol2 = SEGCOLOR(2); + const unsigned long time = millis(); - if (balls[i].height < 0) { //start bounce - balls[i].height = 0; - //damping for better effect using multiple balls - float dampening = 0.90 - float(i)/pow(numBalls,2); - balls[i].impactVelocity = dampening * balls[i].impactVelocity; - balls[i].lastBounceTime = time; + if (SEGENV.call == 0) { + for (size_t i = 0; i < maxNumBalls; i++) balls[i].lastBounceTime = time; + } + + for (size_t i = 0; i < numBalls; i++) { + float timeSinceLastBounce = (time - balls[i].lastBounceTime)/((255-SEGMENT.speed)/64 +1); + float timeSec = timeSinceLastBounce/1000.0f; + balls[i].height = (0.5f * gravity * timeSec + balls[i].impactVelocity) * timeSec; // avoid use pow(x, 2) - its extremely slow ! - if (balls[i].impactVelocity < 0.015) { - balls[i].impactVelocity = impactVelocityStart; + if (balls[i].height <= 0.0f) { + balls[i].height = 0.0f; + //damping for better effect using multiple balls + float dampening = 0.9f - float(i)/float(numBalls * numBalls); // avoid use pow(x, 2) - its extremely slow ! + balls[i].impactVelocity = dampening * balls[i].impactVelocity; + balls[i].lastBounceTime = time; + + if (balls[i].impactVelocity < 0.015f) { + float impactVelocityStart = sqrt(-2 * gravity) * random8(5,11)/10.0f; // randomize impact velocity + balls[i].impactVelocity = impactVelocityStart; + } + } else if (balls[i].height > 1.0f) { + continue; // do not draw OOB ball + } + + uint32_t color = SEGCOLOR(0); + if (SEGMENT.palette) { + color = SEGMENT.color_wheel(i*(256/MAX(numBalls, 8))); + } else if (hasCol2) { + color = SEGCOLOR(i % NUM_COLORS); + } + + int pos = roundf(balls[i].height * (SEGLEN - 1)); + if (SEGLEN<32) SEGMENT.setPixelColor(indexToVStrip(pos, stripNr), color); // encode virtual strip into index + else SEGMENT.setPixelColor(balls[i].height + (stripNr+1)*10.0f, color); } } - - uint32_t color = SEGCOLOR(0); - if (SEGMENT.palette) { - color = color_wheel(i*(256/MAX(numBalls, 8))); - } else if (hasCol2) { - color = SEGCOLOR(i % NUM_COLORS); - } + }; - uint16_t pos = round(balls[i].height * (SEGLEN - 1)); - setPixelColor(pos, color); - } + for (int stripNr=0; stripNr pos ; i--) { - setPixelColor(i, color1); - if (dual) setPixelColor(SEGLEN-1-i, color2); + for (int i = SEGENV.aux0; i > pos ; i--) { + SEGMENT.setPixelColor(i, color1); + if (dual) SEGMENT.setPixelColor(SEGLEN-1-i, color2); } } SEGENV.aux0 = pos; @@ -2725,104 +2896,118 @@ uint16_t WS2812FX::sinelon_base(bool dual, bool rainbow=false) { return FRAMETIME; } -uint16_t WS2812FX::mode_sinelon(void) { + +uint16_t mode_sinelon(void) { return sinelon_base(false); } +static const char _data_FX_MODE_SINELON[] PROGMEM = "Sinelon"; -uint16_t WS2812FX::mode_sinelon_dual(void) { + +uint16_t mode_sinelon_dual(void) { return sinelon_base(true); } +static const char _data_FX_MODE_SINELON_DUAL[] PROGMEM = "Sinelon Dual"; -uint16_t WS2812FX::mode_sinelon_rainbow(void) { + +uint16_t mode_sinelon_rainbow(void) { return sinelon_base(false, true); } +static const char _data_FX_MODE_SINELON_RAINBOW[] PROGMEM = "Sinelon Rainbow"; //Rainbow with glitter, inspired by https://gist.github.com/kriegsman/062e10f7f07ba8518af6 -uint16_t WS2812FX::mode_glitter() +uint16_t mode_glitter() { mode_palette(); if (SEGMENT.intensity > random8()) { - setPixelColor(random16(SEGLEN), ULTRAWHITE); + SEGMENT.setPixelColor(random16(SEGLEN), ULTRAWHITE); } return FRAMETIME; } +static const char _data_FX_MODE_GLITTER[] PROGMEM = "Glitter@,!;!,!,!;!;mp12=0,1d"; //pixels - -//each needs 12 bytes +//each needs 19 bytes //Spark type is used for popcorn, 1D fireworks, and drip typedef struct Spark { - float pos; - float vel; + float pos, posX; + float vel, velX; uint16_t col; uint8_t colIndex; } spark; +#define maxNumPopcorn 21 // max 21 on 16 segment ESP8266 /* * POPCORN * modified from https://github.com/kitesurfer1404/WS2812FX/blob/master/src/custom/Popcorn.h */ -uint16_t WS2812FX::mode_popcorn(void) { +uint16_t mode_popcorn(void) { //allocate segment data - uint16_t maxNumPopcorn = 21; // max 21 on 16 segment ESP8266 + uint16_t strips = SEGMENT.nrOfVStrips(); uint16_t dataSize = sizeof(spark) * maxNumPopcorn; - if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed - + if (!SEGENV.allocateData(dataSize * strips)) return mode_static(); //allocation failed + Spark* popcorn = reinterpret_cast(SEGENV.data); - float gravity = -0.0001 - (SEGMENT.speed/200000.0); // m/s/s - gravity *= SEGLEN; - bool hasCol2 = SEGCOLOR(2); - fill(hasCol2 ? BLACK : SEGCOLOR(1)); + SEGMENT.fill(hasCol2 ? BLACK : SEGCOLOR(1)); - uint8_t numPopcorn = SEGMENT.intensity*maxNumPopcorn/255; - if (numPopcorn == 0) numPopcorn = 1; + struct virtualStrip { + static void runStrip(uint16_t stripNr, Spark* popcorn) { + float gravity = -0.0001 - (SEGMENT.speed/200000.0); // m/s/s + gravity *= SEGLEN; - for(uint8_t i = 0; i < numPopcorn; i++) { - if (popcorn[i].pos >= 0.0f) { // if kernel is active, update its position - popcorn[i].pos += popcorn[i].vel; - popcorn[i].vel += gravity; - } else { // if kernel is inactive, randomly pop it - if (random8() < 2) { // POP!!! - popcorn[i].pos = 0.01f; - - uint16_t peakHeight = 128 + random8(128); //0-255 - peakHeight = (peakHeight * (SEGLEN -1)) >> 8; - popcorn[i].vel = sqrt(-2.0 * gravity * peakHeight); - - if (SEGMENT.palette) - { - popcorn[i].colIndex = random8(); - } else { - byte col = random8(0, NUM_COLORS); - if (!hasCol2 || !SEGCOLOR(col)) col = 0; - popcorn[i].colIndex = col; + uint8_t numPopcorn = SEGMENT.intensity*maxNumPopcorn/255; + if (numPopcorn == 0) numPopcorn = 1; + + for(int i = 0; i < numPopcorn; i++) { + if (popcorn[i].pos >= 0.0f) { // if kernel is active, update its position + popcorn[i].pos += popcorn[i].vel; + popcorn[i].vel += gravity; + } else { // if kernel is inactive, randomly pop it + if (random8() < 2) { // POP!!! + popcorn[i].pos = 0.01f; + + uint16_t peakHeight = 128 + random8(128); //0-255 + peakHeight = (peakHeight * (SEGLEN -1)) >> 8; + popcorn[i].vel = sqrt(-2.0 * gravity * peakHeight); + + if (SEGMENT.palette) + { + popcorn[i].colIndex = random8(); + } else { + byte col = random8(0, NUM_COLORS); + if (!SEGCOLOR(2) || !SEGCOLOR(col)) col = 0; + popcorn[i].colIndex = col; + } + } + } + if (popcorn[i].pos >= 0.0f) { // draw now active popcorn (either active before or just popped) + uint32_t col = SEGMENT.color_wheel(popcorn[i].colIndex); + if (!SEGMENT.palette && popcorn[i].colIndex < NUM_COLORS) col = SEGCOLOR(popcorn[i].colIndex); + uint16_t ledIndex = popcorn[i].pos; + if (ledIndex < SEGLEN) SEGMENT.setPixelColor(indexToVStrip(ledIndex, stripNr), col); } } } - if (popcorn[i].pos >= 0.0f) { // draw now active popcorn (either active before or just popped) - uint32_t col = color_wheel(popcorn[i].colIndex); - if (!SEGMENT.palette && popcorn[i].colIndex < NUM_COLORS) col = SEGCOLOR(popcorn[i].colIndex); - - uint16_t ledIndex = popcorn[i].pos; - if (ledIndex < SEGLEN) setPixelColor(ledIndex, col); - } - } + }; + + for (int stripNr=0; stripNr 0) { - setPixelColor(i, color_blend(SEGCOLOR(1), color_from_palette(i, true, PALETTE_SOLID_WRAP, 0), s)); + SEGMENT.setPixelColor(i, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0), s)); SEGENV.data[d] = s; SEGENV.data[d+1] = s_target; SEGENV.data[d+2] = fadeStep; } else { - for (uint16_t j = 0; j < SEGLEN; j++) { - setPixelColor(j, color_blend(SEGCOLOR(1), color_from_palette(j, true, PALETTE_SOLID_WRAP, 0), s)); + for (int j = 0; j < SEGLEN; j++) { + SEGMENT.setPixelColor(j, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(j, true, PALETTE_SOLID_WRAP, 0), s)); } SEGENV.aux0 = s; SEGENV.aux1 = s_target; SEGENV.step = fadeStep; @@ -2897,16 +3082,19 @@ uint16_t WS2812FX::candle(bool multi) return FRAMETIME_FIXED; } -uint16_t WS2812FX::mode_candle() + +uint16_t mode_candle() { return candle(false); } +static const char _data_FX_MODE_CANDLE[] PROGMEM = "Candle@Flicker rate,Flicker intensity;!,!,;;sx=96,ix=224,pal=0,1d"; -uint16_t WS2812FX::mode_candle_multi() +uint16_t mode_candle_multi() { return candle(true); } +static const char _data_FX_MODE_CANDLE_MULTI[] PROGMEM = "Candle Multi@Flicker rate,Flicker intensity;!,!,;;sx=96,ix=224,pal=0,1d"; /* @@ -2929,11 +3117,11 @@ typedef struct particle { float fragment[STARBURST_MAX_FRAG]; } star; -uint16_t WS2812FX::mode_starburst(void) { +uint16_t mode_starburst(void) { uint16_t maxData = FAIR_DATA_PER_SEG; //ESP8266: 256 ESP32: 640 - uint8_t segs = getActiveSegmentsNum(); - if (segs <= (MAX_NUM_SEGMENTS /2)) maxData *= 2; //ESP8266: 512 if <= 8 segs ESP32: 1280 if <= 16 segs - if (segs <= (MAX_NUM_SEGMENTS /4)) maxData *= 2; //ESP8266: 1024 if <= 4 segs ESP32: 2560 if <= 8 segs + uint8_t segs = strip.getActiveSegmentsNum(); + if (segs <= (strip.getMaxSegments() /2)) maxData *= 2; //ESP8266: 512 if <= 8 segs ESP32: 1280 if <= 16 segs + if (segs <= (strip.getMaxSegments() /4)) maxData *= 2; //ESP8266: 1024 if <= 4 segs ESP32: 2560 if <= 8 segs uint16_t maxStars = maxData / sizeof(star); //ESP8266: max. 4/9/19 stars/seg, ESP32: max. 10/21/42 stars/seg uint8_t numStars = 1 + (SEGLEN >> 3); @@ -2959,7 +3147,7 @@ uint16_t WS2812FX::mode_starburst(void) { uint16_t startPos = random16(SEGLEN-1); float multiplier = (float)(random8())/255.0 * 1.0; - stars[j].color = col_to_crgb(color_wheel(random8())); + stars[j].color = CRGB(SEGMENT.color_wheel(random8())); stars[j].pos = startPos; stars[j].vel = maxSpeed * (float)(random8())/255.0 * multiplier; stars[j].birth = it; @@ -2974,7 +3162,7 @@ uint16_t WS2812FX::mode_starburst(void) { } } - fill(SEGCOLOR(1)); + SEGMENT.fill(SEGCOLOR(1)); for (int j=0; j particleIgnition + particleFadeTime) { fade = 1.0f; // Black hole, all faded out stars[j].birth = 0; - c = col_to_crgb(SEGCOLOR(1)); + c = CRGB(SEGCOLOR(1)); } else { age -= particleIgnition; fade = (age / particleFadeTime); // Fading star byte f = 254.5f*fade; - c = col_to_crgb(color_blend(crgb_to_col(c), SEGCOLOR(1), f)); + c = CRGB(color_blend(RGBW32(c.r,c.g,c.b,0), SEGCOLOR(1), f)); } } - float particleSize = (1.0 - fade) * 2; + float particleSize = (1.0f - fade) * 2.0f; - for (uint8_t index=0; index < STARBURST_MAX_FRAG*2; index++) { + for (size_t index=0; index < STARBURST_MAX_FRAG*2; index++) { bool mirrored = index & 0x1; uint8_t i = index >> 1; if (stars[j].fragment[i] > 0) { @@ -3031,7 +3219,7 @@ uint16_t WS2812FX::mode_starburst(void) { if (start == end) end++; if (end > SEGLEN) end = SEGLEN; for (int p = start; p < end; p++) { - setPixelColor(p, c.r, c.g, c.b); + SEGMENT.setPixelColor(p, c.r, c.g, c.b); } } } @@ -3039,61 +3227,69 @@ uint16_t WS2812FX::mode_starburst(void) { return FRAMETIME; } #undef STARBURST_MAX_FRAG +static const char _data_FX_MODE_STARBURST[] PROGMEM = "Fireworks Starburst@Chance,Fragments;,!,;!;pal=11,mp12=0,1d"; + /* * Exploding fireworks effect * adapted from: http://www.anirama.com/1000leds/1d-fireworks/ + * adapted for 2D WLED by blazoncek (Blaz Kristan (AKA blazoncek)) */ -uint16_t WS2812FX::mode_exploding_fireworks(void) +uint16_t mode_exploding_fireworks(void) { + const uint16_t cols = strip.isMatrix ? SEGMENT.virtualWidth() : 1; + const uint16_t rows = strip.isMatrix ? SEGMENT.virtualHeight() : SEGMENT.virtualLength(); + //allocate segment data uint16_t maxData = FAIR_DATA_PER_SEG; //ESP8266: 256 ESP32: 640 - uint8_t segs = getActiveSegmentsNum(); - if (segs <= (MAX_NUM_SEGMENTS /2)) maxData *= 2; //ESP8266: 512 if <= 8 segs ESP32: 1280 if <= 16 segs - if (segs <= (MAX_NUM_SEGMENTS /4)) maxData *= 2; //ESP8266: 1024 if <= 4 segs ESP32: 2560 if <= 8 segs + uint8_t segs = strip.getActiveSegmentsNum(); + if (segs <= (strip.getMaxSegments() /2)) maxData *= 2; //ESP8266: 512 if <= 8 segs ESP32: 1280 if <= 16 segs + if (segs <= (strip.getMaxSegments() /4)) maxData *= 2; //ESP8266: 1024 if <= 4 segs ESP32: 2560 if <= 8 segs int maxSparks = maxData / sizeof(spark); //ESP8266: max. 21/42/85 sparks/seg, ESP32: max. 53/106/213 sparks/seg - uint16_t numSparks = min(2 + (SEGLEN >> 1), maxSparks); + uint16_t numSparks = min(2 + ((rows*cols) >> 1), maxSparks); uint16_t dataSize = sizeof(spark) * numSparks; - if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed + if (!SEGENV.allocateData(dataSize + sizeof(float))) return mode_static(); //allocation failed + float *dying_gravity = reinterpret_cast(SEGENV.data + dataSize); - if (dataSize != SEGENV.aux1) { //reset to flare if sparks were reallocated + if (dataSize != SEGENV.aux1) { //reset to flare if sparks were reallocated (it may be good idea to reset segment if bounds change) + *dying_gravity = 0.0f; SEGENV.aux0 = 0; SEGENV.aux1 = dataSize; } - fill(BLACK); - - bool actuallyReverse = SEGMENT.getOption(SEG_OPTION_REVERSED); - //have fireworks start in either direction based on intensity - SEGMENT.setOption(SEG_OPTION_REVERSED, SEGENV.step); + //SEGMENT.fill(BLACK); + SEGMENT.fade_out(252); Spark* sparks = reinterpret_cast(SEGENV.data); Spark* flare = sparks; //first spark is flare data - float gravity = -0.0004 - (SEGMENT.speed/800000.0); // m/s/s - gravity *= SEGLEN; + float gravity = -0.0004f - (SEGMENT.speed/800000.0f); // m/s/s + gravity *= rows; if (SEGENV.aux0 < 2) { //FLARE if (SEGENV.aux0 == 0) { //init flare flare->pos = 0; + flare->posX = strip.isMatrix ? random16(2,cols-1) : (SEGMENT.intensity > random8()); // will enable random firing side on 1D uint16_t peakHeight = 75 + random8(180); //0-255 - peakHeight = (peakHeight * (SEGLEN -1)) >> 8; - flare->vel = sqrt(-2.0 * gravity * peakHeight); + peakHeight = (peakHeight * (rows -1)) >> 8; + flare->vel = sqrt(-2.0f * gravity * peakHeight); + flare->velX = strip.isMatrix ? (random8(8)-4)/32.f : 0; // no X velocity on 1D flare->col = 255; //brightness - SEGENV.aux0 = 1; } // launch if (flare->vel > 12 * gravity) { // flare - setPixelColor(int(flare->pos),flare->col,flare->col,flare->col); - - flare->pos += flare->vel; - flare->pos = constrain(flare->pos, 0, SEGLEN-1); - flare->vel += gravity; - flare->col -= 2; + if (strip.isMatrix) SEGMENT.setPixelColorXY(int(flare->posX), rows - uint16_t(flare->pos) - 1, flare->col, flare->col, flare->col); + else SEGMENT.setPixelColor(int(flare->posX) ? rows - int(flare->pos) - 1 : int(flare->pos), flare->col, flare->col, flare->col); + flare->pos += flare->vel; + flare->posX += flare->velX; + flare->pos = constrain(flare->pos, 0, rows-1); + flare->posX = constrain(flare->posX, 0, cols-strip.isMatrix); + flare->vel += gravity; + flare->col -= 2; } else { SEGENV.aux0 = 2; // ready to explode } @@ -3104,48 +3300,56 @@ uint16_t WS2812FX::mode_exploding_fireworks(void) * Explosion happens where the flare ended. * Size is proportional to the height. */ - int nSparks = flare->pos; - nSparks = constrain(nSparks, 0, numSparks); - static float dying_gravity; + int nSparks = flare->pos + random8(4); + nSparks = constrain(nSparks, 1, numSparks); // initialize sparks if (SEGENV.aux0 == 2) { for (int i = 1; i < nSparks; i++) { - sparks[i].pos = flare->pos; - sparks[i].vel = (float(random16(0, 20000)) / 10000.0) - 0.9; // from -0.9 to 1.1 - sparks[i].col = 345;//abs(sparks[i].vel * 750.0); // set colors before scaling velocity to keep them bright + sparks[i].pos = flare->pos; + sparks[i].posX = flare->posX; + sparks[i].vel = (float(random16(0, 20000)) / 10000.0f) - 0.9f; // from -0.9 to 1.1 + sparks[i].vel *= rows<32 ? 0.5f : 1; // reduce velocity for smaller strips + sparks[i].velX = strip.isMatrix ? (float(random16(0, 4000)) / 10000.0f) - 0.2f : 0; // from -0.2 to 0.2 + sparks[i].col = 345;//abs(sparks[i].vel * 750.0); // set colors before scaling velocity to keep them bright //sparks[i].col = constrain(sparks[i].col, 0, 345); sparks[i].colIndex = random8(); - sparks[i].vel *= flare->pos/SEGLEN; // proportional to height - sparks[i].vel *= -gravity *50; + sparks[i].vel *= flare->pos/rows; // proportional to height + sparks[i].velX *= strip.isMatrix ? flare->posX/cols : 0; // proportional to width + sparks[i].vel *= -gravity *50; } //sparks[1].col = 345; // this will be our known spark - dying_gravity = gravity/2; + *dying_gravity = gravity/2; SEGENV.aux0 = 3; } if (sparks[1].col > 4) {//&& sparks[1].pos > 0) { // as long as our known spark is lit, work with all the sparks - for (int i = 1; i < nSparks; i++) { - sparks[i].pos += sparks[i].vel; - sparks[i].vel += dying_gravity; + for (int i = 1; i < nSparks; i++) { + sparks[i].pos += sparks[i].vel; + sparks[i].posX += sparks[i].velX; + sparks[i].vel += *dying_gravity; + sparks[i].velX += strip.isMatrix ? *dying_gravity : 0; if (sparks[i].col > 3) sparks[i].col -= 4; - if (sparks[i].pos > 0 && sparks[i].pos < SEGLEN) { + if (sparks[i].pos > 0 && sparks[i].pos < rows) { + if (strip.isMatrix && !(sparks[i].posX >= 0 && sparks[i].posX < cols)) continue; uint16_t prog = sparks[i].col; - uint32_t spColor = (SEGMENT.palette) ? color_wheel(sparks[i].colIndex) : SEGCOLOR(0); + uint32_t spColor = (SEGMENT.palette) ? SEGMENT.color_wheel(sparks[i].colIndex) : SEGCOLOR(0); CRGB c = CRGB::Black; //HeatColor(sparks[i].col); if (prog > 300) { //fade from white to spark color - c = col_to_crgb(color_blend(spColor, WHITE, (prog - 300)*5)); + c = CRGB(color_blend(spColor, WHITE, (prog - 300)*5)); } else if (prog > 45) { //fade from spark color to black - c = col_to_crgb(color_blend(BLACK, spColor, prog - 45)); + c = CRGB(color_blend(BLACK, spColor, prog - 45)); uint8_t cooling = (300 - prog) >> 5; c.g = qsub8(c.g, cooling); c.b = qsub8(c.b, cooling * 2); } - setPixelColor(int(sparks[i].pos), c.red, c.green, c.blue); + if (strip.isMatrix) SEGMENT.setPixelColorXY(int(sparks[i].posX), rows - int(sparks[i].pos) - 1, c.red, c.green, c.blue); + else SEGMENT.setPixelColor(int(sparks[i].posX) ? rows - int(sparks[i].pos) - 1 : int(sparks[i].pos), c.red, c.green, c.blue); } } - dying_gravity *= .99; // as sparks burn out they fall slower + SEGMENT.blur(16); + *dying_gravity *= .8f; // as sparks burn out they fall slower } else { SEGENV.aux0 = 6 + random8(10); //wait for this many frames } @@ -3153,152 +3357,195 @@ uint16_t WS2812FX::mode_exploding_fireworks(void) SEGENV.aux0--; if (SEGENV.aux0 < 4) { SEGENV.aux0 = 0; //back to flare - SEGENV.step = actuallyReverse ^ (SEGMENT.intensity > random8()); //decide firing side } } - SEGMENT.setOption(SEG_OPTION_REVERSED, actuallyReverse); - return FRAMETIME; } #undef MAX_SPARKS +static const char _data_FX_MODE_EXPLODING_FIREWORKS[] PROGMEM = "Fireworks 1D@Gravity,Firing side;!,!,;!=11;ix=128,1d,2d"; /* * Drip Effect * ported of: https://www.youtube.com/watch?v=sru2fXh4r7k */ -uint16_t WS2812FX::mode_drip(void) +uint16_t mode_drip(void) { //allocate segment data - uint8_t numDrops = 4; - uint16_t dataSize = sizeof(spark) * numDrops; - if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed - - fill(SEGCOLOR(1)); - + uint16_t strips = SEGMENT.nrOfVStrips(); + const int maxNumDrops = 4; + uint16_t dataSize = sizeof(spark) * maxNumDrops; + if (!SEGENV.allocateData(dataSize * strips)) return mode_static(); //allocation failed Spark* drops = reinterpret_cast(SEGENV.data); - numDrops = 1 + (SEGMENT.intensity >> 6); // 255>>6 = 3 + SEGMENT.fill(SEGCOLOR(1)); + + struct virtualStrip { + static void runStrip(uint16_t stripNr, Spark* drops) { - float gravity = -0.0005 - (SEGMENT.speed/50000.0); - gravity *= SEGLEN; - int sourcedrop = 12; + uint8_t numDrops = 1 + (SEGMENT.intensity >> 6); // 255>>6 = 3 - for (uint8_t j=0;j255) drops[j].col=255; - setPixelColor(uint16_t(drops[j].pos),color_blend(BLACK,SEGCOLOR(0),drops[j].col)); - - drops[j].col += map(SEGMENT.speed, 0, 255, 1, 6); // swelling - - if (random8() < drops[j].col/10) { // random drop - drops[j].colIndex=2; //fall - drops[j].col=255; - } - } - if (drops[j].colIndex > 1) { // falling - if (drops[j].pos > 0) { // fall until end of segment - drops[j].pos += drops[j].vel; - if (drops[j].pos < 0) drops[j].pos = 0; - drops[j].vel += gravity; // gravity is negative + float gravity = -0.0005 - (SEGMENT.speed/50000.0); + gravity *= SEGLEN-1; + int sourcedrop = 12; - for (uint16_t i=1;i<7-drops[j].colIndex;i++) { // some minor math so we don't expand bouncing droplets - uint16_t pos = constrain(uint16_t(drops[j].pos) +i, 0, SEGLEN-1); //this is BAD, returns a pos >= SEGLEN occasionally - setPixelColor(pos,color_blend(BLACK,SEGCOLOR(0),drops[j].col/i)); //spread pixel with fade while falling + for (int j=0;j 2) { // during bounce, some water is on the floor - setPixelColor(0,color_blend(SEGCOLOR(0),BLACK,drops[j].col)); - } - } else { // we hit bottom - if (drops[j].colIndex > 2) { // already hit once, so back to forming - drops[j].colIndex = 0; - drops[j].col = sourcedrop; - - } else { + SEGMENT.setPixelColor(indexToVStrip(SEGLEN-1, stripNr), color_blend(BLACK,SEGCOLOR(0), sourcedrop));// water source + if (drops[j].colIndex==1) { + if (drops[j].col>255) drops[j].col=255; + SEGMENT.setPixelColor(indexToVStrip(uint16_t(drops[j].pos), stripNr), color_blend(BLACK,SEGCOLOR(0),drops[j].col)); - if (drops[j].colIndex==2) { // init bounce - drops[j].vel = -drops[j].vel/4;// reverse velocity with damping + drops[j].col += map(SEGMENT.speed, 0, 255, 1, 6); // swelling + + if (random8() < drops[j].col/10) { // random drop + drops[j].colIndex=2; //fall + drops[j].col=255; + } + } + if (drops[j].colIndex > 1) { // falling + if (drops[j].pos > 0) { // fall until end of segment drops[j].pos += drops[j].vel; - } - drops[j].col = sourcedrop*2; - drops[j].colIndex = 5; // bouncing + if (drops[j].pos < 0) drops[j].pos = 0; + drops[j].vel += gravity; // gravity is negative + + for (int i=1;i<7-drops[j].colIndex;i++) { // some minor math so we don't expand bouncing droplets + uint16_t pos = constrain(uint16_t(drops[j].pos) +i, 0, SEGLEN-1); //this is BAD, returns a pos >= SEGLEN occasionally + SEGMENT.setPixelColor(indexToVStrip(pos, stripNr), color_blend(BLACK,SEGCOLOR(0),drops[j].col/i)); //spread pixel with fade while falling + } + + if (drops[j].colIndex > 2) { // during bounce, some water is on the floor + SEGMENT.setPixelColor(indexToVStrip(0, stripNr), color_blend(SEGCOLOR(0),BLACK,drops[j].col)); + } + } else { // we hit bottom + if (drops[j].colIndex > 2) { // already hit once, so back to forming + drops[j].colIndex = 0; + drops[j].col = sourcedrop; + + } else { + + if (drops[j].colIndex==2) { // init bounce + drops[j].vel = -drops[j].vel/4;// reverse velocity with damping + drops[j].pos += drops[j].vel; + } + drops[j].col = sourcedrop*2; + drops[j].colIndex = 5; // bouncing + } + } } } } - } - return FRAMETIME; + }; + + for (int stripNr=0; stripNr(SEGENV.data); + if (!SEGENV.allocateData(dataSize * strips)) return mode_static(); //allocation failed + Tetris* drops = reinterpret_cast(SEGENV.data); - // initialize dropping on first call or segment full - if (SEGENV.call == 0 || SEGENV.aux1 >= SEGLEN) { - SEGENV.aux1 = 0; // reset brick stack size - SEGENV.step = 0; - fill(SEGCOLOR(1)); - return 250; // short wait - } - - if (SEGENV.step == 0) { //init - drop->speed = 0.0238 * (SEGMENT.speed ? (SEGMENT.speed>>2)+1 : random8(6,64)); // set speed - drop->pos = SEGLEN; // start at end of segment (no need to subtract 1) - drop->col = color_from_palette(random8(0,15)<<4,false,false,0); // limit color choices so there is enough HUE gap - SEGENV.step = 1; // drop state (0 init, 1 forming, 2 falling) - SEGENV.aux0 = (SEGMENT.intensity ? (SEGMENT.intensity>>5)+1 : random8(1,5)) * (1+(SEGLEN>>6)); // size of brick - } - - if (SEGENV.step == 1) { // forming - if (random8()>>6) { // random drop - SEGENV.step = 2; // fall - } - } + if (SEGENV.call == 0) SEGMENT.fill(SEGCOLOR(1)); // will fill entire segment (1D or 2D) - if (SEGENV.step > 1) { // falling - if (drop->pos > SEGENV.aux1) { // fall until top of stack - drop->pos -= drop->speed; // may add gravity as: speed += gravity - if (int(drop->pos) < SEGENV.aux1) drop->pos = SEGENV.aux1; - for (uint16_t i=int(drop->pos); ipos)+SEGENV.aux0 ? drop->col : SEGCOLOR(1)); - } else { // we hit bottom - SEGENV.step = 0; // go back to init - SEGENV.aux1 += SEGENV.aux0; // increase the stack size - if (SEGENV.aux1 >= SEGLEN) return 1000; // wait for a second + // virtualStrip idea by @ewowi (Ewoud Wijma) + // requires virtual strip # to be embedded into upper 16 bits of index in setPixelcolor() + // the following functions will not work on virtual strips: fill(), fade_out(), fadeToBlack(), blur() + struct virtualStrip { + static void runStrip(size_t stripNr, Tetris *drop) { + // initialize dropping on first call or segment full + if (SEGENV.call == 0) { + drop->stack = 0; // reset brick stack size + drop->step = 0; + //for (int i=0; istep == 0) { // init brick + // speed calcualtion: a single brick should reach bottom of strip in X seconds + // if the speed is set to 1 this should take 5s and at 255 it should take 0.25s + // as this is dependant on SEGLEN it should be taken into account and the fact that effect runs every FRAMETIME s + int speed = SEGMENT.speed ? SEGMENT.speed : random8(1,255); + speed = map(speed, 1, 255, 5000, 250); // time taken for full (SEGLEN) drop + drop->speed = float(SEGLEN * FRAMETIME) / float(speed); // set speed + drop->pos = SEGLEN; // start at end of segment (no need to subtract 1) + drop->col = random8(0,15)<<4; // limit color choices so there is enough HUE gap + drop->step = 1; // drop state (0 init, 1 forming, 2 falling) + drop->brick = (SEGMENT.intensity ? (SEGMENT.intensity>>5)+1 : random8(1,5)) * (1+(SEGLEN>>6)); // size of brick + } + + if (drop->step == 1) { // forming + if (random8()>>6) { // random drop + drop->step = 2; // fall + } + } + + if (drop->step == 2) { // falling + if (drop->pos > drop->stack) { // fall until top of stack + drop->pos -= drop->speed; // may add gravity as: speed += gravity + if (uint16_t(drop->pos) < drop->stack) drop->pos = drop->stack; + for (int i=int(drop->pos); ipos)+drop->brick ? SEGMENT.color_from_palette(drop->col, false, false, 0) : SEGCOLOR(1); + SEGMENT.setPixelColor(indexToVStrip(i, stripNr), col); + } + } else { // we hit bottom + drop->step = 0; // proceed with next brick, go back to init + drop->stack += drop->brick; // increase the stack size + if (drop->stack >= SEGLEN) drop->step = millis() + 2000; // fade out stack + } + } + + if (drop->step > 2) { // fade strip + drop->brick = 0; // reset brick size (no more growing) + if (drop->step > millis()) { + // allow fading of virtual strip + for (int i=0; istack = 0; // reset brick stack size + drop->step = 0; // proceed with next brick + } + } } - } + }; + + for (int stripNr=0; stripNr> 5))+thisPhase) & 0xFF)/2 // factor=23 // Create a wave and add a phase change and add another wave with its own phase change. + cos8((i*(1+ 2*(SEGMENT.speed >> 5))+thatPhase) & 0xFF)/2; // factor=15 // Hey, you can even change the frequencies if you wish. uint8_t thisBright = qsub8(colorIndex, beatsin8(7,0, (128 - (SEGMENT.intensity>>1)))); - CRGB color = ColorFromPalette(currentPalette, colorIndex, thisBright, LINEARBLEND); - setPixelColor(i, color.red, color.green, color.blue); + //CRGB color = ColorFromPalette(SEGPALETTE, colorIndex, thisBright, LINEARBLEND); + //SEGMENT.setPixelColor(i, color.red, color.green, color.blue); + SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(colorIndex, false, PALETTE_SOLID_WRAP, 0, thisBright)); } return FRAMETIME; } +static const char _data_FX_MODE_PLASMA[] PROGMEM = "Plasma@Phase,;1,2,3;!;1d"; /* * Percentage display * Intesity values from 0-100 turn on the leds. */ -uint16_t WS2812FX::mode_percent(void) { +uint16_t mode_percent(void) { - uint8_t percent = MAX(0, MIN(200, SEGMENT.intensity)); - uint16_t active_leds = (percent < 100) ? SEGLEN * percent / 100.0 + uint8_t percent = SEGMENT.intensity; + percent = constrain(percent, 0, 200); + uint16_t active_leds = (percent < 100) ? SEGLEN * percent / 100.0 : SEGLEN * (200 - percent) / 100.0; uint8_t size = (1 + ((SEGMENT.speed * SEGLEN) >> 11)); if (SEGMENT.speed == 255) size = 255; - if (percent < 100) { - for (uint16_t i = 0; i < SEGLEN; i++) { - if (i < SEGENV.step) { - setPixelColor(i, color_from_palette(i, true, PALETTE_SOLID_WRAP, 0)); - } - else { - setPixelColor(i, SEGCOLOR(1)); - } - } + if (percent <= 100) { + for (int i = 0; i < SEGLEN; i++) { + if (i < SEGENV.aux1) { + if (SEGMENT.check1) + SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(map(percent,0,100,0,255), false, false, 0)); + else + SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0)); + } + else { + SEGMENT.setPixelColor(i, SEGCOLOR(1)); + } + } } else { - for (uint16_t i = 0; i < SEGLEN; i++) { - if (i < (SEGLEN - SEGENV.step)) { - setPixelColor(i, SEGCOLOR(1)); - } - else { - setPixelColor(i, color_from_palette(i, true, PALETTE_SOLID_WRAP, 0)); - } - } + for (int i = 0; i < SEGLEN; i++) { + if (i < (SEGLEN - SEGENV.aux1)) { + SEGMENT.setPixelColor(i, SEGCOLOR(1)); + } + else { + if (SEGMENT.check1) + SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(map(percent,100,200,255,0), false, false, 0)); + else + SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0)); + } + } } - if(active_leds > SEGENV.step) { // smooth transition to the target value - SEGENV.step += size; - if (SEGENV.step > active_leds) SEGENV.step = active_leds; - } else if (active_leds < SEGENV.step) { - if (SEGENV.step > size) SEGENV.step -= size; else SEGENV.step = 0; - if (SEGENV.step < active_leds) SEGENV.step = active_leds; + if(active_leds > SEGENV.aux1) { // smooth transition to the target value + SEGENV.aux1 += size; + if (SEGENV.aux1 > active_leds) SEGENV.aux1 = active_leds; + } else if (active_leds < SEGENV.aux1) { + if (SEGENV.aux1 > size) SEGENV.aux1 -= size; else SEGENV.aux1 = 0; + if (SEGENV.aux1 < active_leds) SEGENV.aux1 = active_leds; } return FRAMETIME; } +static const char _data_FX_MODE_PERCENT[] PROGMEM = "Percent@,% of fill,,,,One color;!,!,;!;1d"; + /* -/ Modulates the brightness similar to a heartbeat -*/ -uint16_t WS2812FX::mode_heartbeat(void) { - uint8_t bpm = 40 + (SEGMENT.speed >> 4); - uint32_t msPerBeat = (60000 / bpm); + * Modulates the brightness similar to a heartbeat + * tries to draw an ECG aproximation on a 2D matrix + */ +uint16_t mode_heartbeat(void) { + uint8_t bpm = 40 + (SEGMENT.speed >> 3); + uint32_t msPerBeat = (60000L / bpm); uint32_t secondBeat = (msPerBeat / 3); - uint32_t bri_lower = SEGENV.aux1; + unsigned long beatTimer = strip.now - SEGENV.step; + bri_lower = bri_lower * 2042 / (2048 + SEGMENT.intensity); SEGENV.aux1 = bri_lower; - unsigned long beatTimer = millis() - SEGENV.step; - if((beatTimer > secondBeat) && !SEGENV.aux0) { // time for the second beat? - SEGENV.aux1 = UINT16_MAX; //full bri + if ((beatTimer > secondBeat) && !SEGENV.aux0) { // time for the second beat? + SEGENV.aux1 = UINT16_MAX; //3/4 bri SEGENV.aux0 = 1; } - if(beatTimer > msPerBeat) { // time to reset the beat timer? + if (beatTimer > msPerBeat) { // time to reset the beat timer? SEGENV.aux1 = UINT16_MAX; //full bri SEGENV.aux0 = 0; - SEGENV.step = millis(); + SEGENV.step = strip.now; } - for (uint16_t i = 0; i < SEGLEN; i++) { - setPixelColor(i, color_blend(color_from_palette(i, true, PALETTE_SOLID_WRAP, 0), SEGCOLOR(1), 255 - (SEGENV.aux1 >> 8))); + for (int i = 0; i < SEGLEN; i++) { + SEGMENT.setPixelColor(i, color_blend(SEGMENT.color_from_palette(i, true, PALETTE_SOLID_WRAP, 0), SEGCOLOR(1), 255 - (SEGENV.aux1 >> 8))); } return FRAMETIME; } +static const char _data_FX_MODE_HEARTBEAT[] PROGMEM = "Heartbeat@!,!;!,!,;!;mp12=1,1d"; // "Pacifica" @@ -3417,9 +3677,25 @@ uint16_t WS2812FX::mode_heartbeat(void) { // // Modified for WLED, based on https://github.com/FastLED/FastLED/blob/master/examples/Pacifica/Pacifica.ino // -uint16_t WS2812FX::mode_pacifica() +// Add one layer of waves into the led array +CRGB pacifica_one_layer(uint16_t i, CRGBPalette16& p, uint16_t cistart, uint16_t wavescale, uint8_t bri, uint16_t ioff) { - uint32_t nowOld = now; + uint16_t ci = cistart; + uint16_t waveangle = ioff; + uint16_t wavescale_half = (wavescale >> 1) + 20; + + waveangle += ((120 + SEGMENT.intensity) * i); //original 250 * i + uint16_t s16 = sin16(waveangle) + 32768; + uint16_t cs = scale16(s16, wavescale_half) + wavescale_half; + ci += (cs * i); + uint16_t sindex16 = sin16(ci) + 32768; + uint8_t sindex8 = scale16(sindex16, 240); + return ColorFromPalette(p, sindex8, bri, LINEARBLEND); +} + +uint16_t mode_pacifica() +{ + uint32_t nowOld = strip.now; CRGBPalette16 pacifica_palette_1 = { 0x000507, 0x000409, 0x00030B, 0x00030D, 0x000210, 0x000212, 0x000114, 0x000117, @@ -3432,19 +3708,17 @@ uint16_t WS2812FX::mode_pacifica() 0x000E39, 0x001040, 0x001450, 0x001860, 0x001C70, 0x002080, 0x1040BF, 0x2060FF }; if (SEGMENT.palette) { - pacifica_palette_1 = currentPalette; - pacifica_palette_2 = currentPalette; - pacifica_palette_3 = currentPalette; + pacifica_palette_1 = SEGPALETTE; + pacifica_palette_2 = SEGPALETTE; + pacifica_palette_3 = SEGPALETTE; } // Increment the four "color index start" counters, one for each wave layer. // Each is incremented at a different speed, and the speeds vary over time. uint16_t sCIStart1 = SEGENV.aux0, sCIStart2 = SEGENV.aux1, sCIStart3 = SEGENV.step, sCIStart4 = SEGENV.step >> 16; - //static uint16_t sCIStart1, sCIStart2, sCIStart3, sCIStart4; - //uint32_t deltams = 26 + (SEGMENT.speed >> 3); uint32_t deltams = (FRAMETIME >> 2) + ((FRAMETIME * SEGMENT.speed) >> 7); - uint64_t deltat = (now >> 2) + ((now * SEGMENT.speed) >> 7); - now = deltat; + uint64_t deltat = (strip.now >> 2) + ((strip.now * SEGMENT.speed) >> 7); + strip.now = deltat; uint16_t speedfactor1 = beatsin16(3, 179, 269); uint16_t speedfactor2 = beatsin16(4, 179, 269); @@ -3459,12 +3733,12 @@ uint16_t WS2812FX::mode_pacifica() SEGENV.step = sCIStart4; SEGENV.step = (SEGENV.step << 16) + sCIStart3; // Clear out the LED array to a dim background blue-green - //fill(132618); + //SEGMENT.fill(132618); uint8_t basethreshold = beatsin8( 9, 55, 65); uint8_t wave = beat8( 7 ); - for( uint16_t i = 0; i < SEGLEN; i++) { + for (int i = 0; i < SEGLEN; i++) { CRGB c = CRGB(2, 6, 10); // Render each of four layers, with different scales and speeds, that vary over time c += pacifica_one_layer(i, pacifica_palette_1, sCIStart1, beatsin16(3, 11 * 256, 14 * 256), beatsin8(10, 70, 130), 0-beat16(301)); @@ -3487,117 +3761,104 @@ uint16_t WS2812FX::mode_pacifica() c.green = scale8(c.green, 200); c |= CRGB( 2, 5, 7); - setPixelColor(i, c.red, c.green, c.blue); + SEGMENT.setPixelColor(i, c.red, c.green, c.blue); } - now = nowOld; + strip.now = nowOld; return FRAMETIME; } +static const char _data_FX_MODE_PACIFICA[] PROGMEM = "Pacifica"; -// Add one layer of waves into the led array -CRGB WS2812FX::pacifica_one_layer(uint16_t i, CRGBPalette16& p, uint16_t cistart, uint16_t wavescale, uint8_t bri, uint16_t ioff) -{ - uint16_t ci = cistart; - uint16_t waveangle = ioff; - uint16_t wavescale_half = (wavescale >> 1) + 20; - - waveangle += ((120 + SEGMENT.intensity) * i); //original 250 * i - uint16_t s16 = sin16(waveangle) + 32768; - uint16_t cs = scale16(s16, wavescale_half) + wavescale_half; - ci += (cs * i); - uint16_t sindex16 = sin16(ci) + 32768; - uint8_t sindex8 = scale16(sindex16, 240); - return ColorFromPalette(p, sindex8, bri, LINEARBLEND); -} //Solid colour background with glitter -uint16_t WS2812FX::mode_solid_glitter() +uint16_t mode_solid_glitter() { - fill(SEGCOLOR(0)); + SEGMENT.fill(SEGCOLOR(0)); if (SEGMENT.intensity > random8()) { - setPixelColor(random16(SEGLEN), ULTRAWHITE); + SEGMENT.setPixelColor(random16(SEGLEN), ULTRAWHITE); } + return FRAMETIME; } +static const char _data_FX_MODE_SOLID_GLITTER[] PROGMEM = "Solid Glitter@,!;!,,;0;mp12=0,1d"; /* * Mode simulates a gradual sunrise */ -uint16_t WS2812FX::mode_sunrise() { +uint16_t mode_sunrise() { //speed 0 - static sun //speed 1 - 60: sunrise time in minutes //speed 60 - 120 : sunset time in minutes - 60; //speed above: "breathing" rise and set if (SEGENV.call == 0 || SEGMENT.speed != SEGENV.aux0) { - SEGENV.step = millis(); //save starting time, millis() because now can change from sync + SEGENV.step = millis(); //save starting time, millis() because now can change from sync SEGENV.aux0 = SEGMENT.speed; } - fill(0); + SEGMENT.fill(0); uint16_t stage = 0xFFFF; uint32_t s10SinceStart = (millis() - SEGENV.step) /100; //tenths of seconds if (SEGMENT.speed > 120) { //quick sunrise and sunset - uint16_t counter = (now >> 1) * (((SEGMENT.speed -120) >> 1) +1); - stage = triwave16(counter); + uint16_t counter = (strip.now >> 1) * (((SEGMENT.speed -120) >> 1) +1); + stage = triwave16(counter); } else if (SEGMENT.speed) { //sunrise - uint8_t durMins = SEGMENT.speed; - if (durMins > 60) durMins -= 60; - uint32_t s10Target = durMins * 600; - if (s10SinceStart > s10Target) s10SinceStart = s10Target; - stage = map(s10SinceStart, 0, s10Target, 0, 0xFFFF); - if (SEGMENT.speed > 60) stage = 0xFFFF - stage; //sunset + uint8_t durMins = SEGMENT.speed; + if (durMins > 60) durMins -= 60; + uint32_t s10Target = durMins * 600; + if (s10SinceStart > s10Target) s10SinceStart = s10Target; + stage = map(s10SinceStart, 0, s10Target, 0, 0xFFFF); + if (SEGMENT.speed > 60) stage = 0xFFFF - stage; //sunset } - for (uint16_t i = 0; i <= SEGLEN/2; i++) + for (int i = 0; i <= SEGLEN/2; i++) { //default palette is Fire - uint32_t c = color_from_palette(0, false, true, 255); //background + uint32_t c = SEGMENT.color_from_palette(0, false, true, 255); //background uint16_t wave = triwave16((i * stage) / SEGLEN); wave = (wave >> 8) + ((wave * SEGMENT.intensity) >> 15); if (wave > 240) { //clipped, full white sun - c = color_from_palette( 240, false, true, 255); + c = SEGMENT.color_from_palette( 240, false, true, 255); } else { //transition - c = color_from_palette(wave, false, true, 255); + c = SEGMENT.color_from_palette(wave, false, true, 255); } - setPixelColor(i, c); - setPixelColor(SEGLEN - i - 1, c); + SEGMENT.setPixelColor(i, c); + SEGMENT.setPixelColor(SEGLEN - i - 1, c); } return FRAMETIME; } +static const char _data_FX_MODE_SUNRISE[] PROGMEM = "Sunrise@Time [min],;;!;sx=60,1d"; /* * Effects by Andrew Tuline */ -uint16_t WS2812FX::phased_base(uint8_t moder) { // We're making sine waves here. By Andrew Tuline. +uint16_t phased_base(uint8_t moder) { // We're making sine waves here. By Andrew Tuline. uint8_t allfreq = 16; // Base frequency. - //float* phasePtr = reinterpret_cast(SEGENV.step); // Phase change value gets calculated. - static float phase = 0;//phasePtr[0]; + float *phase = reinterpret_cast(&SEGENV.step); // Phase change value gets calculated (float fits into unsigned long). uint8_t cutOff = (255-SEGMENT.intensity); // You can change the number of pixels. AKA INTENSITY (was 192). uint8_t modVal = 5;//SEGMENT.fft1/8+1; // You can change the modulus. AKA FFT1 (was 5). - uint8_t index = now/64; // Set color rotation speed - phase += SEGMENT.speed/32.0; // You can change the speed of the wave. AKA SPEED (was .4) - //phasePtr[0] = phase; + uint8_t index = strip.now/64; // Set color rotation speed + *phase += SEGMENT.speed/32.0; // You can change the speed of the wave. AKA SPEED (was .4) for (int i = 0; i < SEGLEN; i++) { if (moder == 1) modVal = (inoise8(i*10 + i*10) /16); // Let's randomize our mod length with some Perlin noise. - uint16_t val = (i+1) * allfreq; // This sets the frequency of the waves. The +1 makes sure that leds[0] is used. + uint16_t val = (i+1) * allfreq; // This sets the frequency of the waves. The +1 makes sure that led 0 is used. if (modVal == 0) modVal = 1; - val += phase * (i % modVal +1) /2; // This sets the varying phase change of the waves. By Andrew Tuline. + val += *phase * (i % modVal +1) /2; // This sets the varying phase change of the waves. By Andrew Tuline. uint8_t b = cubicwave8(val); // Now we make an 8 bit sinewave. b = (b > cutOff) ? (b - cutOff) : 0; // A ternary operator to cutoff the light. - setPixelColor(i, color_blend(SEGCOLOR(1), color_from_palette(index, false, false, 0), b)); + SEGMENT.setPixelColor(i, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(index, false, false, 0), b)); index += 256 / SEGLEN; if (SEGLEN > 256) index ++; // Correction for segments longer than 256 LEDs } @@ -3606,35 +3867,35 @@ uint16_t WS2812FX::phased_base(uint8_t moder) { // We're making } - -uint16_t WS2812FX::mode_phased(void) { +uint16_t mode_phased(void) { return phased_base(0); } +static const char _data_FX_MODE_PHASED[] PROGMEM = "Phased"; - -uint16_t WS2812FX::mode_phased_noise(void) { +uint16_t mode_phased_noise(void) { return phased_base(1); } +static const char _data_FX_MODE_PHASEDNOISE[] PROGMEM = "Phased Noise"; - -uint16_t WS2812FX::mode_twinkleup(void) { // A very short twinkle routine with fade-in and dual controls. By Andrew Tuline. - random16_set_seed(535); // The randomizer needs to be re-set each time through the loop in order for the same 'random' numbers to be the same each time through. +uint16_t mode_twinkleup(void) { // A very short twinkle routine with fade-in and dual controls. By Andrew Tuline. + random16_set_seed(535); // The randomizer needs to be re-set each time through the loop in order for the same 'random' numbers to be the same each time through. for (int i = 0; i SEGMENT.intensity) pixBri = 0; - setPixelColor(i, color_blend(SEGCOLOR(1), color_from_palette(random8()+now/100, false, PALETTE_SOLID_WRAP, 0), pixBri)); + SEGMENT.setPixelColor(i, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(random8()+strip.now/100, false, PALETTE_SOLID_WRAP, 0), pixBri)); } return FRAMETIME; } +static const char _data_FX_MODE_TWINKLEUP[] PROGMEM = "Twinkleup@!,Intensity;!,!,;!;mp12=0,1d"; // Peaceful noise that's slow and with gradually changing palettes. Does not support WLED palettes or default colours or controls. -uint16_t WS2812FX::mode_noisepal(void) { // Slow noise palette by Andrew Tuline. +uint16_t mode_noisepal(void) { // Slow noise palette by Andrew Tuline. uint16_t scale = 15 + (SEGMENT.intensity >> 2); //default was 30 //#define scale 30 @@ -3657,27 +3918,28 @@ uint16_t WS2812FX::mode_noisepal(void) { // S //EVERY_N_MILLIS(10) { //(don't have to time this, effect function is only called every 24ms) nblendPaletteTowardPalette(palettes[0], palettes[1], 48); // Blend towards the target palette over 48 iterations. - if (SEGMENT.palette > 0) palettes[0] = currentPalette; + if (SEGMENT.palette > 0) palettes[0] = SEGPALETTE; - for(int i = 0; i < SEGLEN; i++) { + for (int i = 0; i < SEGLEN; i++) { uint8_t index = inoise8(i*scale, SEGENV.aux0+i*scale); // Get a value from the noise function. I'm using both x and y axis. color = ColorFromPalette(palettes[0], index, 255, LINEARBLEND); // Use the my own palette. - setPixelColor(i, color.red, color.green, color.blue); + SEGMENT.setPixelColor(i, color.red, color.green, color.blue); } SEGENV.aux0 += beatsin8(10,1,4); // Moving along the distance. Vary it a bit with a sine wave. return FRAMETIME; } +static const char _data_FX_MODE_NOISEPAL[] PROGMEM = "Noise Pal"; // Sine waves that have controllable phase change speed, frequency and cutoff. By Andrew Tuline. // SEGMENT.speed ->Speed, SEGMENT.intensity -> Frequency (SEGMENT.fft1 -> Color change, SEGMENT.fft2 -> PWM cutoff) // -uint16_t WS2812FX::mode_sinewave(void) { // Adjustable sinewave. By Andrew Tuline +uint16_t mode_sinewave(void) { // Adjustable sinewave. By Andrew Tuline //#define qsuba(x, b) ((x>b)?x-b:0) // Analog Unsigned subtraction macro. if result <0, then => 0 - uint16_t colorIndex = now /32;//(256 - SEGMENT.fft1); // Amount of colour change. + uint16_t colorIndex = strip.now /32;//(256 - SEGMENT.fft1); // Amount of colour change. SEGENV.step += SEGMENT.speed/16; // Speed of animation. uint16_t freq = SEGMENT.intensity/4;//SEGMENT.fft2/8; // Frequency of the signal. @@ -3685,22 +3947,23 @@ uint16_t WS2812FX::mode_sinewave(void) { // Adjustable sinewave. By for (int i=0; i> 2) +1); + counter = strip.now * ((SEGMENT.speed >> 2) +1); counter = counter >> 8; } @@ -3711,45 +3974,50 @@ uint16_t WS2812FX::mode_flow(void) uint16_t zoneLen = SEGLEN / zones; uint16_t offset = (SEGLEN - zones * zoneLen) >> 1; - fill(color_from_palette(-counter, false, true, 255)); + SEGMENT.fill(SEGMENT.color_from_palette(-counter, false, true, 255)); - for (uint16_t z = 0; z < zones; z++) + for (int z = 0; z < zones; z++) { uint16_t pos = offset + z * zoneLen; - for (uint16_t i = 0; i < zoneLen; i++) + for (int i = 0; i < zoneLen; i++) { uint8_t colorIndex = (i * 255 / zoneLen) - counter; uint16_t led = (z & 0x01) ? i : (zoneLen -1) -i; - if (IS_REVERSE) led = (zoneLen -1) -led; - setPixelColor(pos + led, color_from_palette(colorIndex, false, true, 255)); + if (SEGMENT.reverse) led = (zoneLen -1) -led; + SEGMENT.setPixelColor(pos + led, SEGMENT.color_from_palette(colorIndex, false, true, 255)); } } return FRAMETIME; } +static const char _data_FX_MODE_FLOW[] PROGMEM = "Flow@!,!;!,!,!;!;mp12=1,1d"; //vertical /* * Dots waving around in a sine/pendulum motion. * Little pixel birds flying in a circle. By Aircoookie */ -uint16_t WS2812FX::mode_chunchun(void) +uint16_t mode_chunchun(void) { - fill(SEGCOLOR(1)); - uint16_t counter = now*(6 + (SEGMENT.speed >> 4)); + //SEGMENT.fill(SEGCOLOR(1)); + SEGMENT.fade_out(254); // add a bit of trail + uint16_t counter = strip.now * (6 + (SEGMENT.speed >> 4)); uint16_t numBirds = 2 + (SEGLEN >> 3); // 2 + 1/8 of a segment uint16_t span = (SEGMENT.intensity << 8) / numBirds; - for (uint16_t i = 0; i < numBirds; i++) + for (int i = 0; i < numBirds; i++) { counter -= span; uint16_t megumin = sin16(counter) + 0x8000; - uint32_t bird = (megumin * SEGLEN) >> 16; - uint32_t c = color_from_palette((i * 255)/ numBirds, false, false, 0); // no palette wrapping - setPixelColor(bird, c); + uint16_t bird = uint32_t(megumin * SEGLEN) >> 16; + uint32_t c = SEGMENT.color_from_palette((i * 255)/ numBirds, false, false, 0); // no palette wrapping + bird = constrain(bird, 0, SEGLEN-1); + SEGMENT.setPixelColor(bird, c); } return FRAMETIME; } +static const char _data_FX_MODE_CHUNCHUN[] PROGMEM = "Chunchun@!,Gap size;!,!,;!;1d"; + //13 bytes typedef struct Spotlight { @@ -3781,7 +4049,7 @@ typedef struct Spotlight { * * By Steve Pomeroy @xxv */ -uint16_t WS2812FX::mode_dancing_shadows(void) +uint16_t mode_dancing_shadows(void) { uint8_t numSpotlights = map(SEGMENT.intensity, 0, 255, 2, SPOT_MAX_COUNT); // 49 on 32 segment ESP32, 17 on 16 segment ESP8266 bool initialize = SEGENV.aux0 != numSpotlights; @@ -3791,12 +4059,12 @@ uint16_t WS2812FX::mode_dancing_shadows(void) if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed Spotlight* spotlights = reinterpret_cast(SEGENV.data); - fill(BLACK); + SEGMENT.fill(BLACK); unsigned long time = millis(); bool respawn = false; - for (uint8_t i = 0; i < numSpotlights; i++) { + for (size_t i = 0; i < numSpotlights; i++) { if (!initialize) { // advance the position of the spotlight int16_t delta = (float)(time - spotlights[i].lastUpdateTime) * @@ -3833,61 +4101,59 @@ uint16_t WS2812FX::mode_dancing_shadows(void) spotlights[i].type = random8(SPOT_TYPES_COUNT); } - uint32_t color = color_from_palette(spotlights[i].colorIdx, false, false, 0); + uint32_t color = SEGMENT.color_from_palette(spotlights[i].colorIdx, false, false, 0); int start = spotlights[i].position; if (spotlights[i].width <= 1) { if (start >= 0 && start < SEGLEN) { - blendPixelColor(start, color, 128); + SEGMENT.blendPixelColor(start, color, 128); } } else { switch (spotlights[i].type) { case SPOT_TYPE_SOLID: - for (uint8_t j = 0; j < spotlights[i].width; j++) { + for (size_t j = 0; j < spotlights[i].width; j++) { if ((start + j) >= 0 && (start + j) < SEGLEN) { - blendPixelColor(start + j, color, 128); + SEGMENT.blendPixelColor(start + j, color, 128); } } break; case SPOT_TYPE_GRADIENT: - for (uint8_t j = 0; j < spotlights[i].width; j++) { + for (size_t j = 0; j < spotlights[i].width; j++) { if ((start + j) >= 0 && (start + j) < SEGLEN) { - blendPixelColor(start + j, color, - cubicwave8(map(j, 0, spotlights[i].width - 1, 0, 255))); + SEGMENT.blendPixelColor(start + j, color, cubicwave8(map(j, 0, spotlights[i].width - 1, 0, 255))); } } break; case SPOT_TYPE_2X_GRADIENT: - for (uint8_t j = 0; j < spotlights[i].width; j++) { + for (size_t j = 0; j < spotlights[i].width; j++) { if ((start + j) >= 0 && (start + j) < SEGLEN) { - blendPixelColor(start + j, color, - cubicwave8(2 * map(j, 0, spotlights[i].width - 1, 0, 255))); + SEGMENT.blendPixelColor(start + j, color, cubicwave8(2 * map(j, 0, spotlights[i].width - 1, 0, 255))); } } break; case SPOT_TYPE_2X_DOT: - for (uint8_t j = 0; j < spotlights[i].width; j += 2) { + for (size_t j = 0; j < spotlights[i].width; j += 2) { if ((start + j) >= 0 && (start + j) < SEGLEN) { - blendPixelColor(start + j, color, 128); + SEGMENT.blendPixelColor(start + j, color, 128); } } break; case SPOT_TYPE_3X_DOT: - for (uint8_t j = 0; j < spotlights[i].width; j += 3) { + for (size_t j = 0; j < spotlights[i].width; j += 3) { if ((start + j) >= 0 && (start + j) < SEGLEN) { - blendPixelColor(start + j, color, 128); + SEGMENT.blendPixelColor(start + j, color, 128); } } break; case SPOT_TYPE_4X_DOT: - for (uint8_t j = 0; j < spotlights[i].width; j += 4) { + for (size_t j = 0; j < spotlights[i].width; j += 4) { if ((start + j) >= 0 && (start + j) < SEGLEN) { - blendPixelColor(start + j, color, 128); + SEGMENT.blendPixelColor(start + j, color, 128); } } break; @@ -3897,13 +4163,15 @@ uint16_t WS2812FX::mode_dancing_shadows(void) return FRAMETIME; } +static const char _data_FX_MODE_DANCING_SHADOWS[] PROGMEM = "Dancing Shadows@!,# of shadows;!,!,!;!;1d"; + /* Imitates a washing machine, rotating same waves forward, then pause, then backward. By Stefan Seegel */ -uint16_t WS2812FX::mode_washing_machine(void) { - float speed = tristate_square8(now >> 7, 90, 15); +uint16_t mode_washing_machine(void) { + float speed = tristate_square8(strip.now >> 7, 90, 15); float quot = 32.0f - ((float)SEGMENT.speed / 16.0f); speed /= quot; @@ -3911,37 +4179,41 @@ uint16_t WS2812FX::mode_washing_machine(void) { for (int i=0; i> 7)); - setPixelColor(i, color_from_palette(col, false, PALETTE_SOLID_WRAP, 3)); + SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(col, false, PALETTE_SOLID_WRAP, 3)); } return FRAMETIME; } +static const char _data_FX_MODE_WASHING_MACHINE[] PROGMEM = "Washing Machine"; + /* Blends random colors across palette Modified, originally by Mark Kriegsman https://gist.github.com/kriegsman/1f7ccbbfa492a73c015e */ -uint16_t WS2812FX::mode_blends(void) { +uint16_t mode_blends(void) { uint16_t pixelLen = SEGLEN > UINT8_MAX ? UINT8_MAX : SEGLEN; uint16_t dataSize = sizeof(uint32_t) * (pixelLen + 1); // max segment length of 56 pixels on 16 segment ESP8266 if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed uint32_t* pixels = reinterpret_cast(SEGENV.data); uint8_t blendSpeed = map(SEGMENT.intensity, 0, UINT8_MAX, 10, 128); - uint8_t shift = (now * ((SEGMENT.speed >> 3) +1)) >> 8; + uint8_t shift = (strip.now * ((SEGMENT.speed >> 3) +1)) >> 8; for (int i = 0; i < pixelLen; i++) { - pixels[i] = color_blend(pixels[i], color_from_palette(shift + quadwave8((i + 1) * 16), false, PALETTE_SOLID_WRAP, 255), blendSpeed); + pixels[i] = color_blend(pixels[i], SEGMENT.color_from_palette(shift + quadwave8((i + 1) * 16), false, PALETTE_SOLID_WRAP, 255), blendSpeed); shift += 3; } uint16_t offset = 0; for (int i = 0; i < SEGLEN; i++) { - setPixelColor(i, pixels[offset++]); + SEGMENT.setPixelColor(i, pixels[offset++]); if (offset > pixelLen) offset = 0; } return FRAMETIME; } +static const char _data_FX_MODE_BLENDS[] PROGMEM = "Blends@Shift speed,Blend speed;1,2,3;!;1d"; + /* TV Simulator @@ -3968,7 +4240,7 @@ typedef struct TvSim { uint16_t pb = 0; } tvSim; -uint16_t WS2812FX::mode_tv_simulator(void) { +uint16_t mode_tv_simulator(void) { uint16_t nr, ng, nb, r, g, b, i, hue; uint8_t sat, bri, j; @@ -4056,7 +4328,7 @@ uint16_t WS2812FX::mode_tv_simulator(void) { // set strip color for (i = 0; i < SEGLEN; i++) { - setPixelColor(i, r >> 8, g >> 8, b >> 8); // Quantize to 8-bit + SEGMENT.setPixelColor(i, r >> 8, g >> 8, b >> 8); // Quantize to 8-bit } // if total duration has passed, remember last color and restart the loop @@ -4069,6 +4341,8 @@ uint16_t WS2812FX::mode_tv_simulator(void) { return FRAMETIME; } +static const char _data_FX_MODE_TV_SIMULATOR[] PROGMEM = "TV Simulator"; + /* Aurora effect @@ -4170,12 +4444,15 @@ class AuroraWave { }; }; -uint16_t WS2812FX::mode_aurora(void) { +uint16_t mode_aurora(void) { //aux1 = Wavecount //aux2 = Intensity in last loop AuroraWave* waves; +//TODO: I am not sure this is a correct way of handling memory allocation since if it fails on 1st run +// it will display static effect but on second run it may crash ESP since data will be nullptr + if(SEGENV.aux0 != SEGMENT.intensity || SEGENV.call == 0) { //Intensity slider changed or first call SEGENV.aux1 = map(SEGMENT.intensity, 0, 255, 2, W_MAX_COUNT); @@ -4187,20 +4464,20 @@ uint16_t WS2812FX::mode_aurora(void) { waves = reinterpret_cast(SEGENV.data); - for(int i = 0; i < SEGENV.aux1; i++) { - waves[i].init(SEGLEN, col_to_crgb(color_from_palette(random8(), false, false, random(0, 3)))); + for (int i = 0; i < SEGENV.aux1; i++) { + waves[i].init(SEGLEN, CRGB(SEGMENT.color_from_palette(random8(), false, false, random(0, 3)))); } } else { waves = reinterpret_cast(SEGENV.data); } - for(int i = 0; i < SEGENV.aux1; i++) { + for (int i = 0; i < SEGENV.aux1; i++) { //Update values of wave waves[i].update(SEGLEN, SEGMENT.speed); if(!(waves[i].stillAlive())) { //If a wave dies, reinitialize it starts over. - waves[i].init(SEGLEN, col_to_crgb(color_from_palette(random8(), false, false, random(0, 3)))); + waves[i].init(SEGLEN, CRGB(SEGMENT.color_from_palette(random8(), false, false, random(0, 3)))); } } @@ -4209,12 +4486,12 @@ uint16_t WS2812FX::mode_aurora(void) { if (SEGCOLOR(1)) backlight++; if (SEGCOLOR(2)) backlight++; //Loop through LEDs to determine color - for(int i = 0; i < SEGLEN; i++) { + for (int i = 0; i < SEGLEN; i++) { CRGB mixedRgb = CRGB(backlight, backlight, backlight); //For each LED we must check each wave if it is "active" at this position. //If there are multiple waves active on a LED we multiply their values. - for(int j = 0; j < SEGENV.aux1; j++) { + for (int j = 0; j < SEGENV.aux1; j++) { CRGB rgb = waves[j].getColorForLED(i); if(rgb != CRGB(0)) { @@ -4222,8 +4499,3021 @@ uint16_t WS2812FX::mode_aurora(void) { } } - setPixelColor(i, mixedRgb[0], mixedRgb[1], mixedRgb[2]); + SEGMENT.setPixelColor(i, mixedRgb[0], mixedRgb[1], mixedRgb[2]); } return FRAMETIME; -} \ No newline at end of file +} +static const char _data_FX_MODE_AURORA[] PROGMEM = "Aurora@!,!;1,2,3;!;sx=24,pal=50,1d"; + +// WLED-SR effects + +///////////////////////// +// Perlin Move // +///////////////////////// +// 16 bit perlinmove. Use Perlin Noise instead of sinewaves for movement. By Andrew Tuline. +// Controls are speed, # of pixels, faderate. +uint16_t mode_perlinmove(void) { + + SEGMENT.fade_out(255-SEGMENT.custom1); + for (int i = 0; i < SEGMENT.intensity/16 + 1; i++) { + uint16_t locn = inoise16(millis()*128/(260-SEGMENT.speed)+i*15000, millis()*128/(260-SEGMENT.speed)); // Get a new pixel location from moving noise. + uint16_t pixloc = map(locn, 50*256, 192*256, 0, SEGLEN-1); // Map that to the length of the strand, and ensure we don't go over. + SEGMENT.setPixelColor(pixloc, SEGMENT.color_from_palette(pixloc%255, false, PALETTE_SOLID_WRAP, 0)); + } + + return FRAMETIME; +} // mode_perlinmove() +static const char _data_FX_MODE_PERLINMOVE[] PROGMEM = "Perlin Move@!,# of pixels,fade rate;!,!;!;1d"; + + +///////////////////////// +// Waveins // +///////////////////////// +// Uses beatsin8() + phase shifting. By: Andrew Tuline +uint16_t mode_wavesins(void) { + + for (int i = 0; i < SEGLEN; i++) { + uint8_t bri = sin8(millis()/4 + i * SEGMENT.intensity); + uint8_t index = beatsin8(SEGMENT.speed, SEGMENT.custom1, SEGMENT.custom1+SEGMENT.custom2, 0, i * (SEGMENT.custom3<<3)); // custom3 is reduced resolution slider + //SEGMENT.setPixelColor(i, ColorFromPalette(SEGPALETTE, index, bri, LINEARBLEND)); + SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(index, false, PALETTE_SOLID_WRAP, 0, bri)); + } + + return FRAMETIME; +} // mode_waveins() +static const char _data_FX_MODE_WAVESINS[] PROGMEM = "Wavesins@!,Brightness variation,Starting color,Range of colors,Color variation;!;!;1d"; + + +////////////////////////////// +// Flow Stripe // +////////////////////////////// +// By: ldirko https://editor.soulmatelights.com/gallery/392-flow-led-stripe , modifed by: Andrew Tuline +uint16_t mode_FlowStripe(void) { + + const uint16_t hl = SEGLEN * 10 / 13; + uint8_t hue = millis() / (SEGMENT.speed+1); + uint32_t t = millis() / (SEGMENT.intensity/8+1); + + for (int i = 0; i < SEGLEN; i++) { + int c = (abs(i - hl) / hl) * 127; + c = sin8(c); + c = sin8(c / 2 + t); + byte b = sin8(c + t/8); + SEGMENT.setPixelColor(i, CHSV(b + hue, 255, 255)); + } + + return FRAMETIME; +} // mode_FlowStripe() +static const char _data_FX_MODE_FLOWSTRIPE[] PROGMEM = "Flow Stripe@Hue speed,Effect speed;;;1d"; + + +#ifndef WLED_DISABLE_2D +/////////////////////////////////////////////////////////////////////////////// +//*************************** 2D routines *********************************** +#define XY(x,y) SEGMENT.XY(x,y) + + +// Black hole +uint16_t mode_2DBlackHole(void) { // By: Stepko https://editor.soulmatelights.com/gallery/1012 , Modified by: Andrew Tuline + if (!strip.isMatrix) return mode_static(); // not a 2D set-up + + const uint16_t cols = SEGMENT.virtualWidth(); + const uint16_t rows = SEGMENT.virtualHeight(); + uint16_t x, y; + + // initialize on first call + if (SEGENV.call == 0) { + SEGMENT.setUpLeds(); + SEGMENT.fill(BLACK); + } + + SEGMENT.fadeToBlackBy(16 + (SEGMENT.speed>>3)); // create fading trails + float t = (float)(millis())/128; // timebase + // outer stars + for (size_t i = 0; i < 8; i++) { + x = beatsin8(SEGMENT.custom1>>3, 0, cols - 1, 0, ((i % 2) ? 128 : 0) + t * i); + y = beatsin8(SEGMENT.intensity>>3, 0, rows - 1, 0, ((i % 2) ? 192 : 64) + t * i); + SEGMENT.addPixelColorXY(x, y, CHSV(i*32, 255, 255)); + } + // inner stars + for (size_t i = 0; i < 4; i++) { + x = beatsin8(SEGMENT.custom2>>3, cols/4, cols - 1 - cols/4, 0, ((i % 2) ? 128 : 0) + t * i); + y = beatsin8(SEGMENT.custom3 , rows/4, rows - 1 - rows/4, 0, ((i % 2) ? 192 : 64) + t * i); + SEGMENT.addPixelColorXY(x, y, CHSV(i*32, 255, 255)); + } + // central white dot + SEGMENT.setPixelColorXY(cols/2, rows/2, CHSV(0, 0, 255)); + // blur everything a bit + SEGMENT.blur(16); + + return FRAMETIME; +} // mode_2DBlackHole() +static const char _data_FX_MODE_2DBLACKHOLE[] PROGMEM = "Black Hole@Fade rate,Outer Y freq.,Outer X freq.,Inner X freq.,Inner Y freq.;;;2d"; + + +//////////////////////////// +// 2D Colored Bursts // +//////////////////////////// +uint16_t mode_2DColoredBursts() { // By: ldirko https://editor.soulmatelights.com/gallery/819-colored-bursts , modified by: Andrew Tuline + if (!strip.isMatrix) return mode_static(); // not a 2D set-up + + const uint16_t cols = SEGMENT.virtualWidth(); + const uint16_t rows = SEGMENT.virtualHeight(); + + if (SEGENV.call == 0) { + SEGMENT.setUpLeds(); + SEGMENT.fill(BLACK); + SEGENV.aux0 = 0; // start with red hue + } + + bool dot = false; + bool grad = true; + + byte numLines = SEGMENT.intensity/16 + 1; + + SEGENV.aux0++; // hue + SEGMENT.fadeToBlackBy(40); + for (size_t i = 0; i < numLines; i++) { + byte x1 = beatsin8(2 + SEGMENT.speed/16, 0, (cols - 1)); + byte x2 = beatsin8(1 + SEGMENT.speed/16, 0, (cols - 1)); + byte y1 = beatsin8(5 + SEGMENT.speed/16, 0, (rows - 1), 0, i * 24); + byte y2 = beatsin8(3 + SEGMENT.speed/16, 0, (rows - 1), 0, i * 48 + 64); + CRGB color = ColorFromPalette(SEGPALETTE, i * 255 / numLines + (SEGENV.aux0&0xFF), 255, LINEARBLEND); + + byte xsteps = abs8(x1 - y1) + 1; + byte ysteps = abs8(x2 - y2) + 1; + byte steps = xsteps >= ysteps ? xsteps : ysteps; + + for (size_t i = 1; i <= steps; i++) { + byte dx = lerp8by8(x1, y1, i * 255 / steps); + byte dy = lerp8by8(x2, y2, i * 255 / steps); + SEGMENT.addPixelColorXY(dx, dy, color); // use setPixelColorXY for different look + if (grad) SEGMENT.fadePixelColorXY(dx, dy, (i * 255 / steps)); //Draw gradient line + } + + if (dot) { //add white point at the ends of line + SEGMENT.addPixelColorXY(x1, x2, WHITE); + SEGMENT.addPixelColorXY(y1, y2, WHITE); + } + } + SEGMENT.blur(4); + + return FRAMETIME; +} // mode_2DColoredBursts() +static const char _data_FX_MODE_2DCOLOREDBURSTS[] PROGMEM = "Colored Bursts@Speed,# of lines;;!;2d"; + + +///////////////////// +// 2D DNA // +///////////////////// +uint16_t mode_2Ddna(void) { // dna originally by by ldirko at https://pastebin.com/pCkkkzcs. Updated by Preyy. WLED conversion by Andrew Tuline. + if (!strip.isMatrix) return mode_static(); // not a 2D set-up + + const uint16_t cols = SEGMENT.virtualWidth(); + const uint16_t rows = SEGMENT.virtualHeight(); + + if (SEGENV.call == 0) { + SEGMENT.setUpLeds(); + SEGMENT.fill(BLACK); + } + + SEGMENT.fadeToBlackBy(64); + + for (int i = 0; i < cols; i++) { + SEGMENT.setPixelColorXY(i, beatsin8(SEGMENT.speed/8, 0, rows-1, 0, i*4 ), ColorFromPalette(SEGPALETTE, i*5+millis()/17, beatsin8(5, 55, 255, 0, i*10), LINEARBLEND)); + SEGMENT.setPixelColorXY(i, beatsin8(SEGMENT.speed/8, 0, rows-1, 0, i*4+128), ColorFromPalette(SEGPALETTE, i*5+128+millis()/17, beatsin8(5, 55, 255, 0, i*10+128), LINEARBLEND)); + } + SEGMENT.blur(SEGMENT.intensity>>3); + + return FRAMETIME; +} // mode_2Ddna() +static const char _data_FX_MODE_2DDNA[] PROGMEM = "DNA@Scroll speed,Blur;1,2,3;!;2d"; + + +///////////////////////// +// 2D DNA Spiral // +///////////////////////// +uint16_t mode_2DDNASpiral() { // By: ldirko https://editor.soulmatelights.com/gallery/810 , modified by: Andrew Tuline + if (!strip.isMatrix) return mode_static(); // not a 2D set-up + + const uint16_t cols = SEGMENT.virtualWidth(); + const uint16_t rows = SEGMENT.virtualHeight(); + + if (SEGENV.call == 0) { + SEGMENT.setUpLeds(); + SEGMENT.fill(BLACK); + SEGENV.aux0 = 0; // hue + } + + uint8_t speeds = SEGMENT.speed/2; + uint8_t freq = SEGMENT.intensity/8; + + uint32_t ms = millis() / 20; + SEGMENT.fadeToBlackBy(135); + + for (int i = 0; i < rows; i++) { + uint16_t x = beatsin8(speeds, 0, cols - 1, 0, i * freq) + beatsin8(speeds - 7, 0, cols - 1, 0, i * freq + 128); + uint16_t x1 = beatsin8(speeds, 0, cols - 1, 0, 128 + i * freq) + beatsin8(speeds - 7, 0, cols - 1, 0, 128 + 64 + i * freq); + SEGENV.aux0 = i * 128 / cols + ms; //ewowi20210629: not width - 1 to avoid crash if width = 1 + if ((i + ms / 8) & 3) { + x = x / 2; x1 = x1 / 2; + byte steps = abs8(x - x1) + 1; + for (size_t k = 1; k <= steps; k++) { + byte dx = lerp8by8(x, x1, k * 255 / steps); + SEGMENT.addPixelColorXY(dx, i, ColorFromPalette(SEGPALETTE, SEGENV.aux0, 255, LINEARBLEND)); + SEGMENT.fadePixelColorXY(dx, i, (k * 255 / steps)); + } + SEGMENT.addPixelColorXY(x, i, DARKSLATEGRAY); + SEGMENT.addPixelColorXY(x1, i, WHITE); + } + } + + return FRAMETIME; +} // mode_2DDNASpiral() +static const char _data_FX_MODE_2DDNASPIRAL[] PROGMEM = "DNA Spiral@Scroll speed,Y frequency;;!;2d"; + + +///////////////////////// +// 2D Drift // +///////////////////////// +uint16_t mode_2DDrift() { // By: Stepko https://editor.soulmatelights.com/gallery/884-drift , Modified by: Andrew Tuline + if (!strip.isMatrix) return mode_static(); // not a 2D set-up + + const uint16_t cols = SEGMENT.virtualWidth(); + const uint16_t rows = SEGMENT.virtualHeight(); + + if (SEGENV.call == 0) { + SEGMENT.setUpLeds(); + SEGMENT.fill(BLACK); + } + + SEGMENT.fadeToBlackBy(128); + + const uint16_t maxDim = MAX(cols, rows)/2; + unsigned long t = millis() / (32 - (SEGMENT.speed>>3)); + for (float i = 1; i < maxDim; i += 0.25) { + float angle = radians(t * (maxDim - i)); + uint16_t myX = (cols>>1) + (uint16_t)(sin_t(angle) * i) + (cols%2); + uint16_t myY = (rows>>1) + (uint16_t)(cos_t(angle) * i) + (rows%2); + SEGMENT.setPixelColorXY(myX, myY, ColorFromPalette(SEGPALETTE, (i * 20) + (t / 20), 255, LINEARBLEND)); + } + SEGMENT.blur(SEGMENT.intensity>>3); + + return FRAMETIME; +} // mode_2DDrift() +static const char _data_FX_MODE_2DDRIFT[] PROGMEM = "Drift@Rotation speed,Blur amount;;!;2d"; + + +////////////////////////// +// 2D Firenoise // +////////////////////////// +uint16_t mode_2Dfirenoise(void) { // firenoise2d. By Andrew Tuline. Yet another short routine. + if (!strip.isMatrix) return mode_static(); // not a 2D set-up + + const uint16_t cols = SEGMENT.virtualWidth(); + const uint16_t rows = SEGMENT.virtualHeight(); + + if (SEGENV.call == 0) { + SEGMENT.setUpLeds(); + SEGMENT.fill(BLACK); + } + + uint16_t xscale = SEGMENT.intensity*4; + uint32_t yscale = SEGMENT.speed*8; + uint8_t indexx = 0; + + SEGPALETTE = CRGBPalette16( CRGB(0,0,0), CRGB(0,0,0), CRGB(0,0,0), CRGB(0,0,0), + CRGB::Red, CRGB::Red, CRGB::Red, CRGB::DarkOrange, + CRGB::DarkOrange,CRGB::DarkOrange, CRGB::Orange, CRGB::Orange, + CRGB::Yellow, CRGB::Orange, CRGB::Yellow, CRGB::Yellow); + + for (int j=0; j < cols; j++) { + for (int i=0; i < rows; i++) { + indexx = inoise8(j*yscale*rows/255, i*xscale+millis()/4); // We're moving along our Perlin map. + SEGMENT.setPixelColorXY(j, i, ColorFromPalette(SEGPALETTE, min(i*(indexx)>>4, 255), i*255/cols, LINEARBLEND)); // With that value, look up the 8 bit colour palette value and assign it to the current LED. + } // for i + } // for j + + return FRAMETIME; +} // mode_2Dfirenoise() +static const char _data_FX_MODE_2DFIRENOISE[] PROGMEM = "Firenoise@X scale,Y scale;;;2d"; + + +////////////////////////////// +// 2D Frizzles // +////////////////////////////// +uint16_t mode_2DFrizzles(void) { // By: Stepko https://editor.soulmatelights.com/gallery/640-color-frizzles , Modified by: Andrew Tuline + if (!strip.isMatrix) return mode_static(); // not a 2D set-up + + const uint16_t cols = SEGMENT.virtualWidth(); + const uint16_t rows = SEGMENT.virtualHeight(); + + if (SEGENV.call == 0) { + SEGMENT.setUpLeds(); + SEGMENT.fill(BLACK); + } + + SEGMENT.fadeToBlackBy(16); + for (size_t i = 8; i > 0; i--) { + SEGMENT.addPixelColorXY(beatsin8(SEGMENT.speed/8 + i, 0, cols - 1), + beatsin8(SEGMENT.intensity/8 - i, 0, rows - 1), + ColorFromPalette(SEGPALETTE, beatsin8(12, 0, 255), 255, LINEARBLEND)); + } + SEGMENT.blur(SEGMENT.custom1>>3); + + return FRAMETIME; +} // mode_2DFrizzles() +static const char _data_FX_MODE_2DFRIZZLES[] PROGMEM = "Frizzles@X frequency,Y frequency,Blur;;!;2d"; + + +/////////////////////////////////////////// +// 2D Cellular Automata Game of life // +/////////////////////////////////////////// +typedef struct ColorCount { + CRGB color; + int8_t count; +} colorCount; + +uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https://natureofcode.com/book/chapter-7-cellular-automata/ and https://github.com/DougHaber/nlife-color + if (!strip.isMatrix) return mode_static(); // not a 2D set-up + + const uint16_t cols = SEGMENT.virtualWidth(); + const uint16_t rows = SEGMENT.virtualHeight(); + const uint16_t dataSize = sizeof(CRGB) * SEGMENT.length(); // using width*height prevents reallocation if mirroring is enabled + + if (!SEGENV.allocateData(dataSize + sizeof(unsigned long))) return mode_static(); //allocation failed + CRGB *prevLeds = reinterpret_cast(SEGENV.data); + unsigned long *resetMillis = reinterpret_cast(SEGENV.data + dataSize); // triggers reset + + CRGB backgroundColor = SEGCOLOR(1); + + if (SEGENV.call == 0 || strip.now - *resetMillis > 5000) { + *resetMillis = strip.now; + + random16_set_seed(strip.now); //seed the random generator + + //give the leds random state and colors (based on intensity, colors from palette or all posible colors are chosen) + for (int x = 0; x < cols; x++) for (int y = 0; y < rows; y++) { + uint8_t state = random8()%2; + if (state == 0) + SEGMENT.setPixelColorXY(x,y, backgroundColor); + else + SEGMENT.setPixelColorXY(x,y, SEGMENT.color_from_palette(random8(), false, PALETTE_SOLID_WRAP, 0)); + } + + for (int y = 0; y < rows; y++) for (int x = 0; x < cols; x++) prevLeds[XY(x,y)] = CRGB::Black; + + + SEGENV.aux1 = 0; + SEGENV.aux0 = 0xFFFF; + } + + //copy previous leds (save previous generation) + for (int x = 0; x < cols; x++) for (int y = 0; y < rows; y++) prevLeds[XY(x,y)] = SEGMENT.getPixelColorXY(x,y); + + //calculate new leds + for (int x = 0; x < cols; x++) for (int y = 0; y < rows; y++) { + colorCount colorsCount[9];//count the different colors in the 9*9 matrix + for (int i=0; i<9; i++) colorsCount[i] = {backgroundColor, 0}; //init colorsCount + + //iterate through neighbors and count them and their different colors + int neighbors = 0; + for (int i = -1; i <= 1; i++) for (int j = -1; j <= 1; j++) { //iterate through 9*9 matrix + // wrap around segment + int16_t xx = x+i, yy = y+j; + if (x+i < 0) xx = cols-1; else if (x+i >= cols) xx = 0; + if (y+j < 0) yy = rows-1; else if (y+j >= rows) yy = 0; + uint16_t xy = XY(xx, yy); // previous cell xy to check + + // count different neighbours and colors, except the centre cell + if (xy != XY(x,y) && prevLeds[xy] != backgroundColor) { + neighbors++; + bool colorFound = false; + int k; + for (k=0; k<9 && colorsCount[i].count != 0; k++) + if (colorsCount[k].color == prevLeds[xy]) { + colorsCount[k].count++; + colorFound = true; + } + + if (!colorFound) colorsCount[k] = {prevLeds[xy], 1}; //add new color found in the array + } + } // i,j + + // Rules of Life + uint32_t col = SEGMENT.getPixelColorXY(x,y); + uint32_t bgc = RGBW32(backgroundColor.r, backgroundColor.g, backgroundColor.b, 0); + if ((col != bgc) && (neighbors < 2)) SEGMENT.setPixelColorXY(x,y, bgc); // Loneliness + else if ((col != bgc) && (neighbors > 3)) SEGMENT.setPixelColorXY(x,y, bgc); // Overpopulation + else if ((col == bgc) && (neighbors == 3)) { // Reproduction + //find dominantcolor and assign to cell + colorCount dominantColorCount = {backgroundColor, 0}; + for (int i=0; i<9 && colorsCount[i].count != 0; i++) + if (colorsCount[i].count > dominantColorCount.count) dominantColorCount = colorsCount[i]; + if (dominantColorCount.count > 0) SEGMENT.setPixelColorXY(x,y, dominantColorCount.color); //assign the dominant color + } + // else do nothing! + } //x,y + + // calculate CRC16 of leds + uint16_t crc = crc16((const unsigned char*)prevLeds, dataSize-1); //ewowi: prevLeds instead of leds work as well, tbd: compare more patterns, see SR! + + // check if we had same CRC and reset if needed + // same CRC would mean image did not change or was repeating itself + if (!(crc == SEGENV.aux0 || crc == SEGENV.aux1)) *resetMillis = strip.now; //if no repetition avoid reset + // remember last two + SEGENV.aux1 = SEGENV.aux0; + SEGENV.aux0 = crc; + + return FRAMETIME_FIXED * (128-(SEGMENT.speed>>1)); // update only when appropriate time passes (in 42 FPS slots) +} // mode_2Dgameoflife() +static const char _data_FX_MODE_2DGAMEOFLIFE[] PROGMEM = "Game Of Life@!,;!,!;!;2d"; + + +///////////////////////// +// 2D Hiphotic // +///////////////////////// +uint16_t mode_2DHiphotic() { // By: ldirko https://editor.soulmatelights.com/gallery/810 , Modified by: Andrew Tuline + if (!strip.isMatrix) return mode_static(); // not a 2D set-up + + const uint16_t cols = SEGMENT.virtualWidth(); + const uint16_t rows = SEGMENT.virtualHeight(); + const uint32_t a = strip.now / ((SEGMENT.custom3>>1)+1); + + for (int x = 0; x < cols; x++) { + for (int y = 0; y < rows; y++) { + SEGMENT.setPixelColorXY(x, y, SEGMENT.color_from_palette(sin8(cos8(x * SEGMENT.speed/16 + a / 3) + sin8(y * SEGMENT.intensity/16 + a / 4) + a), false, PALETTE_SOLID_WRAP, 0)); + } + } + + return FRAMETIME; +} // mode_2DHiphotic() +static const char _data_FX_MODE_2DHIPHOTIC[] PROGMEM = "Hiphotic@X scale,Y scale,,,Speed;;!;2d"; + + +///////////////////////// +// 2D Julia // +///////////////////////// +// Sliders are: +// intensity = Maximum number of iterations per pixel. +// Custom1 = Location of X centerpoint +// Custom2 = Location of Y centerpoint +// Custom3 = Size of the area (small value = smaller area) +typedef struct Julia { + float xcen; + float ycen; + float xymag; +} julia; + +uint16_t mode_2DJulia(void) { // An animated Julia set by Andrew Tuline. + if (!strip.isMatrix) return mode_static(); // not a 2D set-up + + const uint16_t cols = SEGMENT.virtualWidth(); + const uint16_t rows = SEGMENT.virtualHeight(); + + if (!SEGENV.allocateData(sizeof(julia))) return mode_static(); + Julia* julias = reinterpret_cast(SEGENV.data); + + float reAl; + float imAg; + + if (SEGENV.call == 0) { // Reset the center if we've just re-started this animation. + julias->xcen = 0.; + julias->ycen = 0.; + julias->xymag = 1.0; + + SEGMENT.custom1 = 128; // Make sure the location widgets are centered to start. + SEGMENT.custom2 = 128; + SEGMENT.custom3 = 16; + SEGMENT.intensity = 24; + } + + julias->xcen = julias->xcen + (float)(SEGMENT.custom1 - 128)/100000.f; + julias->ycen = julias->ycen + (float)(SEGMENT.custom2 - 128)/100000.f; + julias->xymag = julias->xymag + (float)((SEGMENT.custom3 - 16)<<3)/100000.f; // reduced resolution slider + if (julias->xymag < 0.01f) julias->xymag = 0.01f; + if (julias->xymag > 1.0f) julias->xymag = 1.0f; + + float xmin = julias->xcen - julias->xymag; + float xmax = julias->xcen + julias->xymag; + float ymin = julias->ycen - julias->xymag; + float ymax = julias->ycen + julias->xymag; + + // Whole set should be within -1.2,1.2 to -.8 to 1. + xmin = constrain(xmin, -1.2f, 1.2f); + xmax = constrain(xmax, -1.2f, 1.2f); + ymin = constrain(ymin, -0.8f, 1.0f); + ymax = constrain(ymax, -0.8f, 1.0f); + + float dx; // Delta x is mapped to the matrix size. + float dy; // Delta y is mapped to the matrix size. + + int maxIterations = 15; // How many iterations per pixel before we give up. Make it 8 bits to match our range of colours. + float maxCalc = 16.0; // How big is each calculation allowed to be before we give up. + + maxIterations = SEGMENT.intensity/2; + + + // Resize section on the fly for some animaton. + reAl = -0.94299f; // PixelBlaze example + imAg = 0.3162f; + + reAl += sin_t((float)millis()/305.f)/20.f; + imAg += sin_t((float)millis()/405.f)/20.f; + + dx = (xmax - xmin) / (cols); // Scale the delta x and y values to our matrix size. + dy = (ymax - ymin) / (rows); + + // Start y + float y = ymin; + for (int j = 0; j < rows; j++) { + + // Start x + float x = xmin; + for (int i = 0; i < cols; i++) { + + // Now we test, as we iterate z = z^2 + c does z tend towards infinity? + float a = x; + float b = y; + int iter = 0; + + while (iter < maxIterations) { // Here we determine whether or not we're out of bounds. + float aa = a * a; + float bb = b * b; + float len = aa + bb; + if (len > maxCalc) { // |z| = sqrt(a^2+b^2) OR z^2 = a^2+b^2 to save on having to perform a square root. + break; // Bail + } + + // This operation corresponds to z -> z^2+c where z=a+ib c=(x,y). Remember to use 'foil'. + b = 2*a*b + imAg; + a = aa - bb + reAl; + iter++; + } // while + + // We color each pixel based on how long it takes to get to infinity, or black if it never gets there. + if (iter == maxIterations) { + SEGMENT.setPixelColorXY(i, j, 0); + } else { + SEGMENT.setPixelColorXY(i, j, SEGMENT.color_from_palette(iter*255/maxIterations, false, PALETTE_SOLID_WRAP, 0)); + } + x += dx; + } + y += dy; + } +// SEGMENT.blur(64); + + return FRAMETIME; +} // mode_2DJulia() +static const char _data_FX_MODE_2DJULIA[] PROGMEM = "Julia@,Max iterations per pixel,X center,Y center,Area size;;!;ix=24,c1=128,c2=128,c3=16,2d"; + + +////////////////////////////// +// 2D Lissajous // +////////////////////////////// +uint16_t mode_2DLissajous(void) { // By: Andrew Tuline + if (!strip.isMatrix) return mode_static(); // not a 2D set-up + + const uint16_t cols = SEGMENT.virtualWidth(); + const uint16_t rows = SEGMENT.virtualHeight(); + + SEGMENT.fadeToBlackBy(SEGMENT.intensity); + + //for (int i=0; i < 4*(cols+rows); i ++) { + for (int i=0; i < 256; i ++) { + //float xlocn = float(sin8(now/4+i*(SEGMENT.speed>>5))) / 255.0f; + //float ylocn = float(cos8(now/4+i*2)) / 255.0f; + uint8_t xlocn = sin8(strip.now/2+i*(SEGMENT.speed>>5)); + uint8_t ylocn = cos8(strip.now/2+i*2); + xlocn = map(xlocn,0,255,0,cols-1); + ylocn = map(ylocn,0,255,0,rows-1); + SEGMENT.setPixelColorXY(xlocn, ylocn, SEGMENT.color_from_palette(strip.now/100+i, false, PALETTE_SOLID_WRAP, 0)); + } + + return FRAMETIME; +} // mode_2DLissajous() +static const char _data_FX_MODE_2DLISSAJOUS[] PROGMEM = "Lissajous@X frequency,Fade rate;!,!,!;!;2d"; + + +/////////////////////// +// 2D Matrix // +/////////////////////// +uint16_t mode_2Dmatrix(void) { // Matrix2D. By Jeremy Williams. Adapted by Andrew Tuline & improved by merkisoft and ewowi. + if (!strip.isMatrix) return mode_static(); // not a 2D set-up + + const uint16_t cols = SEGMENT.virtualWidth(); + const uint16_t rows = SEGMENT.virtualHeight(); + + if (SEGENV.call == 0) { + SEGMENT.setUpLeds(); + SEGMENT.fill(BLACK); + } + + uint8_t fade = map(SEGMENT.custom1, 0, 255, 50, 250); // equals trail size + uint8_t speed = (256-SEGMENT.speed) >> map(MIN(rows, 150), 0, 150, 0, 3); // slower speeds for small displays + + CRGB spawnColor; + CRGB trailColor; + if (SEGMENT.check1) { + spawnColor = SEGCOLOR(0); + trailColor = SEGCOLOR(1); + } else { + spawnColor = CRGB(175,255,175); + trailColor = CRGB(27,130,39); + } + + if (strip.now - SEGENV.step >= speed) { + SEGENV.step = strip.now; + for (int row=rows-1; row>=0; row--) { + for (int col=0; col>6)); + + // get some 2 random moving points + uint8_t x2 = inoise8(strip.now * speed, 25355, 685 ) / 16; + uint8_t y2 = inoise8(strip.now * speed, 355, 11685 ) / 16; + + uint8_t x3 = inoise8(strip.now * speed, 55355, 6685 ) / 16; + uint8_t y3 = inoise8(strip.now * speed, 25355, 22685 ) / 16; + + // and one Lissajou function + uint8_t x1 = beatsin8(23 * speed, 0, 15); + uint8_t y1 = beatsin8(28 * speed, 0, 15); + + for (int y = 0; y < rows; y++) { + for (int x = 0; x < cols; x++) { + // calculate distances of the 3 points from actual pixel + // and add them together with weightening + uint16_t dx = abs(x - x1); + uint16_t dy = abs(y - y1); + uint16_t dist = 2 * sqrt16((dx * dx) + (dy * dy)); + + dx = abs(x - x2); + dy = abs(y - y2); + dist += sqrt16((dx * dx) + (dy * dy)); + + dx = abs(x - x3); + dy = abs(y - y3); + dist += sqrt16((dx * dx) + (dy * dy)); + + // inverse result + byte color = 1000 / dist; + + // map color between thresholds + if (color > 0 and color < 60) { + SEGMENT.setPixelColorXY(x, y, SEGMENT.color_from_palette(map(color * 9, 9, 531, 0, 255), false, PALETTE_SOLID_WRAP, 0)); + } else { + SEGMENT.setPixelColorXY(x, y, SEGMENT.color_from_palette(0, false, PALETTE_SOLID_WRAP, 0)); + } + // show the 3 points, too + SEGMENT.setPixelColorXY(x1, y1, WHITE); + SEGMENT.setPixelColorXY(x2, y2, WHITE); + SEGMENT.setPixelColorXY(x3, y3, WHITE); + } + } + + return FRAMETIME; +} // mode_2Dmetaballs() +static const char _data_FX_MODE_2DMETABALLS[] PROGMEM = "Metaballs@Speed;!,!,!;!;2d"; + + +////////////////////// +// 2D Noise // +////////////////////// +uint16_t mode_2Dnoise(void) { // By Andrew Tuline + if (!strip.isMatrix) return mode_static(); // not a 2D set-up + + const uint16_t cols = SEGMENT.virtualWidth(); + const uint16_t rows = SEGMENT.virtualHeight(); + + const uint16_t scale = SEGMENT.intensity+2; + + for (int y = 0; y < rows; y++) { + for (int x = 0; x < cols; x++) { + uint8_t pixelHue8 = inoise8(x * scale, y * scale, millis() / (16 - SEGMENT.speed/16)); + SEGMENT.setPixelColorXY(x, y, ColorFromPalette(SEGPALETTE, pixelHue8)); + } + } + + return FRAMETIME; +} // mode_2Dnoise() +static const char _data_FX_MODE_2DNOISE[] PROGMEM = "Noise2D@Speed,Scale;!,!,!;!;2d"; + + +////////////////////////////// +// 2D Plasma Ball // +////////////////////////////// +uint16_t mode_2DPlasmaball(void) { // By: Stepko https://editor.soulmatelights.com/gallery/659-plasm-ball , Modified by: Andrew Tuline + if (!strip.isMatrix) return mode_static(); // not a 2D set-up + + const uint16_t cols = SEGMENT.virtualWidth(); + const uint16_t rows = SEGMENT.virtualHeight(); + + if (SEGENV.call == 0) { + SEGMENT.setUpLeds(); + SEGMENT.fill(BLACK); + } + + SEGMENT.fadeToBlackBy(SEGMENT.custom1>>2); + + float t = millis() / (33 - SEGMENT.speed/8); + for (int i = 0; i < cols; i++) { + uint16_t thisVal = inoise8(i * 30, t, t); + uint16_t thisMax = map(thisVal, 0, 255, 0, cols-1); + for (int j = 0; j < rows; j++) { + uint16_t thisVal_ = inoise8(t, j * 30, t); + uint16_t thisMax_ = map(thisVal_, 0, 255, 0, rows-1); + uint16_t x = (i + thisMax_ - cols / 2); + uint16_t y = (j + thisMax - cols / 2); + uint16_t cx = (i + thisMax_); + uint16_t cy = (j + thisMax); + + SEGMENT.addPixelColorXY(i, j, ((x - y > -2) && (x - y < 2)) || + ((cols - 1 - x - y) > -2 && (cols - 1 - x - y < 2)) || + (cols - cx == 0) || + (cols - 1 - cx == 0) || + ((rows - cy == 0) || + (rows - 1 - cy == 0)) ? ColorFromPalette(SEGPALETTE, beat8(5), thisVal, LINEARBLEND) : CRGB::Black); + } + } + SEGMENT.blur(SEGMENT.custom2>>5); + + return FRAMETIME; +} // mode_2DPlasmaball() +static const char _data_FX_MODE_2DPLASMABALL[] PROGMEM = "Plasma Ball@Speed,,Fade,Blur;!,!,!;!;2d"; + + +//////////////////////////////// +// 2D Polar Lights // +//////////////////////////////// +//static float fmap(const float x, const float in_min, const float in_max, const float out_min, const float out_max) { +// return (out_max - out_min) * (x - in_min) / (in_max - in_min) + out_min; +//} +uint16_t mode_2DPolarLights(void) { // By: Kostyantyn Matviyevskyy https://editor.soulmatelights.com/gallery/762-polar-lights , Modified by: Andrew Tuline + if (!strip.isMatrix) return mode_static(); // not a 2D set-up + + const uint16_t cols = SEGMENT.virtualWidth(); + const uint16_t rows = SEGMENT.virtualHeight(); + + CRGBPalette16 auroraPalette = {0x000000, 0x003300, 0x006600, 0x009900, 0x00cc00, 0x00ff00, 0x33ff00, 0x66ff00, 0x99ff00, 0xccff00, 0xffff00, 0xffcc00, 0xff9900, 0xff6600, 0xff3300, 0xff0000}; + + if (SEGENV.call == 0) { + SEGMENT.setUpLeds(); + SEGMENT.fill(BLACK); + SEGENV.step = 0; + } + + float adjustHeight = (float)map(rows, 8, 32, 28, 12); + uint16_t adjScale = map(cols, 8, 64, 310, 63); +/* + if (SEGENV.aux1 != SEGMENT.custom1/12) { // Hacky palette rotation. We need that black. + SEGENV.aux1 = SEGMENT.custom1/12; + for (int i = 0; i < 16; i++) { + long ilk; + ilk = (long)currentPalette[i].r << 16; + ilk += (long)currentPalette[i].g << 8; + ilk += (long)currentPalette[i].b; + ilk = (ilk << SEGENV.aux1) | (ilk >> (24 - SEGENV.aux1)); + currentPalette[i].r = ilk >> 16; + currentPalette[i].g = ilk >> 8; + currentPalette[i].b = ilk; + } + } +*/ + uint16_t _scale = map(SEGMENT.intensity, 0, 255, 30, adjScale); + byte _speed = map(SEGMENT.speed, 0, 255, 128, 16); + + for (int x = 0; x < cols; x++) { + for (int y = 0; y < rows; y++) { + SEGENV.step++; + SEGMENT.setPixelColorXY(x, y, ColorFromPalette(auroraPalette, + qsub8( + inoise8((SEGENV.step%2) + x * _scale, y * 16 + SEGENV.step % 16, SEGENV.step / _speed), + fabs((float)rows / 2 - (float)y) * adjustHeight))); + } + } + + return FRAMETIME; +} // mode_2DPolarLights() +static const char _data_FX_MODE_2DPOLARLIGHTS[] PROGMEM = "Polar Lights@Speed,Scale;;;2d"; + + +///////////////////////// +// 2D Pulser // +///////////////////////// +uint16_t mode_2DPulser(void) { // By: ldirko https://editor.soulmatelights.com/gallery/878-pulse-test , modifed by: Andrew Tuline + if (!strip.isMatrix) return mode_static(); // not a 2D set-up + + //const uint16_t cols = SEGMENT.virtualWidth(); + const uint16_t rows = SEGMENT.virtualHeight(); + + if (SEGENV.call == 0) { + SEGMENT.setUpLeds(); + SEGMENT.fill(BLACK); + } + + SEGMENT.fadeToBlackBy(8 - (SEGMENT.intensity>>5)); + + uint16_t a = strip.now / (18 - SEGMENT.speed / 16); + uint16_t x = (a / 14); + uint16_t y = map((sin8(a * 5) + sin8(a * 4) + sin8(a * 2)), 0, 765, rows-1, 0); + SEGMENT.setPixelColorXY(x, y, ColorFromPalette(SEGPALETTE, map(y, 0, rows-1, 0, 255), 255, LINEARBLEND)); + + SEGMENT.blur(1 + (SEGMENT.intensity>>4)); + + return FRAMETIME; +} // mode_2DPulser() +static const char _data_FX_MODE_2DPULSER[] PROGMEM = "Pulser@Speed,Blur;;!;2d"; + + +///////////////////////// +// 2D Sindots // +///////////////////////// +uint16_t mode_2DSindots(void) { // By: ldirko https://editor.soulmatelights.com/gallery/597-sin-dots , modified by: Andrew Tuline + if (!strip.isMatrix) return mode_static(); // not a 2D set-up + + const uint16_t cols = SEGMENT.virtualWidth(); + const uint16_t rows = SEGMENT.virtualHeight(); + + if (SEGENV.call == 0) { + SEGMENT.setUpLeds(); + SEGMENT.fill(BLACK); + } + + SEGMENT.fadeToBlackBy(SEGMENT.custom1>>3); + + byte t1 = millis() / (257 - SEGMENT.speed); // 20; + byte t2 = sin8(t1) / 4 * 2; + for (int i = 0; i < 13; i++) { + byte x = sin8(t1 + i * SEGMENT.intensity/8)*(cols-1)/255; // max index now 255x15/255=15! + byte y = sin8(t2 + i * SEGMENT.intensity/8)*(rows-1)/255; // max index now 255x15/255=15! + SEGMENT.setPixelColorXY(x, y, ColorFromPalette(SEGPALETTE, i * 255 / 13, 255, LINEARBLEND)); + } + SEGMENT.blur(SEGMENT.custom2>>3); + + return FRAMETIME; +} // mode_2DSindots() +static const char _data_FX_MODE_2DSINDOTS[] PROGMEM = "Sindots@Speed,Dot distance,Fade rate,Blur;;!;2d"; + + +////////////////////////////// +// 2D Squared Swirl // +////////////////////////////// +// custom3 affects the blur amount. +uint16_t mode_2Dsquaredswirl(void) { // By: Mark Kriegsman. https://gist.github.com/kriegsman/368b316c55221134b160 + // Modifed by: Andrew Tuline + if (!strip.isMatrix) return mode_static(); // not a 2D set-up + + const uint16_t cols = SEGMENT.virtualWidth(); + const uint16_t rows = SEGMENT.virtualHeight(); + + if (SEGENV.call == 0) { + SEGMENT.setUpLeds(); + SEGMENT.fill(BLACK); + } + + const uint8_t kBorderWidth = 2; + + SEGMENT.fadeToBlackBy(24); + + uint8_t blurAmount = SEGMENT.custom3>>1; // reduced resolution slider + SEGMENT.blur(blurAmount); + + // Use two out-of-sync sine waves + uint8_t i = beatsin8(19, kBorderWidth, cols-kBorderWidth); + uint8_t j = beatsin8(22, kBorderWidth, cols-kBorderWidth); + uint8_t k = beatsin8(17, kBorderWidth, cols-kBorderWidth); + uint8_t m = beatsin8(18, kBorderWidth, rows-kBorderWidth); + uint8_t n = beatsin8(15, kBorderWidth, rows-kBorderWidth); + uint8_t p = beatsin8(20, kBorderWidth, rows-kBorderWidth); + + uint16_t ms = millis(); + + SEGMENT.addPixelColorXY(i, m, ColorFromPalette(SEGPALETTE, ms/29, 255, LINEARBLEND)); + SEGMENT.addPixelColorXY(j, n, ColorFromPalette(SEGPALETTE, ms/41, 255, LINEARBLEND)); + SEGMENT.addPixelColorXY(k, p, ColorFromPalette(SEGPALETTE, ms/73, 255, LINEARBLEND)); + + return FRAMETIME; +} // mode_2Dsquaredswirl() +static const char _data_FX_MODE_2DSQUAREDSWIRL[] PROGMEM = "Squared Swirl@,,,,Blur;,,;!;2d"; + + +////////////////////////////// +// 2D Sun Radiation // +////////////////////////////// +uint16_t mode_2DSunradiation(void) { // By: ldirko https://editor.soulmatelights.com/gallery/599-sun-radiation , modified by: Andrew Tuline + if (!strip.isMatrix) return mode_static(); // not a 2D set-up + + const uint16_t cols = SEGMENT.virtualWidth(); + const uint16_t rows = SEGMENT.virtualHeight(); + + if (!SEGENV.allocateData(sizeof(byte)*(cols+2)*(rows+2))) return mode_static(); //allocation failed + byte *bump = reinterpret_cast(SEGENV.data); + + if (SEGENV.call == 0) { + SEGMENT.setUpLeds(); + SEGMENT.fill(BLACK); + } + + unsigned long t = millis() / 4; + int index = 0; + uint8_t someVal = SEGMENT.speed/4; // Was 25. + for (int j = 0; j < (rows + 2); j++) { + for (int i = 0; i < (cols + 2); i++) { + byte col = (inoise8_raw(i * someVal, j * someVal, t)) / 2; + bump[index++] = col; + } + } + + int yindex = cols + 3; + int16_t vly = -(rows / 2 + 1); + for (int y = 0; y < rows; y++) { + ++vly; + int16_t vlx = -(cols / 2 + 1); + for (int x = 0; x < cols; x++) { + ++vlx; + int8_t nx = bump[x + yindex + 1] - bump[x + yindex - 1]; + int8_t ny = bump[x + yindex + (cols + 2)] - bump[x + yindex - (cols + 2)]; + byte difx = abs8(vlx * 7 - nx); + byte dify = abs8(vly * 7 - ny); + int temp = difx * difx + dify * dify; + int col = 255 - temp / 8; //8 its a size of effect + if (col < 0) col = 0; + SEGMENT.setPixelColorXY(x, y, HeatColor(col / (3.0f-(float)(SEGMENT.intensity)/128.f))); + } + yindex += (cols + 2); + } + + return FRAMETIME; +} // mode_2DSunradiation() +static const char _data_FX_MODE_2DSUNRADIATION[] PROGMEM = "Sun Radiation@Variance,Brightness;;;2d"; + + +///////////////////////// +// 2D Tartan // +///////////////////////// +uint16_t mode_2Dtartan(void) { // By: Elliott Kember https://editor.soulmatelights.com/gallery/3-tartan , Modified by: Andrew Tuline + if (!strip.isMatrix) return mode_static(); // not a 2D set-up + + const uint16_t cols = SEGMENT.virtualWidth(); + const uint16_t rows = SEGMENT.virtualHeight(); + + if (SEGENV.call == 0) { + SEGMENT.setUpLeds(); + SEGMENT.fill(BLACK); + } + + uint8_t hue; + int offsetX = beatsin16(3, -360, 360); + int offsetY = beatsin16(2, -360, 360); + + for (int x = 0; x < cols; x++) { + for (int y = 0; y < rows; y++) { + hue = x * beatsin16(10, 1, 10) + offsetY; + SEGMENT.setPixelColorXY(x, y, ColorFromPalette(SEGPALETTE, hue, sin8(x * SEGMENT.speed + offsetX) * sin8(x * SEGMENT.speed + offsetX) / 255, LINEARBLEND)); + hue = y * 3 + offsetX; + SEGMENT.addPixelColorXY(x, y, ColorFromPalette(SEGPALETTE, hue, sin8(y * SEGMENT.intensity + offsetY) * sin8(y * SEGMENT.intensity + offsetY) / 255, LINEARBLEND)); + } + } + + return FRAMETIME; +} // mode_2DTartan() +static const char _data_FX_MODE_2DTARTAN[] PROGMEM = "Tartan@X scale,Y scale;;!;2d"; + + +///////////////////////// +// 2D spaceships // +///////////////////////// +uint16_t mode_2Dspaceships(void) { //// Space ships by stepko (c)05.02.21 [https://editor.soulmatelights.com/gallery/639-space-ships], adapted by Blaz Kristan (AKA blazoncek) + if (!strip.isMatrix) return mode_static(); // not a 2D set-up + + const uint16_t cols = SEGMENT.virtualWidth(); + const uint16_t rows = SEGMENT.virtualHeight(); + + if (SEGENV.call == 0) { + SEGMENT.setUpLeds(); + SEGMENT.fill(BLACK); + } + + uint32_t tb = strip.now >> 12; // every ~4s + if (tb > SEGENV.step) { + int8_t dir = ++SEGENV.aux0; + dir += (int)random8(3)-1; + if (dir > 7) SEGENV.aux0 = 0; + else if (dir < 0) SEGENV.aux0 = 7; + else SEGENV.aux0 = dir; + SEGENV.step = tb + random8(4); + } + + SEGMENT.fadeToBlackBy(map(SEGMENT.speed, 0, 255, 248, 16)); + SEGMENT.move(SEGENV.aux0, 1); + + for (size_t i = 0; i < 8; i++) { + byte x = beatsin8(12 + i, 2, cols - 3); + byte y = beatsin8(15 + i, 2, rows - 3); + CRGB color = ColorFromPalette(SEGPALETTE, beatsin8(12 + i, 0, 255), 255); + SEGMENT.addPixelColorXY(x, y, color); + if (cols > 24 || rows > 24) { + SEGMENT.addPixelColorXY(x+1, y, color); + SEGMENT.addPixelColorXY(x-1, y, color); + SEGMENT.addPixelColorXY(x, y+1, color); + SEGMENT.addPixelColorXY(x, y-1, color); + } + } + SEGMENT.blur(SEGMENT.intensity>>3); + + return FRAMETIME; +} +static const char _data_FX_MODE_2DSPACESHIPS[] PROGMEM = "Spaceships@!,Blur;!,!,!;!;2d"; + + +///////////////////////// +// 2D Crazy Bees // +///////////////////////// +//// Crazy bees by stepko (c)12.02.21 [https://editor.soulmatelights.com/gallery/651-crazy-bees], adapted by Blaz Kristan (AKA blazoncek) +#define MAX_BEES 5 +uint16_t mode_2Dcrazybees(void) { + if (!strip.isMatrix) return mode_static(); // not a 2D set-up + + const uint16_t cols = SEGMENT.virtualWidth(); + const uint16_t rows = SEGMENT.virtualHeight(); + + byte n = MIN(MAX_BEES, (rows * cols) / 256 + 1); + + typedef struct Bee { + uint8_t posX, posY, aimX, aimY, hue; + int8_t deltaX, deltaY, signX, signY, error; + void aimed(uint16_t w, uint16_t h) { + random16_set_seed(millis()); + aimX = random8(0, w); + aimY = random8(0, h); + hue = random8(); + deltaX = abs(aimX - posX); + deltaY = abs(aimY - posY); + signX = posX < aimX ? 1 : -1; + signY = posY < aimY ? 1 : -1; + error = deltaX - deltaY; + }; + } bee_t; + + if (!SEGENV.allocateData(sizeof(bee_t)*MAX_BEES)) return mode_static(); //allocation failed + bee_t *bee = reinterpret_cast(SEGENV.data); + + if (SEGENV.call == 0) { + SEGMENT.setUpLeds(); + SEGMENT.fill(BLACK); + for (size_t i = 0; i < n; i++) { + bee[i].posX = random8(0, cols); + bee[i].posY = random8(0, rows); + bee[i].aimed(cols, rows); + } + } + + if (millis() > SEGENV.step) { + SEGENV.step = millis() + (FRAMETIME * 8 / ((SEGMENT.speed>>5)+1)); + + SEGMENT.fadeToBlackBy(32); + + for (size_t i = 0; i < n; i++) { + SEGMENT.addPixelColorXY(bee[i].aimX + 1, bee[i].aimY, CHSV(bee[i].hue, 255, 255)); + SEGMENT.addPixelColorXY(bee[i].aimX, bee[i].aimY + 1, CHSV(bee[i].hue, 255, 255)); + SEGMENT.addPixelColorXY(bee[i].aimX - 1, bee[i].aimY, CHSV(bee[i].hue, 255, 255)); + SEGMENT.addPixelColorXY(bee[i].aimX, bee[i].aimY - 1, CHSV(bee[i].hue, 255, 255)); + if (bee[i].posX != bee[i].aimX || bee[i].posY != bee[i].aimY) { + SEGMENT.setPixelColorXY(bee[i].posX, bee[i].posY, CRGB(CHSV(bee[i].hue, 60, 255))); + int8_t error2 = bee[i].error * 2; + if (error2 > -bee[i].deltaY) { + bee[i].error -= bee[i].deltaY; + bee[i].posX += bee[i].signX; + } + if (error2 < bee[i].deltaX) { + bee[i].error += bee[i].deltaX; + bee[i].posY += bee[i].signY; + } + } else { + bee[i].aimed(cols, rows); + } + } + SEGMENT.blur(SEGMENT.intensity>>4); + } + return FRAMETIME; +} +static const char _data_FX_MODE_2DCRAZYBEES[] PROGMEM = "Crazy Bees@!,Blur;;;2d"; + + +///////////////////////// +// 2D Ghost Rider // +///////////////////////// +//// Ghost Rider by stepko (c)2021 [https://editor.soulmatelights.com/gallery/716-ghost-rider], adapted by Blaz Kristan (AKA blazoncek) +#define LIGHTERS_AM 64 // max lighters (adequate for 32x32 matrix) +uint16_t mode_2Dghostrider(void) { + if (!strip.isMatrix) return mode_static(); // not a 2D set-up + + const uint16_t cols = SEGMENT.virtualWidth(); + const uint16_t rows = SEGMENT.virtualHeight(); + + typedef struct Lighter { + int16_t gPosX; + int16_t gPosY; + uint16_t gAngle; + int8_t angleSpeed; + uint16_t lightersPosX[LIGHTERS_AM]; + uint16_t lightersPosY[LIGHTERS_AM]; + uint16_t Angle[LIGHTERS_AM]; + uint16_t time[LIGHTERS_AM]; + bool reg[LIGHTERS_AM]; + int8_t Vspeed; + } lighter_t; + + if (!SEGENV.allocateData(sizeof(lighter_t))) return mode_static(); //allocation failed + lighter_t *lighter = reinterpret_cast(SEGENV.data); + + const size_t maxLighters = min(cols + rows, LIGHTERS_AM); + + if (SEGENV.call == 0) SEGMENT.setUpLeds(); + if (SEGENV.aux0 != cols || SEGENV.aux1 != rows) { + SEGENV.aux0 = cols; + SEGENV.aux1 = rows; + SEGMENT.fill(BLACK); + random16_set_seed(strip.now); + lighter->angleSpeed = random8(0,20) - 10; + lighter->Vspeed = 5; + lighter->gPosX = (cols/2) * 10; + lighter->gPosY = (rows/2) * 10; + for (size_t i = 0; i < maxLighters; i++) { + lighter->lightersPosX[i] = lighter->gPosX; + lighter->lightersPosY[i] = lighter->gPosY + i; + lighter->time[i] = i * 2; + } + } + + if (millis() > SEGENV.step) { + SEGENV.step = millis() + 1024 / (cols+rows); + + SEGMENT.fadeToBlackBy((SEGMENT.speed>>2)+64); + + CRGB color = CRGB::White; + SEGMENT.wu_pixel(lighter->gPosX * 256 / 10, lighter->gPosY * 256 / 10, color); + + lighter->gPosX += lighter->Vspeed * sin_t(radians(lighter->gAngle)); + lighter->gPosY += lighter->Vspeed * cos_t(radians(lighter->gAngle)); + lighter->gAngle += lighter->angleSpeed; + if (lighter->gPosX < 0) lighter->gPosX = (cols - 1) * 10; + if (lighter->gPosX > (cols - 1) * 10) lighter->gPosX = 0; + if (lighter->gPosY < 0) lighter->gPosY = (rows - 1) * 10; + if (lighter->gPosY > (rows - 1) * 10) lighter->gPosY = 0; + for (size_t i = 0; i < maxLighters; i++) { + lighter->time[i] += random8(5, 20); + if (lighter->time[i] >= 255 || + (lighter->lightersPosX[i] <= 0) || + (lighter->lightersPosX[i] >= (cols - 1) * 10) || + (lighter->lightersPosY[i] <= 0) || + (lighter->lightersPosY[i] >= (rows - 1) * 10)) { + lighter->reg[i] = true; + } + if (lighter->reg[i]) { + lighter->lightersPosY[i] = lighter->gPosY; + lighter->lightersPosX[i] = lighter->gPosX; + lighter->Angle[i] = lighter->gAngle + random(-10, 10); + lighter->time[i] = 0; + lighter->reg[i] = false; + } else { + lighter->lightersPosX[i] += -7 * sin_t(radians(lighter->Angle[i])); + lighter->lightersPosY[i] += -7 * cos_t(radians(lighter->Angle[i])); + } + SEGMENT.wu_pixel(lighter->lightersPosX[i] * 256 / 10, lighter->lightersPosY[i] * 256 / 10, ColorFromPalette(SEGPALETTE, (256 - lighter->time[i]))); + } + SEGMENT.blur(SEGMENT.intensity>>3); + } + + return FRAMETIME; +} +static const char _data_FX_MODE_2DGHOSTRIDER[] PROGMEM = "Ghost Rider@Fade rate,Blur;!,!,!;!;2d"; + + +//////////////////////////// +// 2D Floating Blobs // +//////////////////////////// +//// Floating Blobs by stepko (c)2021 [https://editor.soulmatelights.com/gallery/573-blobs], adapted by Blaz Kristan (AKA blazoncek) +#define MAX_BLOBS 8 +uint16_t mode_2Dfloatingblobs(void) { + if (!strip.isMatrix) return mode_static(); // not a 2D set-up + + const uint16_t cols = SEGMENT.virtualWidth(); + const uint16_t rows = SEGMENT.virtualHeight(); + + typedef struct Blob { + float x[MAX_BLOBS], y[MAX_BLOBS]; + float sX[MAX_BLOBS], sY[MAX_BLOBS]; // speed + float r[MAX_BLOBS]; + bool grow[MAX_BLOBS]; + byte color[MAX_BLOBS]; + } blob_t; + + uint8_t Amount = (SEGMENT.intensity>>5) + 1; // NOTE: be sure to update MAX_BLOBS if you change this + + if (!SEGENV.allocateData(sizeof(blob_t))) return mode_static(); //allocation failed + blob_t *blob = reinterpret_cast(SEGENV.data); + + if (SEGENV.call == 0) SEGMENT.setUpLeds(); + if (SEGENV.aux0 != cols || SEGENV.aux1 != rows) { + SEGENV.aux0 = cols; // re-initialise if virtual size changes + SEGENV.aux1 = rows; + SEGMENT.fill(BLACK); + for (size_t i = 0; i < MAX_BLOBS; i++) { + blob->r[i] = random8(1, cols>8 ? (cols/4) : 2); + blob->sX[i] = (float) random8(3, cols) / (float)(256 - SEGMENT.speed); // speed x + blob->sY[i] = (float) random8(3, rows) / (float)(256 - SEGMENT.speed); // speed y + blob->x[i] = random8(0, cols-1); + blob->y[i] = random8(0, rows-1); + blob->color[i] = random8(); + blob->grow[i] = (blob->r[i] < 1.f); + if (blob->sX[i] == 0) blob->sX[i] = 1; + if (blob->sY[i] == 0) blob->sY[i] = 1; + } + } + + SEGMENT.fadeToBlackBy(20); + + // Bounce balls around + for (size_t i = 0; i < Amount; i++) { + if (SEGENV.step < millis()) blob->color[i] = add8(blob->color[i], 4); // slowly change color + // change radius if needed + if (blob->grow[i]) { + // enlarge radius until it is >= 4 + blob->r[i] += (fabs(blob->sX[i]) > fabs(blob->sY[i]) ? fabs(blob->sX[i]) : fabs(blob->sY[i])) * 0.05f; + if (blob->r[i] >= MIN(cols/4.f,2.f)) { + blob->grow[i] = false; + } + } else { + // reduce radius until it is < 1 + blob->r[i] -= (fabs(blob->sX[i]) > fabs(blob->sY[i]) ? fabs(blob->sX[i]) : fabs(blob->sY[i])) * 0.05f; + if (blob->r[i] < 1.f) { + blob->grow[i] = true; + } + } + uint32_t c = SEGMENT.color_from_palette(blob->color[i], false, false, 0); + if (blob->r[i] > 1.f) SEGMENT.fill_circle(blob->y[i], blob->x[i], roundf(blob->r[i]), c); + else SEGMENT.setPixelColorXY(blob->y[i], blob->x[i], c); + // move x + if (blob->x[i] + blob->r[i] >= cols - 1) blob->x[i] += (blob->sX[i] * ((cols - 1 - blob->x[i]) / blob->r[i] + 0.005f)); + else if (blob->x[i] - blob->r[i] <= 0) blob->x[i] += (blob->sX[i] * (blob->x[i] / blob->r[i] + 0.005f)); + else blob->x[i] += blob->sX[i]; + // move y + if (blob->y[i] + blob->r[i] >= rows - 1) blob->y[i] += (blob->sY[i] * ((rows - 1 - blob->y[i]) / blob->r[i] + 0.005f)); + else if (blob->y[i] - blob->r[i] <= 0) blob->y[i] += (blob->sY[i] * (blob->y[i] / blob->r[i] + 0.005f)); + else blob->y[i] += blob->sY[i]; + // bounce x + if (blob->x[i] < 0.01f) { + blob->sX[i] = (float)random8(3, cols) / (256 - SEGMENT.speed); + blob->x[i] = 0.01f; + } else if (blob->x[i] > (float)cols - 1.01f) { + blob->sX[i] = (float)random8(3, cols) / (256 - SEGMENT.speed); + blob->sX[i] = -blob->sX[i]; + blob->x[i] = (float)cols - 1.01f; + } + // bounce y + if (blob->y[i] < 0.01f) { + blob->sY[i] = (float)random8(3, rows) / (256 - SEGMENT.speed); + blob->y[i] = 0.01f; + } else if (blob->y[i] > (float)rows - 1.01f) { + blob->sY[i] = (float)random8(3, rows) / (256 - SEGMENT.speed); + blob->sY[i] = -blob->sY[i]; + blob->y[i] = (float)rows - 1.01f; + } + } + SEGMENT.blur(SEGMENT.custom1>>2); + + if (SEGENV.step < millis()) SEGENV.step = millis() + 2000; // change colors every 2 seconds + + return FRAMETIME; +} +#undef MAX_BLOBS +static const char _data_FX_MODE_2DBLOBS[] PROGMEM = "Blobs@!,# blobs,Blur;!,!,;!;c1=8,2d"; + + +//////////////////////////// +// 2D Scrolling text // +//////////////////////////// +uint16_t mode_2Dscrollingtext(void) { + if (!strip.isMatrix) return mode_static(); // not a 2D set-up + + const uint16_t cols = SEGMENT.virtualWidth(); + const uint16_t rows = SEGMENT.virtualHeight(); + + int letterWidth; + int letterHeight; + switch (map(SEGMENT.custom2, 0, 255, 1, 5)) { + default: + case 1: letterWidth = 4; letterHeight = 6; break; + case 2: letterWidth = 5; letterHeight = 8; break; + case 3: letterWidth = 6; letterHeight = 8; break; + case 4: letterWidth = 7; letterHeight = 9; break; + case 5: letterWidth = 5; letterHeight = 12; break; + } + const int yoffset = map(SEGMENT.intensity, 0, 255, -rows/2, rows/2) + (rows-letterHeight)/2; + char text[33] = {'\0'}; + if (SEGMENT.name) for (size_t i=0,j=0; i31 && SEGMENT.name[i]<128) text[j++] = SEGMENT.name[i]; + + if (!strlen(text) || !strncmp_P(text,PSTR("#DATE"),5) || !strncmp_P(text,PSTR("#TIME"),5)) { // fallback if empty segment name: display date and time + char sec[5]; + byte AmPmHour = hour(localTime); + boolean isitAM = true; + if (useAMPM) { + if (AmPmHour > 11) { AmPmHour -= 12; isitAM = false; } + if (AmPmHour == 0) { AmPmHour = 12; } + } + if (useAMPM) sprintf_P(sec, PSTR(" %2s"), (isitAM ? "AM" : "PM")); + else sprintf_P(sec, PSTR(":%02d"), second(localTime)); + if (!strncmp_P(text,PSTR("#DATE"),5)) sprintf_P(text, PSTR("%d.%d.%d"), day(localTime), month(localTime), year(localTime)); + else if (!strncmp_P(text,PSTR("#TIME"),5)) sprintf_P(text, PSTR("%2d:%02d%s"), AmPmHour, minute(localTime), sec); + else sprintf_P(text, PSTR("%s %d, %d %2d:%02d%s"), monthShortStr(month(localTime)), day(localTime), year(localTime), AmPmHour, minute(localTime), sec); + } + const int numberOfLetters = strlen(text); + + if (SEGENV.step < millis()) { + if ((numberOfLetters * letterWidth) > cols) ++SEGENV.aux0 %= (numberOfLetters * letterWidth) + cols; // offset + else SEGENV.aux0 = (cols + (numberOfLetters * letterWidth))/2; + ++SEGENV.aux1 &= 0xFF; // color shift + SEGENV.step = millis() + map(SEGMENT.speed, 0, 255, 10*FRAMETIME_FIXED, 2*FRAMETIME_FIXED); + + // we need it 3 times + SEGMENT.fade_out(255 - (SEGMENT.custom1>>5)); // fade to background color + SEGMENT.fade_out(255 - (SEGMENT.custom1>>5)); // fade to background color + SEGMENT.fade_out(255 - (SEGMENT.custom1>>5)); // fade to background color + for (int i = 0; i < numberOfLetters; i++) { + if (int(cols) - int(SEGENV.aux0) + letterWidth*(i+1) < 0) continue; // don't draw characters off-screen + SEGMENT.drawCharacter(text[i], int(cols) - int(SEGENV.aux0) + letterWidth*i, yoffset, letterWidth, letterHeight, SEGMENT.color_from_palette(SEGENV.aux1, false, PALETTE_SOLID_WRAP, 0)); + } + } + + return FRAMETIME; +} +static const char _data_FX_MODE_2DSCROLLTEXT[] PROGMEM = "Scrolling Text@!,Y Offset,Trail,Font size;!,!;!;ix=128,c1=0,rev=0,mi=0,rY=0,mY=0,2d"; + + +//////////////////////////// +// 2D Drift Rose // +//////////////////////////// +//// Drift Rose by stepko (c)2021 [https://editor.soulmatelights.com/gallery/1369-drift-rose-pattern], adapted by Blaz Kristan (AKA blazoncek) +uint16_t mode_2Ddriftrose(void) { + if (!strip.isMatrix) return mode_static(); // not a 2D set-up + + const uint16_t cols = SEGMENT.virtualWidth(); + const uint16_t rows = SEGMENT.virtualHeight(); + + const float CX = (cols-cols%2)/2.f - .5f; + const float CY = (rows-rows%2)/2.f - .5f; + const float L = min(cols, rows) / 2.f; + + if (SEGENV.call == 0) { + SEGMENT.setUpLeds(); + SEGMENT.fill(BLACK); + } + + SEGMENT.fadeToBlackBy(32+(SEGMENT.speed>>3)); + for (size_t i = 1; i < 37; i++) { + uint32_t x = (CX + (sin_t(radians(i * 10)) * (beatsin8(i, 0, L*2)-L))) * 255.f; + uint32_t y = (CY + (cos_t(radians(i * 10)) * (beatsin8(i, 0, L*2)-L))) * 255.f; + SEGMENT.wu_pixel(x, y, CHSV(i * 10, 255, 255)); + } + SEGMENT.blur((SEGMENT.intensity>>4)+1); + + return FRAMETIME; +} +static const char _data_FX_MODE_2DDRIFTROSE[] PROGMEM = "Drift Rose@Fade,Blur;;;2d"; + +#endif // WLED_DISABLE_2D + + +/////////////////////////////////////////////////////////////////////////////// +/******************** audio enhanced routines ************************/ +/////////////////////////////////////////////////////////////////////////////// + + +/* use the following code to pass AudioReactive usermod variables to effect + + uint8_t *binNum = (uint8_t*)&SEGENV.aux1, *maxVol = (uint8_t*)(&SEGENV.aux1+1); // just in case assignment + bool samplePeak = false; + float FFT_MajorPeak = 1.0; + uint8_t *fftResult = nullptr; + float *fftBin = nullptr; + um_data_t *um_data; + if (usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { + volumeSmth = *(float*) um_data->u_data[0]; + volumeRaw = *(float*) um_data->u_data[1]; + fftResult = (uint8_t*) um_data->u_data[2]; + samplePeak = *(uint8_t*) um_data->u_data[3]; + FFT_MajorPeak = *(float*) um_data->u_data[4]; + my_magnitude = *(float*) um_data->u_data[5]; + maxVol = (uint8_t*) um_data->u_data[6]; // requires UI element (SEGMENT.customX?), changes source element + binNum = (uint8_t*) um_data->u_data[7]; // requires UI element (SEGMENT.customX?), changes source element + fftBin = (float*) um_data->u_data[8]; + } else { + // add support for no audio data + um_data = simulateSound(SEGMENT.soundSim); + } +*/ + + +// a few constants needed for AudioReactive effects + +// for 22Khz sampling +#define MAX_FREQUENCY 11025 // sample frequency / 2 (as per Nyquist criterion) +#define MAX_FREQ_LOG10 4.04238f // log10(MAX_FREQUENCY) + +// for 20Khz sampling +//#define MAX_FREQUENCY 10240 +//#define MAX_FREQ_LOG10 4.0103f + +// for 10Khz sampling +//#define MAX_FREQUENCY 5120 +//#define MAX_FREQ_LOG10 3.71f + + +///////////////////////////////// +// * Ripple Peak // +///////////////////////////////// +uint16_t mode_ripplepeak(void) { // * Ripple peak. By Andrew Tuline. + // This currently has no controls. + #define maxsteps 16 // Case statement wouldn't allow a variable. + + uint16_t maxRipples = 16; + uint16_t dataSize = sizeof(Ripple) * maxRipples; + if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed + Ripple* ripples = reinterpret_cast(SEGENV.data); + + um_data_t *um_data; + if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { + // add support for no audio + um_data = simulateSound(SEGMENT.soundSim); + } + uint8_t samplePeak = *(uint8_t*)um_data->u_data[3]; + #ifdef ESP32 + float FFT_MajorPeak = *(float*) um_data->u_data[4]; + #endif + uint8_t *maxVol = (uint8_t*)um_data->u_data[6]; + uint8_t *binNum = (uint8_t*)um_data->u_data[7]; + + // printUmData(); + + if (SEGENV.call == 0) { + SEGENV.aux0 = 255; + SEGMENT.custom1 = *binNum; + SEGMENT.custom2 = *maxVol * 2; + } + + *binNum = SEGMENT.custom1; // Select a bin. + *maxVol = SEGMENT.custom2 / 2; // Our volume comparator. + + SEGMENT.fade_out(240); // Lower frame rate means less effective fading than FastLED + SEGMENT.fade_out(240); + + for (int i = 0; i < SEGMENT.intensity/16; i++) { // Limit the number of ripples. + if (samplePeak) ripples[i].state = 255; + + switch (ripples[i].state) { + case 254: // Inactive mode + break; + + case 255: // Initialize ripple variables. + ripples[i].pos = random16(SEGLEN); + #ifdef ESP32 + if (FFT_MajorPeak > 1) // log10(0) is "forbidden" (throws exception) + ripples[i].color = (int)(log10f(FFT_MajorPeak)*128); + else ripples[i].color = 0; + #else + ripples[i].color = random8(); + #endif + ripples[i].state = 0; + break; + + case 0: + SEGMENT.setPixelColor(ripples[i].pos, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(ripples[i].color, false, PALETTE_SOLID_WRAP, 0), SEGENV.aux0)); + ripples[i].state++; + break; + + case maxsteps: // At the end of the ripples. 254 is an inactive mode. + ripples[i].state = 254; + break; + + default: // Middle of the ripples. + SEGMENT.setPixelColor((ripples[i].pos + ripples[i].state + SEGLEN) % SEGLEN, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(ripples[i].color, false, PALETTE_SOLID_WRAP, 0), SEGENV.aux0/ripples[i].state*2)); + SEGMENT.setPixelColor((ripples[i].pos - ripples[i].state + SEGLEN) % SEGLEN, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(ripples[i].color, false, PALETTE_SOLID_WRAP, 0), SEGENV.aux0/ripples[i].state*2)); + ripples[i].state++; // Next step. + break; + } // switch step + } // for i + + return FRAMETIME; +} // mode_ripplepeak() +static const char _data_FX_MODE_RIPPLEPEAK[] PROGMEM = "Ripple Peak@Fade rate,Max # of ripples,Select bin,Volume (minimum);!,!;!;c2=0,mp12=0,ssim=0,1d,vo"; // Pixel, Beatsin + + +#ifndef WLED_DISABLE_2D +///////////////////////// +// * 2D Swirl // +///////////////////////// +// By: Mark Kriegsman https://gist.github.com/kriegsman/5adca44e14ad025e6d3b , modified by Andrew Tuline +uint16_t mode_2DSwirl(void) { + if (!strip.isMatrix) return mode_static(); // not a 2D set-up + + const uint16_t cols = SEGMENT.virtualWidth(); + const uint16_t rows = SEGMENT.virtualHeight(); + + if (SEGENV.call == 0) { + SEGMENT.setUpLeds(); + SEGMENT.fill(BLACK); + } + + const uint8_t borderWidth = 2; + + SEGMENT.blur(SEGMENT.custom1); + + uint8_t i = beatsin8( 27*SEGMENT.speed/255, borderWidth, cols - borderWidth); + uint8_t j = beatsin8( 41*SEGMENT.speed/255, borderWidth, rows - borderWidth); + uint8_t ni = (cols - 1) - i; + uint8_t nj = (cols - 1) - j; + uint16_t ms = millis(); + + um_data_t *um_data; + if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { + // add support for no audio + um_data = simulateSound(SEGMENT.soundSim); + } + float volumeSmth = *(float*) um_data->u_data[0]; //ewowi: use instead of sampleAvg??? + int16_t volumeRaw = *(int16_t*) um_data->u_data[1]; + + // printUmData(); + + SEGMENT.addPixelColorXY( i, j, ColorFromPalette(SEGPALETTE, (ms / 11 + volumeSmth*4), volumeRaw * SEGMENT.intensity / 64, LINEARBLEND)); //CHSV( ms / 11, 200, 255); + SEGMENT.addPixelColorXY( j, i, ColorFromPalette(SEGPALETTE, (ms / 13 + volumeSmth*4), volumeRaw * SEGMENT.intensity / 64, LINEARBLEND)); //CHSV( ms / 13, 200, 255); + SEGMENT.addPixelColorXY(ni,nj, ColorFromPalette(SEGPALETTE, (ms / 17 + volumeSmth*4), volumeRaw * SEGMENT.intensity / 64, LINEARBLEND)); //CHSV( ms / 17, 200, 255); + SEGMENT.addPixelColorXY(nj,ni, ColorFromPalette(SEGPALETTE, (ms / 29 + volumeSmth*4), volumeRaw * SEGMENT.intensity / 64, LINEARBLEND)); //CHSV( ms / 29, 200, 255); + SEGMENT.addPixelColorXY( i,nj, ColorFromPalette(SEGPALETTE, (ms / 37 + volumeSmth*4), volumeRaw * SEGMENT.intensity / 64, LINEARBLEND)); //CHSV( ms / 37, 200, 255); + SEGMENT.addPixelColorXY(ni, j, ColorFromPalette(SEGPALETTE, (ms / 41 + volumeSmth*4), volumeRaw * SEGMENT.intensity / 64, LINEARBLEND)); //CHSV( ms / 41, 200, 255); + + return FRAMETIME; +} // mode_2DSwirl() +static const char _data_FX_MODE_2DSWIRL[] PROGMEM = "Swirl@!,Sensitivity,Blur;,Bg Swirl;!;ix=64ssim=0,2d,vo"; // Beatsin + + +///////////////////////// +// * 2D Waverly // +///////////////////////// +// By: Stepko, https://editor.soulmatelights.com/gallery/652-wave , modified by Andrew Tuline +uint16_t mode_2DWaverly(void) { + if (!strip.isMatrix) return mode_static(); // not a 2D set-up + + const uint16_t cols = SEGMENT.virtualWidth(); + const uint16_t rows = SEGMENT.virtualHeight(); + + if (SEGENV.call == 0) { + SEGMENT.setUpLeds(); + SEGMENT.fill(BLACK); + } + + um_data_t *um_data; + if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { + // add support for no audio + um_data = simulateSound(SEGMENT.soundSim); + } + float volumeSmth = *(float*) um_data->u_data[0]; + + SEGMENT.fadeToBlackBy(SEGMENT.speed); + + long t = millis() / 2; + for (int i = 0; i < cols; i++) { + uint16_t thisVal = (1 + SEGMENT.intensity/64) * inoise8(i * 45 , t , t)/2; + // use audio if available + if (um_data) { + thisVal /= 32; // reduce intensity of inoise8() + thisVal *= volumeSmth; + } + uint16_t thisMax = map(thisVal, 0, 512, 0, rows); + + for (int j = 0; j < thisMax; j++) { + SEGMENT.addPixelColorXY(i, j, ColorFromPalette(SEGPALETTE, map(j, 0, thisMax, 250, 0), 255, LINEARBLEND)); + SEGMENT.addPixelColorXY((cols - 1) - i, (rows - 1) - j, ColorFromPalette(SEGPALETTE, map(j, 0, thisMax, 250, 0), 255, LINEARBLEND)); + } + } + SEGMENT.blur(16); + + return FRAMETIME; +} // mode_2DWaverly() +static const char _data_FX_MODE_2DWAVERLY[] PROGMEM = "Waverly@Amplification,Sensitivity;;!;ix=64,ssim=0,2d,vo"; // Beatsin + +#endif // WLED_DISABLE_2D + +// float version of map() +static float mapf(float x, float in_min, float in_max, float out_min, float out_max){ + return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; +} + +// Gravity struct requited for GRAV* effects +typedef struct Gravity { + int topLED; + int gravityCounter; +} gravity; + +/////////////////////// +// * GRAVCENTER // +/////////////////////// +uint16_t mode_gravcenter(void) { // Gravcenter. By Andrew Tuline. + + const uint16_t dataSize = sizeof(gravity); + if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed + Gravity* gravcen = reinterpret_cast(SEGENV.data); + + um_data_t *um_data; + if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { + // add support for no audio + um_data = simulateSound(SEGMENT.soundSim); + } + float volumeSmth = *(float*) um_data->u_data[0]; + + //SEGMENT.fade_out(240); + SEGMENT.fade_out(251); // 30% + + float segmentSampleAvg = volumeSmth * (float)SEGMENT.intensity / 255.0f; + segmentSampleAvg *= 0.125; // divide by 8, to compensate for later "sensitivty" upscaling + + float mySampleAvg = mapf(segmentSampleAvg*2.0, 0, 32, 0, (float)SEGLEN/2.0); // map to pixels available in current segment + uint16_t tempsamp = constrain(mySampleAvg, 0, SEGLEN/2); // Keep the sample from overflowing. + uint8_t gravity = 8 - SEGMENT.speed/32; + + for (int i=0; i= gravcen->topLED) + gravcen->topLED = tempsamp-1; + else if (gravcen->gravityCounter % gravity == 0) + gravcen->topLED--; + + if (gravcen->topLED >= 0) { + SEGMENT.setPixelColor(gravcen->topLED+SEGLEN/2, SEGMENT.color_from_palette(millis(), false, PALETTE_SOLID_WRAP, 0)); + SEGMENT.setPixelColor(SEGLEN/2-1-gravcen->topLED, SEGMENT.color_from_palette(millis(), false, PALETTE_SOLID_WRAP, 0)); + } + gravcen->gravityCounter = (gravcen->gravityCounter + 1) % gravity; + + return FRAMETIME; +} // mode_gravcenter() +static const char _data_FX_MODE_GRAVCENTER[] PROGMEM = "Gravcenter@Rate of fall,Sensitivity;,!;!;ix=128,mp12=2,ssim=0,1d,vo"; // Circle, Beatsin + + +/////////////////////// +// * GRAVCENTRIC // +/////////////////////// +uint16_t mode_gravcentric(void) { // Gravcentric. By Andrew Tuline. + + uint16_t dataSize = sizeof(gravity); + if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed + Gravity* gravcen = reinterpret_cast(SEGENV.data); + + um_data_t *um_data; + if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { + // add support for no audio + um_data = simulateSound(SEGMENT.soundSim); + } + float volumeSmth = *(float*) um_data->u_data[0]; + + // printUmData(); + + //SEGMENT.fade_out(240); + //SEGMENT.fade_out(240); // twice? really? + SEGMENT.fade_out(253); // 50% + + float segmentSampleAvg = volumeSmth * (float)SEGMENT.intensity / 255.0; + segmentSampleAvg *= 0.125f; // divide by 8, to compensate for later "sensitivty" upscaling + + float mySampleAvg = mapf(segmentSampleAvg*2.0, 0.0f, 32.0f, 0.0f, (float)SEGLEN/2.0); // map to pixels availeable in current segment + int tempsamp = constrain(mySampleAvg, 0, SEGLEN/2); // Keep the sample from overflowing. + uint8_t gravity = 8 - SEGMENT.speed/32; + + for (int i=0; i= gravcen->topLED) + gravcen->topLED = tempsamp-1; + else if (gravcen->gravityCounter % gravity == 0) + gravcen->topLED--; + + if (gravcen->topLED >= 0) { + SEGMENT.setPixelColor(gravcen->topLED+SEGLEN/2, CRGB::Gray); + SEGMENT.setPixelColor(SEGLEN/2-1-gravcen->topLED, CRGB::Gray); + } + gravcen->gravityCounter = (gravcen->gravityCounter + 1) % gravity; + + return FRAMETIME; +} // mode_gravcentric() +static const char _data_FX_MODE_GRAVCENTRIC[] PROGMEM = "Gravcentric@Rate of fall,Sensitivity;!;!;ix=128,mp12=3,ssim=0,1d,vo"; // Corner, Beatsin + + +/////////////////////// +// * GRAVIMETER // +/////////////////////// +uint16_t mode_gravimeter(void) { // Gravmeter. By Andrew Tuline. + + uint16_t dataSize = sizeof(gravity); + if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed + Gravity* gravcen = reinterpret_cast(SEGENV.data); + + um_data_t *um_data; + if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { + // add support for no audio + um_data = simulateSound(SEGMENT.soundSim); + } + float volumeSmth = *(float*) um_data->u_data[0]; + + //SEGMENT.fade_out(240); + SEGMENT.fade_out(249); // 25% + + float segmentSampleAvg = volumeSmth * (float)SEGMENT.intensity / 255.0; + segmentSampleAvg *= 0.25; // divide by 4, to compensate for later "sensitivty" upscaling + + float mySampleAvg = mapf(segmentSampleAvg*2.0, 0, 64, 0, (SEGLEN-1)); // map to pixels availeable in current segment + int tempsamp = constrain(mySampleAvg,0,SEGLEN-1); // Keep the sample from overflowing. + uint8_t gravity = 8 - SEGMENT.speed/32; + + for (int i=0; i= gravcen->topLED) + gravcen->topLED = tempsamp; + else if (gravcen->gravityCounter % gravity == 0) + gravcen->topLED--; + + if (gravcen->topLED > 0) { + SEGMENT.setPixelColor(gravcen->topLED, SEGMENT.color_from_palette(millis(), false, PALETTE_SOLID_WRAP, 0)); + } + gravcen->gravityCounter = (gravcen->gravityCounter + 1) % gravity; + + return FRAMETIME; +} // mode_gravimeter() +static const char _data_FX_MODE_GRAVIMETER[] PROGMEM = "Gravimeter@Rate of fall,Sensitivity;!,!;!;ix=128,mp12=2,ssim=0,1d,vo"; // Circle, Beatsin + + +////////////////////// +// * JUGGLES // +////////////////////// +uint16_t mode_juggles(void) { // Juggles. By Andrew Tuline. + um_data_t *um_data; + if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { + // add support for no audio + um_data = simulateSound(SEGMENT.soundSim); + } + float volumeSmth = *(float*) um_data->u_data[0]; + + SEGMENT.fade_out(224); // 6.25% + uint16_t my_sampleAgc = fmax(fmin(volumeSmth, 255.0), 0); + + for (size_t i=0; iu_data[1]; + + if (SEGENV.call == 0) { + SEGMENT.setUpLeds(); + SEGMENT.fill(BLACK); + } + + uint8_t secondHand = micros()/(256-SEGMENT.speed)/500 % 16; + if(SEGENV.aux0 != secondHand) { + SEGENV.aux0 = secondHand; + + int pixBri = volumeRaw * SEGMENT.intensity / 64; + for (int i=0; iu_data[0]; + + SEGMENT.fade_out(SEGMENT.speed); + SEGMENT.fade_out(SEGMENT.speed); + + float tmpSound2 = volumeSmth * (float)SEGMENT.intensity / 256.0; // Too sensitive. + tmpSound2 *= (float)SEGMENT.intensity / 128.0; // Reduce sensitity/length. + + int maxLen = mapf(tmpSound2, 0, 127, 0, SEGLEN/2); + if (maxLen >SEGLEN/2) maxLen = SEGLEN/2; + + for (int i=(SEGLEN/2-maxLen); i<(SEGLEN/2+maxLen); i++) { + uint8_t index = inoise8(i*volumeSmth+SEGENV.aux0, SEGENV.aux1+i*volumeSmth); // Get a value from the noise function. I'm using both x and y axis. + SEGMENT.setPixelColor(i, SEGMENT.color_from_palette(index, false, PALETTE_SOLID_WRAP, 0)); + } + + SEGENV.aux0=SEGENV.aux0+beatsin8(5,0,10); + SEGENV.aux1=SEGENV.aux1+beatsin8(4,0,10); + + return FRAMETIME; +} // mode_midnoise() +static const char _data_FX_MODE_MIDNOISE[] PROGMEM = "Midnoise@Fade rate,Maximum length;,!;!;ix=128,mp12=1,ssim=0,1d,vo"; // Bar, Beatsin + + +////////////////////// +// * NOISEFIRE // +////////////////////// +// I am the god of hellfire. . . Volume (only) reactive fire routine. Oh, look how short this is. +uint16_t mode_noisefire(void) { // Noisefire. By Andrew Tuline. + CRGBPalette16 myPal = CRGBPalette16(CHSV(0,255,2), CHSV(0,255,4), CHSV(0,255,8), CHSV(0, 255, 8), // Fire palette definition. Lower value = darker. + CHSV(0, 255, 16), CRGB::Red, CRGB::Red, CRGB::Red, + CRGB::DarkOrange, CRGB::DarkOrange, CRGB::Orange, CRGB::Orange, + CRGB::Yellow, CRGB::Orange, CRGB::Yellow, CRGB::Yellow); + + um_data_t *um_data; + if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { + // add support for no audio + um_data = simulateSound(SEGMENT.soundSim); + } + float volumeSmth = *(float*) um_data->u_data[0]; + + if (SEGENV.call == 0) SEGMENT.fill(BLACK); + + for (int i = 0; i < SEGLEN; i++) { + uint16_t index = inoise8(i*SEGMENT.speed/64,millis()*SEGMENT.speed/64*SEGLEN/255); // X location is constant, but we move along the Y at the rate of millis(). By Andrew Tuline. + index = (255 - i*256/SEGLEN) * index/(256-SEGMENT.intensity); // Now we need to scale index so that it gets blacker as we get close to one of the ends. + // This is a simple y=mx+b equation that's been scaled. index/128 is another scaling. + + CRGB color = ColorFromPalette(myPal, index, volumeSmth*2, LINEARBLEND); // Use the my own palette. + SEGMENT.setPixelColor(i, color); + } + + return FRAMETIME; +} // mode_noisefire() +static const char _data_FX_MODE_NOISEFIRE[] PROGMEM = "Noisefire@!,!;;;mp12=2,ssim=0,1d,vo"; // Circle, Beatsin + + +/////////////////////// +// * Noisemeter // +/////////////////////// +uint16_t mode_noisemeter(void) { // Noisemeter. By Andrew Tuline. + + um_data_t *um_data; + if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { + // add support for no audio + um_data = simulateSound(SEGMENT.soundSim); + } + float volumeSmth = *(float*) um_data->u_data[0]; + int16_t volumeRaw = *(int16_t*)um_data->u_data[1]; + + //uint8_t fadeRate = map(SEGMENT.speed,0,255,224,255); + uint8_t fadeRate = map(SEGMENT.speed,0,255,200,254); + SEGMENT.fade_out(fadeRate); + + float tmpSound2 = volumeRaw * 2.0 * (float)SEGMENT.intensity / 255.0; + int maxLen = mapf(tmpSound2, 0, 255, 0, SEGLEN); // map to pixels availeable in current segment // Still a bit too sensitive. + if (maxLen <0) maxLen = 0; + if (maxLen >SEGLEN) maxLen = SEGLEN; + + for (int i=0; iu_data[1]; + + uint8_t secondHand = micros()/(256-SEGMENT.speed)/500+1 % 16; + if (SEGENV.aux0 != secondHand) { + SEGENV.aux0 = secondHand; + + int pixBri = volumeRaw * SEGMENT.intensity / 64; + + SEGMENT.setPixelColor(SEGLEN/2, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(millis(), false, PALETTE_SOLID_WRAP, 0), pixBri)); + for (int i = SEGLEN - 1; i > SEGLEN/2; i--) SEGMENT.setPixelColor(i, SEGMENT.getPixelColor(i-1)); //move to the left + for (int i = 0; i < SEGLEN/2; i++) SEGMENT.setPixelColor(i, SEGMENT.getPixelColor(i+1)); // move to the right + } + + return FRAMETIME; +} // mode_pixelwave() +static const char _data_FX_MODE_PIXELWAVE[] PROGMEM = "Pixelwave@!,Sensitivity;!,!;!;ix=64,mp12=2,ssim=0,1d,vo"; // Circle, Beatsin + + +////////////////////// +// * PLASMOID // +////////////////////// +typedef struct Plasphase { + int16_t thisphase; + int16_t thatphase; +} plasphase; + +uint16_t mode_plasmoid(void) { // Plasmoid. By Andrew Tuline. + // even with 1D effect we have to take logic for 2D segments for allocation as fill_solid() fills whole segment + if (!SEGENV.allocateData(sizeof(plasphase))) return mode_static(); //allocation failed + Plasphase* plasmoip = reinterpret_cast(SEGENV.data); + + um_data_t *um_data; + if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { + // add support for no audio + um_data = simulateSound(SEGMENT.soundSim); + } + float volumeSmth = *(float*) um_data->u_data[0]; + + SEGMENT.fadeToBlackBy(32); + + plasmoip->thisphase += beatsin8(6,-4,4); // You can change direction and speed individually. + plasmoip->thatphase += beatsin8(7,-4,4); // Two phase values to make a complex pattern. By Andrew Tuline. + + for (int i=0; ithisphase) & 0xFF)/2; + thisbright += cos8(((i*(97 +(5*SEGMENT.speed/32)))+plasmoip->thatphase) & 0xFF)/2; // Let's munge the brightness a bit and animate it all with the phases. + + uint8_t colorIndex=thisbright; + if (volumeSmth * SEGMENT.intensity / 64 < thisbright) {thisbright = 0;} + + SEGMENT.addPixelColor(i, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(colorIndex, false, PALETTE_SOLID_WRAP, 0), thisbright)); + } + + return FRAMETIME; +} // mode_plasmoid() +static const char _data_FX_MODE_PLASMOID[] PROGMEM = "Plasmoid@Phase,# of pixels;!,!;!;sx=128,ix=128,mp12=0,ssim=0,1d,vo"; // Pixels, Beatsin + + +/////////////////////// +// * PUDDLEPEAK // +/////////////////////// +// Andrew's crappy peak detector. If I were 40+ years younger, I'd learn signal processing. +uint16_t mode_puddlepeak(void) { // Puddlepeak. By Andrew Tuline. + + uint16_t size = 0; + uint8_t fadeVal = map(SEGMENT.speed,0,255, 224, 254); + uint16_t pos = random(SEGLEN); // Set a random starting position. + + um_data_t *um_data; + if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { + // add support for no audio + um_data = simulateSound(SEGMENT.soundSim); + } + uint8_t samplePeak = *(uint8_t*)um_data->u_data[3]; + uint8_t *maxVol = (uint8_t*)um_data->u_data[6]; + uint8_t *binNum = (uint8_t*)um_data->u_data[7]; + float volumeSmth = *(float*) um_data->u_data[0]; + + if (SEGENV.call == 0) { + SEGMENT.custom1 = *binNum; + SEGMENT.custom2 = *maxVol * 2; + } + + *binNum = SEGMENT.custom1; // Select a bin. + *maxVol = SEGMENT.custom2 / 2; // Our volume comparator. + + SEGMENT.fade_out(fadeVal); + + if (samplePeak == 1) { + size = volumeSmth * SEGMENT.intensity /256 /4 + 1; // Determine size of the flash based on the volume. + if (pos+size>= SEGLEN) size = SEGLEN - pos; + } + + for (int i=0; iu_data[1]; + + if (volumeRaw > 1) { + size = volumeRaw * SEGMENT.intensity /256 /8 + 1; // Determine size of the flash based on the volume. + if (pos+size >= SEGLEN) size = SEGLEN - pos; + } + + for (int i=0; i(SEGENV.data); // Used to store a pile of samples because WLED frame rate and WLED sample rate are not synchronized. Frame rate is too low. + + um_data_t *um_data; + if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { + um_data = simulateSound(SEGMENT.soundSim); + } + float volumeSmth = *(float*) um_data->u_data[0]; + + myVals[millis()%32] = volumeSmth; // filling values semi randomly + + SEGMENT.fade_out(64+(SEGMENT.speed>>1)); + + for (int i=0; i u_data[2]; + + if (SEGENV.call == 0) { + SEGMENT.fill(BLACK); + SEGENV.aux0 = 0; + } + + int fadeoutDelay = (256 - SEGMENT.speed) / 32; + if ((fadeoutDelay <= 1 ) || ((SEGENV.call % fadeoutDelay) == 0)) SEGMENT.fade_out(SEGMENT.speed); + + SEGENV.step += FRAMETIME; + if (SEGENV.step > SPEED_FORMULA_L) { + uint16_t segLoc = random16(SEGLEN); + SEGMENT.setPixelColor(segLoc, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(2*fftResult[SEGENV.aux0%16]*240/(SEGLEN-1), false, PALETTE_SOLID_WRAP, 0), 2*fftResult[SEGENV.aux0%16])); + ++(SEGENV.aux0) %= 16; // make sure it doesn't cross 16 + + SEGENV.step = 1; + SEGMENT.blur(SEGMENT.intensity); + } + + return FRAMETIME; +} // mode_blurz() +static const char _data_FX_MODE_BLURZ[] PROGMEM = "Blurz@Fade rate,Blur amount;!,Color mix;!;mp12=0,ssim=0,1d,fr"; // Pixels, Beatsin + + +///////////////////////// +// ** DJLight // +///////////////////////// +uint16_t mode_DJLight(void) { // Written by ??? Adapted by Will Tatam. + const int mid = SEGLEN / 2; + + um_data_t *um_data; + if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { + // add support for no audio + um_data = simulateSound(SEGMENT.soundSim); + } + uint8_t *fftResult = (uint8_t*)um_data->u_data[2]; + + if (SEGENV.call == 0) { + SEGMENT.setUpLeds(); + SEGMENT.fill(BLACK); + } + + uint8_t secondHand = micros()/(256-SEGMENT.speed)/500+1 % 64; + if (SEGENV.aux0 != secondHand) { // Triggered millis timing. + SEGENV.aux0 = secondHand; + + SEGMENT.setPixelColor(mid, CRGB(fftResult[15]/2, fftResult[5]/2, fftResult[0]/2)); // 16-> 15 as 16 is out of bounds + CRGB color = SEGMENT.getPixelColor(mid); + SEGMENT.setPixelColor(mid, color.fadeToBlackBy(map(fftResult[1*4], 0, 255, 255, 10))); // TODO - Update + + for (int i = SEGLEN - 1; i > mid; i--) SEGMENT.setPixelColor(i, SEGMENT.getPixelColor(i-1)); //move to the left + for (int i = 0; i < mid; i++) SEGMENT.setPixelColor(i, SEGMENT.getPixelColor(i+1)); // move to the right + } + + return FRAMETIME; +} // mode_DJLight() +static const char _data_FX_MODE_DJLIGHT[] PROGMEM = "DJ Light@Speed;;;mp12=2,ssim=0,1d,fr"; // Circle, Beatsin + + +//////////////////// +// ** Freqmap // +//////////////////// +uint16_t mode_freqmap(void) { // Map FFT_MajorPeak to SEGLEN. Would be better if a higher framerate. + // Start frequency = 60 Hz and log10(60) = 1.78 + // End frequency = MAX_FREQUENCY in Hz and lo10(MAX_FREQUENCY) = MAX_FREQ_LOG10 + + um_data_t *um_data; + if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { + // add support for no audio + um_data = simulateSound(SEGMENT.soundSim); + } + float FFT_MajorPeak = *(float*) um_data->u_data[4]; + float my_magnitude = *(float*) um_data->u_data[5] / 4.0f; + if (FFT_MajorPeak < 1) FFT_MajorPeak = 1; // log10(0) is "forbidden" (throws exception) + + if (SEGENV.call == 0) SEGMENT.fill(BLACK); + int fadeoutDelay = (256 - SEGMENT.speed) / 32; + if ((fadeoutDelay <= 1 ) || ((SEGENV.call % fadeoutDelay) == 0)) SEGMENT.fade_out(SEGMENT.speed); + + int locn = (log10f((float)FFT_MajorPeak) - 1.78f) * (float)SEGLEN/(MAX_FREQ_LOG10 - 1.78f); // log10 frequency range is from 1.78 to 3.71. Let's scale to SEGLEN. + if (locn < 1) locn = 0; // avoid underflow + + if (locn >=SEGLEN) locn = SEGLEN-1; + uint16_t pixCol = (log10f(FFT_MajorPeak) - 1.78f) * 255.0f/(MAX_FREQ_LOG10 - 1.78f); // Scale log10 of frequency values to the 255 colour index. + if (FFT_MajorPeak < 61.0f) pixCol = 0; // handle underflow + + uint16_t bright = (int)my_magnitude; + + SEGMENT.setPixelColor(locn, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(SEGMENT.intensity+pixCol, false, PALETTE_SOLID_WRAP, 0), bright)); + + return FRAMETIME; +} // mode_freqmap() +static const char _data_FX_MODE_FREQMAP[] PROGMEM = "Freqmap@Fade rate,Starting color;,!;!;mp12=0,ssim=0,1d,fr"; // Pixels, Beatsin + + +/////////////////////// +// ** Freqmatrix // +/////////////////////// +uint16_t mode_freqmatrix(void) { // Freqmatrix. By Andreas Pleschung. + um_data_t *um_data; + if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { + // add support for no audio + um_data = simulateSound(SEGMENT.soundSim); + } + float FFT_MajorPeak = *(float*)um_data->u_data[4]; + float volumeSmth = *(float*) um_data->u_data[0]; + + if (SEGENV.call == 0) { + SEGMENT.setUpLeds(); + SEGMENT.fill(BLACK); + } + + uint8_t secondHand = micros()/(256-SEGMENT.speed)/500 % 16; + if(SEGENV.aux0 != secondHand) { + SEGENV.aux0 = secondHand; + + uint8_t sensitivity = map(SEGMENT.custom3, 0, 31, 1, 10); // reduced resolution slider + int pixVal = (volumeSmth * SEGMENT.intensity * sensitivity) / 256.0f; + if (pixVal > 255) pixVal = 255; + + float intensity = map(pixVal, 0, 255, 0, 100) / 100.0f; // make a brightness from the last avg + + CRGB color = CRGB::Black; + + if (FFT_MajorPeak > MAX_FREQUENCY) FFT_MajorPeak = 1; + // MajorPeak holds the freq. value which is most abundant in the last sample. + // With our sampling rate of 10240Hz we have a usable freq range from roughtly 80Hz to 10240/2 Hz + // we will treat everything with less than 65Hz as 0 + + if (FFT_MajorPeak < 80) { + color = CRGB::Black; + } else { + int upperLimit = 80 + 42 * SEGMENT.custom2; + int lowerLimit = 80 + 3 * SEGMENT.custom1; + uint8_t i = lowerLimit!=upperLimit ? map(FFT_MajorPeak, lowerLimit, upperLimit, 0, 255) : FFT_MajorPeak; // may under/overflow - so we enforce uint8_t + uint16_t b = 255 * intensity; + if (b > 255) b = 255; + color = CHSV(i, 240, (uint8_t)b); // implicit conversion to RGB supplied by FastLED + } + + // shift the pixels one pixel up + SEGMENT.setPixelColor(0, color); + for (int i = SEGLEN - 1; i > 0; i--) SEGMENT.setPixelColor(i, SEGMENT.getPixelColor(i-1)); //move to the left + } + + return FRAMETIME; +} // mode_freqmatrix() +static const char _data_FX_MODE_FREQMATRIX[] PROGMEM = "Freqmatrix@Time delay,Sound effect,Low bin,High bin,Sensivity;;;mp12=3,ssim=0,1d,fr"; // Corner, Beatsin + + +////////////////////// +// ** Freqpixels // +////////////////////// +// Start frequency = 60 Hz and log10(60) = 1.78 +// End frequency = 5120 Hz and lo10(5120) = 3.71 +// SEGMENT.speed select faderate +// SEGMENT.intensity select colour index +uint16_t mode_freqpixels(void) { // Freqpixel. By Andrew Tuline. + um_data_t *um_data; + if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { + // add support for no audio + um_data = simulateSound(SEGMENT.soundSim); + } + float FFT_MajorPeak = *(float*) um_data->u_data[4]; + float my_magnitude = *(float*) um_data->u_data[5] / 16.0f; + if (FFT_MajorPeak < 1) FFT_MajorPeak = 1; // log10(0) is "forbidden" (throws exception) + + uint16_t fadeRate = 2*SEGMENT.speed - SEGMENT.speed*SEGMENT.speed/255; // Get to 255 as quick as you can. + + if (SEGENV.call == 0) SEGMENT.fill(BLACK); + int fadeoutDelay = (256 - SEGMENT.speed) / 64; + if ((fadeoutDelay <= 1 ) || ((SEGENV.call % fadeoutDelay) == 0)) SEGMENT.fade_out(fadeRate); + + for (int i=0; i < SEGMENT.intensity/32+1; i++) { + uint16_t locn = random16(0,SEGLEN); + uint8_t pixCol = (log10f(FFT_MajorPeak) - 1.78f) * 255.0f/(MAX_FREQ_LOG10 - 1.78f); // Scale log10 of frequency values to the 255 colour index. + if (FFT_MajorPeak < 61.0f) pixCol = 0; // handle underflow + SEGMENT.setPixelColor(locn, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(SEGMENT.intensity+pixCol, false, PALETTE_SOLID_WRAP, 0), (int)my_magnitude)); + } + + return FRAMETIME; +} // mode_freqpixels() +static const char _data_FX_MODE_FREQPIXELS[] PROGMEM = "Freqpixels@Fade rate,Starting colour and # of pixels;;;mp12=0,ssim=0,1d,fr"; // Pixels, Beatsin + + +////////////////////// +// ** Freqwave // +////////////////////// +// Assign a color to the central (starting pixels) based on the predominant frequencies and the volume. The color is being determined by mapping the MajorPeak from the FFT +// and then mapping this to the HSV color circle. Currently we are sampling at 10240 Hz, so the highest frequency we can look at is 5120Hz. +// +// SEGMENT.custom1: the lower cut off point for the FFT. (many, most time the lowest values have very little information since they are FFT conversion artifacts. Suggested value is close to but above 0 +// SEGMENT.custom2: The high cut off point. This depends on your sound profile. Most music looks good when this slider is between 50% and 100%. +// SEGMENT.custom3: "preamp" for the audio signal for audio10. +// +// I suggest that for this effect you turn the brightness to 95%-100% but again it depends on your soundprofile you find yourself in. +// Instead of using colorpalettes, This effect works on the HSV color circle with red being the lowest frequency +// +// As a compromise between speed and accuracy we are currently sampling with 10240Hz, from which we can then determine with a 512bin FFT our max frequency is 5120Hz. +// Depending on the music stream you have you might find it useful to change the frequency mapping. +uint16_t mode_freqwave(void) { // Freqwave. By Andreas Pleschung. + um_data_t *um_data; + if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { + // add support for no audio + um_data = simulateSound(SEGMENT.soundSim); + } + float FFT_MajorPeak = *(float*) um_data->u_data[4]; + float volumeSmth = *(float*) um_data->u_data[0]; + + if (SEGENV.call == 0) { + SEGMENT.setUpLeds(); + SEGMENT.fill(BLACK); + } + + uint8_t secondHand = micros()/(256-SEGMENT.speed)/500 % 16; + if(SEGENV.aux0 != secondHand) { + SEGENV.aux0 = secondHand; + + float sensitivity = mapf(SEGMENT.custom3, 1, 31, 1, 10); // reduced resolution slider + float pixVal = volumeSmth * (float)SEGMENT.intensity / 256.0f * sensitivity; + if (pixVal > 255) pixVal = 255; + + float intensity = mapf(pixVal, 0, 255, 0, 100) / 100.0f; // make a brightness from the last avg + + CRGB color = 0; + + if (FFT_MajorPeak > MAX_FREQUENCY) FFT_MajorPeak = 1.0f; + // MajorPeak holds the freq. value which is most abundant in the last sample. + // With our sampling rate of 10240Hz we have a usable freq range from roughtly 80Hz to 10240/2 Hz + // we will treat everything with less than 65Hz as 0 + + if (FFT_MajorPeak < 80) { + color = CRGB::Black; + } else { + int upperLimit = 80 + 42 * SEGMENT.custom2; + int lowerLimit = 80 + 3 * SEGMENT.custom1; + uint8_t i = lowerLimit!=upperLimit ? map(FFT_MajorPeak, lowerLimit, upperLimit, 0, 255) : FFT_MajorPeak; // may under/overflow - so we enforce uint8_t + uint16_t b = 255.0 * intensity; + if (b > 255) b=255; + color = CHSV(i, 240, (uint8_t)b); // implicit conversion to RGB supplied by FastLED + } + + SEGMENT.setPixelColor(SEGLEN/2, color); + + // shift the pixels one pixel outwards + for (int i = SEGLEN - 1; i > SEGLEN/2; i--) SEGMENT.setPixelColor(i, SEGMENT.getPixelColor(i-1)); //move to the left + for (int i = 0; i < SEGLEN/2; i++) SEGMENT.setPixelColor(i, SEGMENT.getPixelColor(i+1)); // move to the right + } + + return FRAMETIME; +} // mode_freqwave() +static const char _data_FX_MODE_FREQWAVE[] PROGMEM = "Freqwave@Time delay,Sound effect,Low bin,High bin,Pre-amp;;;mp12=2,ssim=0,1d,fr"; // Circle, Beatsin + + +/////////////////////// +// ** Gravfreq // +/////////////////////// +uint16_t mode_gravfreq(void) { // Gravfreq. By Andrew Tuline. + + uint16_t dataSize = sizeof(gravity); + if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed + Gravity* gravcen = reinterpret_cast(SEGENV.data); + + um_data_t *um_data; + if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { + // add support for no audio + um_data = simulateSound(SEGMENT.soundSim); + } + float FFT_MajorPeak = *(float*) um_data->u_data[4]; + float volumeSmth = *(float*) um_data->u_data[0]; + if (FFT_MajorPeak < 1) FFT_MajorPeak = 1; // log10(0) is "forbidden" (throws exception) + + SEGMENT.fade_out(250); + + float segmentSampleAvg = volumeSmth * (float)SEGMENT.intensity / 255.0; + segmentSampleAvg *= 0.125; // divide by 8, to compensate for later "sensitivty" upscaling + + float mySampleAvg = mapf(segmentSampleAvg*2.0, 0,32, 0, (float)SEGLEN/2.0); // map to pixels availeable in current segment + int tempsamp = constrain(mySampleAvg,0,SEGLEN/2); // Keep the sample from overflowing. + uint8_t gravity = 8 - SEGMENT.speed/32; + + for (int i=0; i= gravcen->topLED) + gravcen->topLED = tempsamp-1; + else if (gravcen->gravityCounter % gravity == 0) + gravcen->topLED--; + + if (gravcen->topLED >= 0) { + SEGMENT.setPixelColor(gravcen->topLED+SEGLEN/2, CRGB::Gray); + SEGMENT.setPixelColor(SEGLEN/2-1-gravcen->topLED, CRGB::Gray); + } + gravcen->gravityCounter = (gravcen->gravityCounter + 1) % gravity; + + return FRAMETIME; +} // mode_gravfreq() +static const char _data_FX_MODE_GRAVFREQ[] PROGMEM = "Gravfreq@Rate of fall,Sensivity;,!;!;ix=128,mp12=0,ssim=0,1d,fr"; // Pixels, Beatsin + + +////////////////////// +// ** Noisemove // +////////////////////// +uint16_t mode_noisemove(void) { // Noisemove. By: Andrew Tuline + um_data_t *um_data; + if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { + // add support for no audio + um_data = simulateSound(SEGMENT.soundSim); + } + uint8_t *fftResult = (uint8_t*)um_data->u_data[2]; + + if (SEGENV.call == 0) SEGMENT.fill(BLACK); + //SEGMENT.fade_out(224); // Just in case something doesn't get faded. + int fadeoutDelay = (256 - SEGMENT.speed) / 96; + if ((fadeoutDelay <= 1 ) || ((SEGENV.call % fadeoutDelay) == 0)) SEGMENT.fadeToBlackBy(4+ SEGMENT.speed/4); + + uint8_t numBins = map(SEGMENT.intensity,0,255,0,16); // Map slider to fftResult bins. + for (int i=0; iu_data[4]; + float my_magnitude = *(float*) um_data->u_data[5] / 16.0f; + + if (SEGENV.call == 0) SEGMENT.fill(BLACK); + SEGMENT.fadeToBlackBy(16); // Just in case something doesn't get faded. + + float frTemp = FFT_MajorPeak; + uint8_t octCount = 0; // Octave counter. + uint8_t volTemp = 0; + + volTemp = 32.0f + my_magnitude * 1.5f; // brightness = volume (overflows are handled in next lines) + if (my_magnitude < 48) volTemp = 0; // We need to squelch out the background noise. + if (my_magnitude > 144) volTemp = 255; // everything above this is full brightness + + while ( frTemp > 249 ) { + octCount++; // This should go up to 5. + frTemp = frTemp/2; + } + + frTemp -=132; // This should give us a base musical note of C3 + frTemp = fabs(frTemp * 2.1); // Fudge factors to compress octave range starting at 0 and going to 255; + + uint16_t i = map(beatsin8(8+octCount*4, 0, 255, 0, octCount*8), 0, 255, 0, SEGLEN-1); + i = constrain(i, 0, SEGLEN-1); + SEGMENT.addPixelColor(i, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette((uint8_t)frTemp, false, PALETTE_SOLID_WRAP, 0), volTemp)); + + return FRAMETIME; +} // mode_rocktaves() +static const char _data_FX_MODE_ROCKTAVES[] PROGMEM = "Rocktaves@;,!;!;mp12=1,ssim=0,1d,fr"; // Bar, Beatsin + + +/////////////////////// +// ** Waterfall // +/////////////////////// +// Combines peak detection with FFT_MajorPeak and FFT_Magnitude. +uint16_t mode_waterfall(void) { // Waterfall. By: Andrew Tuline + if (SEGENV.call == 0) SEGMENT.fill(BLACK); + + um_data_t *um_data; + if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { + // add support for no audio + um_data = simulateSound(SEGMENT.soundSim); + } + uint8_t samplePeak = *(uint8_t*)um_data->u_data[3]; + float FFT_MajorPeak = *(float*) um_data->u_data[4]; + uint8_t *maxVol = (uint8_t*)um_data->u_data[6]; + uint8_t *binNum = (uint8_t*)um_data->u_data[7]; + float my_magnitude = *(float*) um_data->u_data[5] / 8.0f; + + if (FFT_MajorPeak < 1) FFT_MajorPeak = 1; // log10(0) is "forbidden" (throws exception) + + if (SEGENV.call == 0) { + SEGMENT.setUpLeds(); + SEGMENT.fill(BLACK); + SEGENV.aux0 = 255; + SEGMENT.custom1 = *binNum; + SEGMENT.custom2 = *maxVol * 2; + } + + *binNum = SEGMENT.custom1; // Select a bin. + *maxVol = SEGMENT.custom2 / 2; // Our volume comparator. + + uint8_t secondHand = micros() / (256-SEGMENT.speed)/500 + 1 % 16; + if (SEGENV.aux0 != secondHand) { // Triggered millis timing. + SEGENV.aux0 = secondHand; + + //uint8_t pixCol = (log10f((float)FFT_MajorPeak) - 2.26f) * 177; // 10Khz sampling - log10 frequency range is from 2.26 (182hz) to 3.7 (5012hz). Let's scale accordingly. + uint8_t pixCol = (log10f(FFT_MajorPeak) - 2.26f) * 150; // 22Khz sampling - log10 frequency range is from 2.26 (182hz) to 3.967 (9260hz). Let's scale accordingly. + if (FFT_MajorPeak < 182.0f) pixCol = 0; // handle underflow + + if (samplePeak) { + SEGMENT.setPixelColor(SEGLEN-1, CHSV(92,92,92)); + } else { + SEGMENT.setPixelColor(SEGLEN-1, color_blend(SEGCOLOR(1), SEGMENT.color_from_palette(pixCol+SEGMENT.intensity, false, PALETTE_SOLID_WRAP, 0), (int)my_magnitude)); + } + for (int i=0; i(SEGENV.data); //array of previous bar heights per frequency band + + um_data_t *um_data; + if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { + // add support for no audio + um_data = simulateSound(SEGMENT.soundSim); + } + uint8_t *fftResult = (uint8_t*)um_data->u_data[2]; + + if (SEGENV.call == 0) for (int i=0; i= (256U - SEGMENT.intensity)) { + SEGENV.step = millis(); + rippleTime = true; + } + + if (SEGENV.call == 0) SEGMENT.fill(BLACK); + int fadeoutDelay = (256 - SEGMENT.speed) / 64; + if ((fadeoutDelay <= 1 ) || ((SEGENV.call % fadeoutDelay) == 0)) SEGMENT.fadeToBlackBy(SEGMENT.speed); + + for (int x=0; x < cols; x++) { + uint8_t band = map(x, 0, cols-1, 0, NUM_BANDS - 1); + if (NUM_BANDS < 16) band = map(band, 0, NUM_BANDS - 1, 0, 15); // always use full range. comment out this line to get the previous behaviour. + band = constrain(band, 0, 15); + uint16_t colorIndex = band * 17; + uint16_t barHeight = map(fftResult[band], 0, 255, 0, rows); // do not subtract -1 from rows here + if (barHeight > previousBarHeight[x]) previousBarHeight[x] = barHeight; //drive the peak up + + uint32_t ledColor = BLACK; + for (int y=0; y < barHeight; y++) { + if (SEGMENT.check1) //color_vertical / color bars toggle + colorIndex = map(y, 0, rows-1, 0, 255); + + ledColor = SEGMENT.color_from_palette(colorIndex, false, PALETTE_SOLID_WRAP, 0); + SEGMENT.setPixelColorXY(x, rows-1 - y, ledColor); + } + if (previousBarHeight[x] > 0) + SEGMENT.setPixelColorXY(x, rows - previousBarHeight[x], (SEGCOLOR(2) != BLACK) ? SEGCOLOR(2) : ledColor); + + if (rippleTime && previousBarHeight[x]>0) previousBarHeight[x]--; //delay/ripple effect + } + + return FRAMETIME; +} // mode_2DGEQ() +static const char _data_FX_MODE_2DGEQ[] PROGMEM = "GEQ@Fade speed,Ripple decay,# of bands,,,Color bars,,;!,,Peak Color;!;c1=255,c2=64,pal=11,ssim=0,2d,fr"; // Beatsin + + +///////////////////////// +// ** 2D Funky plank // +///////////////////////// +uint16_t mode_2DFunkyPlank(void) { // Written by ??? Adapted by Will Tatam. + if (!strip.isMatrix) return mode_static(); // not a 2D set-up + + const uint16_t cols = SEGMENT.virtualWidth(); + const uint16_t rows = SEGMENT.virtualHeight(); + + int NUMB_BANDS = map(SEGMENT.custom1, 0, 255, 1, 16); + int barWidth = (cols / NUMB_BANDS); + int bandInc = 1; + if (barWidth == 0) { + // Matrix narrower than fft bands + barWidth = 1; + bandInc = (NUMB_BANDS / cols); + } + + um_data_t *um_data; + if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { + // add support for no audio + um_data = simulateSound(SEGMENT.soundSim); + } + uint8_t *fftResult = (uint8_t*)um_data->u_data[2]; + + if (SEGENV.call == 0) { + SEGMENT.setUpLeds(); + SEGMENT.fill(BLACK); + } + + uint8_t secondHand = micros()/(256-SEGMENT.speed)/500+1 % 64; + if (SEGENV.aux0 != secondHand) { // Triggered millis timing. + SEGENV.aux0 = secondHand; + + // display values of + int b = 0; + for (int band = 0; band < NUMB_BANDS; band += bandInc, b++) { + int hue = fftResult[band % 16]; + int v = map(fftResult[band % 16], 0, 255, 10, 255); + for (int w = 0; w < barWidth; w++) { + int xpos = (barWidth * b) + w; + SEGMENT.setPixelColorXY(xpos, 0, CHSV(hue, 255, v)); + } + } + + // Update the display: + for (int i = (rows - 1); i > 0; i--) { + for (int j = (cols - 1); j >= 0; j--) { + SEGMENT.setPixelColorXY(j, i, SEGMENT.getPixelColorXY(j, i-1)); + } + } + } + + return FRAMETIME; +} // mode_2DFunkyPlank +static const char _data_FX_MODE_2DFUNKYPLANK[] PROGMEM = "Funky Plank@Scroll speed,,# of bands;;;ssim=0,2d,fr"; // Beatsin + + +///////////////////////// +// 2D Akemi // +///////////////////////// +static uint8_t akemi[] PROGMEM = { + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,2,2,3,3,3,3,3,3,2,2,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,2,3,3,0,0,0,0,0,0,3,3,2,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,2,3,0,0,0,6,5,5,4,0,0,0,3,2,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,2,3,0,0,6,6,5,5,5,5,4,4,0,0,3,2,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,2,3,0,6,5,5,5,5,5,5,5,5,4,0,3,2,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,2,3,0,6,5,5,5,5,5,5,5,5,5,5,4,0,3,2,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,3,2,0,6,5,5,5,5,5,5,5,5,5,5,4,0,2,3,0,0,0,0,0,0,0, + 0,0,0,0,0,0,3,2,3,6,5,5,7,7,5,5,5,5,7,7,5,5,4,3,2,3,0,0,0,0,0,0, + 0,0,0,0,0,2,3,1,3,6,5,1,7,7,7,5,5,1,7,7,7,5,4,3,1,3,2,0,0,0,0,0, + 0,0,0,0,0,8,3,1,3,6,5,1,7,7,7,5,5,1,7,7,7,5,4,3,1,3,8,0,0,0,0,0, + 0,0,0,0,0,8,3,1,3,6,5,5,1,1,5,5,5,5,1,1,5,5,4,3,1,3,8,0,0,0,0,0, + 0,0,0,0,0,2,3,1,3,6,5,5,5,5,5,5,5,5,5,5,5,5,4,3,1,3,2,0,0,0,0,0, + 0,0,0,0,0,0,3,2,3,6,5,5,5,5,5,5,5,5,5,5,5,5,4,3,2,3,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,6,5,5,5,5,5,7,7,5,5,5,5,5,4,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,6,5,5,5,5,5,5,5,5,5,5,5,5,4,0,0,0,0,0,0,0,0,0, + 1,0,0,0,0,0,0,0,0,6,5,5,5,5,5,5,5,5,5,5,5,5,4,0,0,0,0,0,0,0,0,2, + 0,2,2,2,0,0,0,0,0,6,5,5,5,5,5,5,5,5,5,5,5,5,4,0,0,0,0,0,2,2,2,0, + 0,0,0,3,2,0,0,0,6,5,4,4,4,4,4,4,4,4,4,4,4,4,4,4,0,0,0,2,2,0,0,0, + 0,0,0,3,2,0,0,0,6,5,5,5,5,5,5,5,5,5,5,5,5,5,5,4,0,0,0,2,3,0,0,0, + 0,0,0,0,3,2,0,0,0,0,3,3,0,3,3,0,0,3,3,0,3,3,0,0,0,0,2,2,0,0,0,0, + 0,0,0,0,3,2,0,0,0,0,3,2,0,3,2,0,0,3,2,0,3,2,0,0,0,0,2,3,0,0,0,0, + 0,0,0,0,0,3,2,0,0,3,2,0,0,3,2,0,0,3,2,0,0,3,2,0,0,2,3,0,0,0,0,0, + 0,0,0,0,0,3,2,2,2,2,0,0,0,3,2,0,0,3,2,0,0,0,3,2,2,2,3,0,0,0,0,0, + 0,0,0,0,0,0,3,3,3,0,0,0,0,3,2,0,0,3,2,0,0,0,0,3,3,3,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,3,2,0,0,3,2,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,3,2,0,0,3,2,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,3,2,0,0,3,2,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,3,2,0,0,3,2,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,3,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,3,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +}; + +uint16_t mode_2DAkemi(void) { + if (!strip.isMatrix) return mode_static(); // not a 2D set-up + + const uint16_t cols = SEGMENT.virtualWidth(); + const uint16_t rows = SEGMENT.virtualHeight(); + + uint16_t counter = (strip.now * ((SEGMENT.speed >> 2) +2)) & 0xFFFF; + counter = counter >> 8; + + const float lightFactor = 0.15f; + const float normalFactor = 0.4f; + + um_data_t *um_data; + if (!usermods.getUMData(&um_data, USERMOD_ID_AUDIOREACTIVE)) { + um_data = simulateSound(SEGMENT.soundSim); + } + uint8_t *fftResult = (uint8_t*)um_data->u_data[2]; + float base = fftResult[0]/255.0f; + + //draw and color Akemi + for (int y=0; y < rows; y++) for (int x=0; x < cols; x++) { + CRGB color; + CRGB soundColor = ORANGE; + CRGB faceColor = SEGMENT.color_wheel(counter); + CRGB armsAndLegsColor = SEGCOLOR(1) > 0 ? SEGCOLOR(1) : 0xFFE0A0; //default warmish white 0xABA8FF; //0xFF52e5;// + uint8_t ak = pgm_read_byte_near(akemi + ((y * 32)/rows) * 32 + (x * 32)/cols); // akemi[(y * 32)/rows][(x * 32)/cols] + switch (ak) { + case 3: armsAndLegsColor.r *= lightFactor; armsAndLegsColor.g *= lightFactor; armsAndLegsColor.b *= lightFactor; color = armsAndLegsColor; break; //light arms and legs 0x9B9B9B + case 2: armsAndLegsColor.r *= normalFactor; armsAndLegsColor.g *= normalFactor; armsAndLegsColor.b *= normalFactor; color = armsAndLegsColor; break; //normal arms and legs 0x888888 + case 1: color = armsAndLegsColor; break; //dark arms and legs 0x686868 + case 6: faceColor.r *= lightFactor; faceColor.g *= lightFactor; faceColor.b *= lightFactor; color=faceColor; break; //light face 0x31AAFF + case 5: faceColor.r *= normalFactor; faceColor.g *= normalFactor; faceColor.b *= normalFactor; color=faceColor; break; //normal face 0x0094FF + case 4: color = faceColor; break; //dark face 0x007DC6 + case 7: color = SEGCOLOR(2) > 0 ? SEGCOLOR(2) : 0xFFFFFF; break; //eyes and mouth default white + case 8: if (base > 0.4) {soundColor.r *= base; soundColor.g *= base; soundColor.b *= base; color=soundColor;} else color = armsAndLegsColor; break; + default: color = BLACK; break; + } + + if (SEGMENT.intensity > 128 && fftResult && fftResult[0] > 128) { //dance if base is high + SEGMENT.setPixelColorXY(x, 0, BLACK); + SEGMENT.setPixelColorXY(x, y+1, color); + } else + SEGMENT.setPixelColorXY(x, y, color); + } + + //add geq left and right + if (um_data && fftResult) { + for (int x=0; x < cols/8; x++) { + uint16_t band = x * cols/8; + band = constrain(band, 0, 15); + uint16_t barHeight = map(fftResult[band], 0, 255, 0, 17*rows/32); + CRGB color = SEGMENT.color_from_palette((band * 35), false, PALETTE_SOLID_WRAP, 0); + + for (int y=0; y < barHeight; y++) { + SEGMENT.setPixelColorXY(x, rows/2-y, color); + SEGMENT.setPixelColorXY(cols-1-x, rows/2-y, color); + } + } + } + + return FRAMETIME; +} // mode_2DAkemi +static const char _data_FX_MODE_2DAKEMI[] PROGMEM = "Akemi@Color speed,Dance;Head palette,Arms & Legs,Eyes & Mouth;Face palette;ssim=0,2d,fr"; //beatsin +#endif // WLED_DISABLE_2D + + +////////////////////////////////////////////////////////////////////////////////////////// +// mode data +static const char _data_RESERVED[] PROGMEM = "RSVD"; + +// add (or replace reserved) effect mode and data into vector +// use id==255 to find unallocatd gaps (with "Reserved" data string) +// if vector size() is smaller than id (single) data is appended at the end (regardless of id) +void WS2812FX::addEffect(uint8_t id, mode_ptr mode_fn, const char *mode_name) { + if (id == 255) { // find empty slot + for (size_t i=1; i<_mode.size(); i++) if (_modeData[i] == _data_RESERVED) { id = i; break; } + } + if (id < _mode.size()) { + if (_modeData[id] != _data_RESERVED) return; // do not overwrite alerady added effect + _mode[id] = mode_fn; + _modeData[id] = mode_name; + } else { + _mode.push_back(mode_fn); + _modeData.push_back(mode_name); + if (_modeCount < _mode.size()) _modeCount++; + } +} + +void WS2812FX::setupEffectData() { + // Solid must be first! (assuming vector is empty upon call to setup) + _mode.push_back(&mode_static); + _modeData.push_back(_data_FX_MODE_STATIC); + // fill reserved word in case there will be any gaps in the array + for (size_t i=1; i<_modeCount; i++) { + _mode.push_back(&mode_static); + _modeData.push_back(_data_RESERVED); + } + // now replace all pre-allocated effects + // --- 1D non-audio effects --- + addEffect(FX_MODE_BLINK, &mode_blink, _data_FX_MODE_BLINK); + addEffect(FX_MODE_COLOR_WIPE, &mode_color_wipe, _data_FX_MODE_COLOR_WIPE); + addEffect(FX_MODE_COLOR_WIPE_RANDOM, &mode_color_wipe_random, _data_FX_MODE_COLOR_WIPE_RANDOM); + addEffect(FX_MODE_RANDOM_COLOR, &mode_random_color, _data_FX_MODE_RANDOM_COLOR); + addEffect(FX_MODE_COLOR_SWEEP, &mode_color_sweep, _data_FX_MODE_COLOR_SWEEP); + addEffect(FX_MODE_DYNAMIC, &mode_dynamic, _data_FX_MODE_DYNAMIC); + addEffect(FX_MODE_RAINBOW, &mode_rainbow, _data_FX_MODE_RAINBOW); + addEffect(FX_MODE_RAINBOW_CYCLE, &mode_rainbow_cycle, _data_FX_MODE_RAINBOW_CYCLE); + addEffect(FX_MODE_SCAN, &mode_scan, _data_FX_MODE_SCAN); + addEffect(FX_MODE_DUAL_SCAN, &mode_dual_scan, _data_FX_MODE_DUAL_SCAN); + addEffect(FX_MODE_FADE, &mode_fade, _data_FX_MODE_FADE); + addEffect(FX_MODE_THEATER_CHASE, &mode_theater_chase, _data_FX_MODE_THEATER_CHASE); + addEffect(FX_MODE_THEATER_CHASE_RAINBOW, &mode_theater_chase_rainbow, _data_FX_MODE_THEATER_CHASE_RAINBOW); + addEffect(FX_MODE_SAW, &mode_saw, _data_FX_MODE_SAW); + addEffect(FX_MODE_TWINKLE, &mode_twinkle, _data_FX_MODE_TWINKLE); + addEffect(FX_MODE_DISSOLVE, &mode_dissolve, _data_FX_MODE_DISSOLVE); + addEffect(FX_MODE_DISSOLVE_RANDOM, &mode_dissolve_random, _data_FX_MODE_DISSOLVE_RANDOM); + addEffect(FX_MODE_SPARKLE, &mode_sparkle, _data_FX_MODE_SPARKLE); + addEffect(FX_MODE_FLASH_SPARKLE, &mode_flash_sparkle, _data_FX_MODE_FLASH_SPARKLE); + addEffect(FX_MODE_HYPER_SPARKLE, &mode_hyper_sparkle, _data_FX_MODE_HYPER_SPARKLE); + addEffect(FX_MODE_STROBE, &mode_strobe, _data_FX_MODE_STROBE); + addEffect(FX_MODE_STROBE_RAINBOW, &mode_strobe_rainbow, _data_FX_MODE_STROBE_RAINBOW); + addEffect(FX_MODE_MULTI_STROBE, &mode_multi_strobe, _data_FX_MODE_MULTI_STROBE); + addEffect(FX_MODE_BLINK_RAINBOW, &mode_blink_rainbow, _data_FX_MODE_BLINK_RAINBOW); + addEffect(FX_MODE_ANDROID, &mode_android, _data_FX_MODE_ANDROID); + addEffect(FX_MODE_CHASE_COLOR, &mode_chase_color, _data_FX_MODE_CHASE_COLOR); + addEffect(FX_MODE_CHASE_RANDOM, &mode_chase_random, _data_FX_MODE_CHASE_RANDOM); + addEffect(FX_MODE_CHASE_RAINBOW, &mode_chase_rainbow, _data_FX_MODE_CHASE_RAINBOW); + addEffect(FX_MODE_CHASE_FLASH, &mode_chase_flash, _data_FX_MODE_CHASE_FLASH); + addEffect(FX_MODE_CHASE_FLASH_RANDOM, &mode_chase_flash_random, _data_FX_MODE_CHASE_FLASH_RANDOM); + addEffect(FX_MODE_CHASE_RAINBOW_WHITE, &mode_chase_rainbow_white, _data_FX_MODE_CHASE_RAINBOW_WHITE); + addEffect(FX_MODE_COLORFUL, &mode_colorful, _data_FX_MODE_COLORFUL); + addEffect(FX_MODE_TRAFFIC_LIGHT, &mode_traffic_light, _data_FX_MODE_TRAFFIC_LIGHT); + addEffect(FX_MODE_COLOR_SWEEP_RANDOM, &mode_color_sweep_random, _data_FX_MODE_COLOR_SWEEP_RANDOM); + addEffect(FX_MODE_RUNNING_COLOR, &mode_running_color, _data_FX_MODE_RUNNING_COLOR); + addEffect(FX_MODE_AURORA, &mode_aurora, _data_FX_MODE_AURORA); + addEffect(FX_MODE_RUNNING_RANDOM, &mode_running_random, _data_FX_MODE_RUNNING_RANDOM); + addEffect(FX_MODE_LARSON_SCANNER, &mode_larson_scanner, _data_FX_MODE_LARSON_SCANNER); + addEffect(FX_MODE_COMET, &mode_comet, _data_FX_MODE_COMET); + addEffect(FX_MODE_FIREWORKS, &mode_fireworks, _data_FX_MODE_FIREWORKS); + addEffect(FX_MODE_RAIN, &mode_rain, _data_FX_MODE_RAIN); + addEffect(FX_MODE_TETRIX, &mode_tetrix, _data_FX_MODE_TETRIX); + addEffect(FX_MODE_FIRE_FLICKER, &mode_fire_flicker, _data_FX_MODE_FIRE_FLICKER); + addEffect(FX_MODE_GRADIENT, &mode_gradient, _data_FX_MODE_GRADIENT); + addEffect(FX_MODE_LOADING, &mode_loading, _data_FX_MODE_LOADING); + addEffect(FX_MODE_WAVESINS, &mode_wavesins, _data_FX_MODE_WAVESINS); + addEffect(FX_MODE_FAIRY, &mode_fairy, _data_FX_MODE_FAIRY); + addEffect(FX_MODE_TWO_DOTS, &mode_two_dots, _data_FX_MODE_TWO_DOTS); + addEffect(FX_MODE_FAIRYTWINKLE, &mode_fairytwinkle, _data_FX_MODE_FAIRYTWINKLE); + addEffect(FX_MODE_RUNNING_DUAL, &mode_running_dual, _data_FX_MODE_RUNNING_DUAL); + addEffect(FX_MODE_PERLINMOVE, &mode_perlinmove, _data_FX_MODE_PERLINMOVE); + addEffect(FX_MODE_TRICOLOR_CHASE, &mode_tricolor_chase, _data_FX_MODE_TRICOLOR_CHASE); + addEffect(FX_MODE_TRICOLOR_WIPE, &mode_tricolor_wipe, _data_FX_MODE_TRICOLOR_WIPE); + addEffect(FX_MODE_TRICOLOR_FADE, &mode_tricolor_fade, _data_FX_MODE_TRICOLOR_FADE); + addEffect(FX_MODE_BREATH, &mode_breath, _data_FX_MODE_BREATH); + addEffect(FX_MODE_RUNNING_LIGHTS, &mode_running_lights, _data_FX_MODE_RUNNING_LIGHTS); + addEffect(FX_MODE_LIGHTNING, &mode_lightning, _data_FX_MODE_LIGHTNING); + addEffect(FX_MODE_ICU, &mode_icu, _data_FX_MODE_ICU); + addEffect(FX_MODE_MULTI_COMET, &mode_multi_comet, _data_FX_MODE_MULTI_COMET); + addEffect(FX_MODE_DUAL_LARSON_SCANNER, &mode_dual_larson_scanner, _data_FX_MODE_DUAL_LARSON_SCANNER); + addEffect(FX_MODE_RANDOM_CHASE, &mode_random_chase, _data_FX_MODE_RANDOM_CHASE); + addEffect(FX_MODE_OSCILLATE, &mode_oscillate, _data_FX_MODE_OSCILLATE); + addEffect(FX_MODE_FIRE_2012, &mode_fire_2012, _data_FX_MODE_FIRE_2012); + addEffect(FX_MODE_PRIDE_2015, &mode_pride_2015, _data_FX_MODE_PRIDE_2015); + addEffect(FX_MODE_BPM, &mode_bpm, _data_FX_MODE_BPM); + addEffect(FX_MODE_JUGGLE, &mode_juggle, _data_FX_MODE_JUGGLE); + addEffect(FX_MODE_PALETTE, &mode_palette, _data_FX_MODE_PALETTE); + addEffect(FX_MODE_COLORWAVES, &mode_colorwaves, _data_FX_MODE_COLORWAVES); + addEffect(FX_MODE_FILLNOISE8, &mode_fillnoise8, _data_FX_MODE_FILLNOISE8); + addEffect(FX_MODE_NOISE16_1, &mode_noise16_1, _data_FX_MODE_NOISE16_1); + addEffect(FX_MODE_NOISE16_2, &mode_noise16_2, _data_FX_MODE_NOISE16_2); + addEffect(FX_MODE_NOISE16_3, &mode_noise16_3, _data_FX_MODE_NOISE16_3); + addEffect(FX_MODE_NOISE16_4, &mode_noise16_4, _data_FX_MODE_NOISE16_4); + addEffect(FX_MODE_COLORTWINKLE, &mode_colortwinkle, _data_FX_MODE_COLORTWINKLE); + addEffect(FX_MODE_LAKE, &mode_lake, _data_FX_MODE_LAKE); + addEffect(FX_MODE_METEOR, &mode_meteor, _data_FX_MODE_METEOR); + addEffect(FX_MODE_METEOR_SMOOTH, &mode_meteor_smooth, _data_FX_MODE_METEOR_SMOOTH); + addEffect(FX_MODE_RAILWAY, &mode_railway, _data_FX_MODE_RAILWAY); + addEffect(FX_MODE_RIPPLE, &mode_ripple, _data_FX_MODE_RIPPLE); + addEffect(FX_MODE_TWINKLEFOX, &mode_twinklefox, _data_FX_MODE_TWINKLEFOX); + addEffect(FX_MODE_TWINKLECAT, &mode_twinklecat, _data_FX_MODE_TWINKLECAT); + addEffect(FX_MODE_HALLOWEEN_EYES, &mode_halloween_eyes, _data_FX_MODE_HALLOWEEN_EYES); + addEffect(FX_MODE_STATIC_PATTERN, &mode_static_pattern, _data_FX_MODE_STATIC_PATTERN); + addEffect(FX_MODE_TRI_STATIC_PATTERN, &mode_tri_static_pattern, _data_FX_MODE_TRI_STATIC_PATTERN); + addEffect(FX_MODE_SPOTS, &mode_spots, _data_FX_MODE_SPOTS); + addEffect(FX_MODE_SPOTS_FADE, &mode_spots_fade, _data_FX_MODE_SPOTS_FADE); + addEffect(FX_MODE_GLITTER, &mode_glitter, _data_FX_MODE_GLITTER); + addEffect(FX_MODE_CANDLE, &mode_candle, _data_FX_MODE_CANDLE); + addEffect(FX_MODE_STARBURST, &mode_starburst, _data_FX_MODE_STARBURST); + addEffect(FX_MODE_EXPLODING_FIREWORKS, &mode_exploding_fireworks, _data_FX_MODE_EXPLODING_FIREWORKS); + addEffect(FX_MODE_BOUNCINGBALLS, &mode_bouncing_balls, _data_FX_MODE_BOUNCINGBALLS); + addEffect(FX_MODE_SINELON, &mode_sinelon, _data_FX_MODE_SINELON); + addEffect(FX_MODE_SINELON_DUAL, &mode_sinelon_dual, _data_FX_MODE_SINELON_DUAL); + addEffect(FX_MODE_SINELON_RAINBOW, &mode_sinelon_rainbow, _data_FX_MODE_SINELON_RAINBOW); + addEffect(FX_MODE_POPCORN, &mode_popcorn, _data_FX_MODE_POPCORN); + addEffect(FX_MODE_DRIP, &mode_drip, _data_FX_MODE_DRIP); + addEffect(FX_MODE_PLASMA, &mode_plasma, _data_FX_MODE_PLASMA); + addEffect(FX_MODE_PERCENT, &mode_percent, _data_FX_MODE_PERCENT); + addEffect(FX_MODE_RIPPLE_RAINBOW, &mode_ripple_rainbow, _data_FX_MODE_RIPPLE_RAINBOW); + addEffect(FX_MODE_HEARTBEAT, &mode_heartbeat, _data_FX_MODE_HEARTBEAT); + addEffect(FX_MODE_PACIFICA, &mode_pacifica, _data_FX_MODE_PACIFICA); + addEffect(FX_MODE_CANDLE_MULTI, &mode_candle_multi, _data_FX_MODE_CANDLE_MULTI); + addEffect(FX_MODE_SOLID_GLITTER, &mode_solid_glitter, _data_FX_MODE_SOLID_GLITTER); + addEffect(FX_MODE_SUNRISE, &mode_sunrise, _data_FX_MODE_SUNRISE); + addEffect(FX_MODE_PHASED, &mode_phased, _data_FX_MODE_PHASED); + addEffect(FX_MODE_TWINKLEUP, &mode_twinkleup, _data_FX_MODE_TWINKLEUP); + addEffect(FX_MODE_NOISEPAL, &mode_noisepal, _data_FX_MODE_NOISEPAL); + addEffect(FX_MODE_SINEWAVE, &mode_sinewave, _data_FX_MODE_SINEWAVE); + addEffect(FX_MODE_PHASEDNOISE, &mode_phased_noise, _data_FX_MODE_PHASEDNOISE); + addEffect(FX_MODE_FLOW, &mode_flow, _data_FX_MODE_FLOW); + addEffect(FX_MODE_CHUNCHUN, &mode_chunchun, _data_FX_MODE_CHUNCHUN); + addEffect(FX_MODE_DANCING_SHADOWS, &mode_dancing_shadows, _data_FX_MODE_DANCING_SHADOWS); + addEffect(FX_MODE_WASHING_MACHINE, &mode_washing_machine, _data_FX_MODE_WASHING_MACHINE); + addEffect(FX_MODE_FLOWSTRIPE, &mode_FlowStripe, _data_FX_MODE_FLOWSTRIPE); + addEffect(FX_MODE_BLENDS, &mode_blends, _data_FX_MODE_BLENDS); + addEffect(FX_MODE_TV_SIMULATOR, &mode_tv_simulator, _data_FX_MODE_TV_SIMULATOR); + addEffect(FX_MODE_DYNAMIC_SMOOTH, &mode_dynamic_smooth, _data_FX_MODE_DYNAMIC_SMOOTH); + + // --- 2D non-audio effects --- +#ifndef WLED_DISABLE_2D + addEffect(FX_MODE_2DSPACESHIPS, &mode_2Dspaceships, _data_FX_MODE_2DSPACESHIPS); + addEffect(FX_MODE_2DCRAZYBEES, &mode_2Dcrazybees, _data_FX_MODE_2DCRAZYBEES); + addEffect(FX_MODE_2DGHOSTRIDER, &mode_2Dghostrider, _data_FX_MODE_2DGHOSTRIDER); + addEffect(FX_MODE_2DBLOBS, &mode_2Dfloatingblobs, _data_FX_MODE_2DBLOBS); + addEffect(FX_MODE_2DSCROLLTEXT, &mode_2Dscrollingtext, _data_FX_MODE_2DSCROLLTEXT); + addEffect(FX_MODE_2DDRIFTROSE, &mode_2Ddriftrose, _data_FX_MODE_2DDRIFTROSE); + addEffect(FX_MODE_2DBLACKHOLE, &mode_2DBlackHole, _data_FX_MODE_2DBLACKHOLE); + addEffect(FX_MODE_2DDNASPIRAL, &mode_2DDNASpiral, _data_FX_MODE_2DDNASPIRAL); + addEffect(FX_MODE_2DHIPHOTIC, &mode_2DHiphotic, _data_FX_MODE_2DHIPHOTIC); + addEffect(FX_MODE_2DPLASMABALL, &mode_2DPlasmaball, _data_FX_MODE_2DPLASMABALL); + addEffect(FX_MODE_2DSINDOTS, &mode_2DSindots, _data_FX_MODE_2DSINDOTS); + addEffect(FX_MODE_2DFRIZZLES, &mode_2DFrizzles, _data_FX_MODE_2DFRIZZLES); + addEffect(FX_MODE_2DLISSAJOUS, &mode_2DLissajous, _data_FX_MODE_2DLISSAJOUS); + addEffect(FX_MODE_2DPOLARLIGHTS, &mode_2DPolarLights, _data_FX_MODE_2DPOLARLIGHTS); + addEffect(FX_MODE_2DTARTAN, &mode_2Dtartan, _data_FX_MODE_2DTARTAN); + addEffect(FX_MODE_2DGAMEOFLIFE, &mode_2Dgameoflife, _data_FX_MODE_2DGAMEOFLIFE); + addEffect(FX_MODE_2DJULIA, &mode_2DJulia, _data_FX_MODE_2DJULIA); + addEffect(FX_MODE_2DCOLOREDBURSTS, &mode_2DColoredBursts, _data_FX_MODE_2DCOLOREDBURSTS); + addEffect(FX_MODE_2DSUNRADIATION, &mode_2DSunradiation, _data_FX_MODE_2DSUNRADIATION); + addEffect(FX_MODE_2DNOISE, &mode_2Dnoise, _data_FX_MODE_2DNOISE); + addEffect(FX_MODE_2DFIRENOISE, &mode_2Dfirenoise, _data_FX_MODE_2DFIRENOISE); + addEffect(FX_MODE_2DSQUAREDSWIRL, &mode_2Dsquaredswirl, _data_FX_MODE_2DSQUAREDSWIRL); + addEffect(FX_MODE_2DDNA, &mode_2Ddna, _data_FX_MODE_2DDNA); + addEffect(FX_MODE_2DMATRIX, &mode_2Dmatrix, _data_FX_MODE_2DMATRIX); + addEffect(FX_MODE_2DMETABALLS, &mode_2Dmetaballs, _data_FX_MODE_2DMETABALLS); + addEffect(FX_MODE_2DPULSER, &mode_2DPulser, _data_FX_MODE_2DPULSER); + addEffect(FX_MODE_2DDRIFT, &mode_2DDrift, _data_FX_MODE_2DDRIFT); + // --- 2D audio effects --- + addEffect(FX_MODE_2DWAVERLY, &mode_2DWaverly, _data_FX_MODE_2DWAVERLY); + addEffect(FX_MODE_2DSWIRL, &mode_2DSwirl, _data_FX_MODE_2DSWIRL); + addEffect(FX_MODE_2DAKEMI, &mode_2DAkemi, _data_FX_MODE_2DAKEMI); + addEffect(FX_MODE_2DGEQ, &mode_2DGEQ, _data_FX_MODE_2DGEQ); + addEffect(FX_MODE_2DFUNKYPLANK, &mode_2DFunkyPlank, _data_FX_MODE_2DFUNKYPLANK); +#endif // WLED_DISABLE_2D + + // --- 1D audio effects --- + addEffect(FX_MODE_PIXELWAVE, &mode_pixelwave, _data_FX_MODE_PIXELWAVE); + addEffect(FX_MODE_JUGGLES, &mode_juggles, _data_FX_MODE_JUGGLES); + addEffect(FX_MODE_MATRIPIX, &mode_matripix, _data_FX_MODE_MATRIPIX); + addEffect(FX_MODE_GRAVIMETER, &mode_gravimeter, _data_FX_MODE_GRAVIMETER); + addEffect(FX_MODE_PLASMOID, &mode_plasmoid, _data_FX_MODE_PLASMOID); + addEffect(FX_MODE_PUDDLES, &mode_puddles, _data_FX_MODE_PUDDLES); + addEffect(FX_MODE_MIDNOISE, &mode_midnoise, _data_FX_MODE_MIDNOISE); + addEffect(FX_MODE_NOISEMETER, &mode_noisemeter, _data_FX_MODE_NOISEMETER); + addEffect(FX_MODE_NOISEFIRE, &mode_noisefire, _data_FX_MODE_NOISEFIRE); + addEffect(FX_MODE_PUDDLEPEAK, &mode_puddlepeak, _data_FX_MODE_PUDDLEPEAK); + addEffect(FX_MODE_RIPPLEPEAK, &mode_ripplepeak, _data_FX_MODE_RIPPLEPEAK); + addEffect(FX_MODE_GRAVCENTER, &mode_gravcenter, _data_FX_MODE_GRAVCENTER); + addEffect(FX_MODE_GRAVCENTRIC, &mode_gravcentric, _data_FX_MODE_GRAVCENTRIC); + addEffect(FX_MODE_PIXELS, &mode_pixels, _data_FX_MODE_PIXELS); + addEffect(FX_MODE_FREQWAVE, &mode_freqwave, _data_FX_MODE_FREQWAVE); + addEffect(FX_MODE_FREQMATRIX, &mode_freqmatrix, _data_FX_MODE_FREQMATRIX); + addEffect(FX_MODE_WATERFALL, &mode_waterfall, _data_FX_MODE_WATERFALL); + addEffect(FX_MODE_FREQPIXELS, &mode_freqpixels, _data_FX_MODE_FREQPIXELS); + addEffect(FX_MODE_NOISEMOVE, &mode_noisemove, _data_FX_MODE_NOISEMOVE); + addEffect(FX_MODE_FREQMAP, &mode_freqmap, _data_FX_MODE_FREQMAP); + addEffect(FX_MODE_GRAVFREQ, &mode_gravfreq, _data_FX_MODE_GRAVFREQ); + addEffect(FX_MODE_DJLIGHT, &mode_DJLight, _data_FX_MODE_DJLIGHT); + addEffect(FX_MODE_BLURZ, &mode_blurz, _data_FX_MODE_BLURZ); + addEffect(FX_MODE_ROCKTAVES, &mode_rocktaves, _data_FX_MODE_ROCKTAVES); +} diff --git a/wled00/FX.h b/wled00/FX.h index aeefee35e..e39d9aef2 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -27,6 +27,8 @@ #ifndef WS2812FX_h #define WS2812FX_h +#include + #include "const.h" #define FASTLED_INTERNAL //remove annoying pragma messages @@ -38,6 +40,9 @@ #define DEFAULT_SPEED (uint8_t)128 #define DEFAULT_INTENSITY (uint8_t)128 #define DEFAULT_COLOR (uint32_t)0xFFAA00 +#define DEFAULT_C1 (uint8_t)128 +#define DEFAULT_C2 (uint8_t)128 +#define DEFAULT_C3 (uint8_t)16 #ifndef MIN #define MIN(a,b) ((a)<(b)?(a):(b)) @@ -54,37 +59,37 @@ /* Not used in all effects yet */ #define WLED_FPS 42 #define FRAMETIME_FIXED (1000/WLED_FPS) -#define FRAMETIME _frametime +//#define FRAMETIME _frametime +#define FRAMETIME strip.getFrameTime() /* each segment uses 52 bytes of SRAM memory, so if you're application fails because of insufficient memory, decreasing MAX_NUM_SEGMENTS may help */ #ifdef ESP8266 #define MAX_NUM_SEGMENTS 16 - /* How many color transitions can run at once */ - #define MAX_NUM_TRANSITIONS 8 /* How much data bytes all segments combined may allocate */ - #define MAX_SEGMENT_DATA 4096 + #define MAX_SEGMENT_DATA 5120 #else #ifndef MAX_NUM_SEGMENTS #define MAX_NUM_SEGMENTS 32 #endif - #define MAX_NUM_TRANSITIONS 24 - #define MAX_SEGMENT_DATA 20480 + #define MAX_SEGMENT_DATA 32767 #endif /* How much data bytes each segment should max allocate to leave enough space for other segments, assuming each segment uses the same amount of data. 256 for ESP8266, 640 for ESP32. */ -#define FAIR_DATA_PER_SEG (MAX_SEGMENT_DATA / MAX_NUM_SEGMENTS) +#define FAIR_DATA_PER_SEG (MAX_SEGMENT_DATA / strip.getMaxSegments()) #define MIN_SHOW_DELAY (_frametime < 16 ? 8 : 15) #define NUM_COLORS 3 /* number of colors per segment */ -#define SEGMENT _segments[_segment_index] -#define SEGCOLOR(x) _colors_t[x] -#define SEGENV _segment_runtimes[_segment_index] -#define SEGLEN _virtualSegmentLength -#define SEGACT SEGMENT.stop -#define SPEED_FORMULA_L 5U + (50U*(255U - SEGMENT.speed))/SEGLEN +#define SEGMENT strip._segments[strip.getCurrSegmentId()] +#define SEGENV strip._segments[strip.getCurrSegmentId()] +//#define SEGCOLOR(x) strip._segments[strip.getCurrSegmentId()].currentColor(x, strip._segments[strip.getCurrSegmentId()].colors[x]) +//#define SEGLEN strip._segments[strip.getCurrSegmentId()].virtualLength() +#define SEGCOLOR(x) strip.segColor(x) /* saves us a few kbytes of code */ +#define SEGPALETTE strip._currentPalette +#define SEGLEN strip._virtualSegmentLength /* saves us a few kbytes of code */ +#define SPEED_FORMULA_L (5U + (50U*(255U - SEGMENT.speed))/SEGLEN) // some common colors #define RED (uint32_t)0xFF0000 @@ -99,6 +104,8 @@ #define ORANGE (uint32_t)0xFF3000 #define PINK (uint32_t)0xFF1493 #define ULTRAWHITE (uint32_t)0xFFFFFFFF +#define DARKSLATEGRAY (uint32_t)0x2F4F4F +#define DARKSLATEGREY (uint32_t)0x2F4F4F // options // bit 7: segment is in transition mode @@ -107,19 +114,15 @@ // bit 2: segment is on // bit 1: reverse segment // bit 0: segment is selected -#define NO_OPTIONS (uint8_t)0x00 -#define TRANSITIONAL (uint8_t)0x80 -#define MIRROR (uint8_t)0x08 -#define SEGMENT_ON (uint8_t)0x04 -#define REVERSE (uint8_t)0x02 -#define SELECTED (uint8_t)0x01 -#define IS_TRANSITIONAL ((SEGMENT.options & TRANSITIONAL) == TRANSITIONAL) -#define IS_MIRROR ((SEGMENT.options & MIRROR ) == MIRROR ) -#define IS_SEGMENT_ON ((SEGMENT.options & SEGMENT_ON ) == SEGMENT_ON ) -#define IS_REVERSE ((SEGMENT.options & REVERSE ) == REVERSE ) -#define IS_SELECTED ((SEGMENT.options & SELECTED ) == SELECTED ) - -#define MODE_COUNT 118 +#define NO_OPTIONS (uint16_t)0x0000 +#define TRANSPOSED (uint16_t)0x0400 // rotated 90deg & reversed +#define REVERSE_Y_2D (uint16_t)0x0200 +#define MIRROR_Y_2D (uint16_t)0x0100 +#define TRANSITIONAL (uint16_t)0x0080 +#define MIRROR (uint16_t)0x0008 +#define SEGMENT_ON (uint16_t)0x0004 +#define REVERSE (uint16_t)0x0002 +#define SELECTED (uint16_t)0x0001 #define FX_MODE_STATIC 0 #define FX_MODE_BLINK 1 @@ -169,12 +172,14 @@ #define FX_MODE_FIRE_FLICKER 45 #define FX_MODE_GRADIENT 46 #define FX_MODE_LOADING 47 -#define FX_MODE_POLICE 48 // candidate for removal (after below three) +//#define FX_MODE_POLICE 48 // candidate for removal (after below three) +#define FX_MODE_WAVESINS 48 // was Police prior to 0.14 (use Two Dots with Red/Blue) #define FX_MODE_FAIRY 49 //was Police All prior to 0.13.0-b6 (use "Two Dots" with Red/Blue and full intensity) #define FX_MODE_TWO_DOTS 50 #define FX_MODE_FAIRYTWINKLE 51 //was Two Areas prior to 0.13.0-b6 (use "Two Dots" with full intensity) #define FX_MODE_RUNNING_DUAL 52 -#define FX_MODE_HALLOWEEN 53 // candidate for removal +//#define FX_MODE_HALLOWEEN 53 // candidate for removal +#define FX_MODE_PERLINMOVE 53 // was Halloween prior to 0.14 (use Cahse 2 with Purple/Orange) #define FX_MODE_TRICOLOR_CHASE 54 #define FX_MODE_TRICOLOR_WIPE 55 #define FX_MODE_TRICOLOR_FADE 56 @@ -235,720 +240,651 @@ #define FX_MODE_CHUNCHUN 111 #define FX_MODE_DANCING_SHADOWS 112 #define FX_MODE_WASHING_MACHINE 113 -#define FX_MODE_CANDY_CANE 114 // candidate for removal +//#define FX_MODE_CANDY_CANE 114 // candidate for removal +#define FX_MODE_FLOWSTRIPE 114 // was Cany Cane prior to 0.14 (use Cahse 2 with Red/White) #define FX_MODE_BLENDS 115 #define FX_MODE_TV_SIMULATOR 116 #define FX_MODE_DYNAMIC_SMOOTH 117 +#ifndef WLED_DISABLE_2D + // new 2D effects + #define FX_MODE_2DSPACESHIPS 118 + #define FX_MODE_2DCRAZYBEES 119 + #define FX_MODE_2DGHOSTRIDER 120 + #define FX_MODE_2DBLOBS 121 + #define FX_MODE_2DSCROLLTEXT 122 + #define FX_MODE_2DDRIFTROSE 123 + // WLED-SR effects (non SR compatible IDs) + #define FX_MODE_2DBLACKHOLE 124 // non audio + #define FX_MODE_2DDNASPIRAL 125 // non audio + #define FX_MODE_2DHIPHOTIC 126 // non audio + #define FX_MODE_2DPLASMABALL 127 // non audio + #define FX_MODE_2DSINDOTS 128 // non audio + #define FX_MODE_2DFRIZZLES 129 // non audio + #define FX_MODE_2DLISSAJOUS 130 // non audio + #define FX_MODE_2DPOLARLIGHTS 131 // non audio + #define FX_MODE_2DTARTAN 132 // non audio + #define FX_MODE_2DGAMEOFLIFE 133 // non audio + #define FX_MODE_2DJULIA 134 // non audio + #define FX_MODE_2DCOLOREDBURSTS 135 // non audio + #define FX_MODE_2DSUNRADIATION 136 // non audio + #define FX_MODE_2DNOISE 137 // non audio + #define FX_MODE_2DFIRENOISE 138 // non audio + #define FX_MODE_2DSQUAREDSWIRL 139 // non audio + #define FX_MODE_2DDNA 140 // non audio + #define FX_MODE_2DMATRIX 141 // non audio + #define FX_MODE_2DMETABALLS 142 // non audio + #define FX_MODE_2DPULSER 143 // non audio + #define FX_MODE_2DDRIFT 144 // non audio + #define FX_MODE_2DWAVERLY 145 // audio enhanced + #define FX_MODE_2DSWIRL 146 // audio enhanced + #define FX_MODE_2DAKEMI 147 // audio enhanced + #define FX_MODE_2DGEQ 148 // audio enhanced + #define FX_MODE_2DFUNKYPLANK 149 // audio enhanced +#endif //WLED_DISABLE_2D +#define FX_MODE_PIXELWAVE 150 // audio enhanced +#define FX_MODE_JUGGLES 151 // audio enhanced +#define FX_MODE_MATRIPIX 152 // audio enhanced +#define FX_MODE_GRAVIMETER 153 // audio enhanced +#define FX_MODE_PLASMOID 154 // audio enhanced +#define FX_MODE_PUDDLES 155 // audio enhanced +#define FX_MODE_MIDNOISE 156 // audio enhanced +#define FX_MODE_NOISEMETER 157 // audio enhanced +#define FX_MODE_NOISEFIRE 158 // audio enhanced +#define FX_MODE_PUDDLEPEAK 159 // audio enhanced +#define FX_MODE_RIPPLEPEAK 160 // audio enhanced +#define FX_MODE_GRAVCENTER 161 // audio enhanced +#define FX_MODE_GRAVCENTRIC 162 // audio enhanced +#define FX_MODE_PIXELS 163 // audio enhanced +#define FX_MODE_FREQWAVE 164 // audio enhanced +#define FX_MODE_FREQMATRIX 165 // audio enhanced +#define FX_MODE_WATERFALL 166 // audio enhanced +#define FX_MODE_FREQPIXELS 167 // audio enhanced +#define FX_MODE_BINMAP 168 // audio enhanced +#define FX_MODE_NOISEMOVE 169 // audio enhanced +#define FX_MODE_FREQMAP 170 // audio enhanced +#define FX_MODE_GRAVFREQ 171 // audio enhanced +#define FX_MODE_DJLIGHT 172 // audio enhanced +#define FX_MODE_BLURZ 173 // audio enhanced +#define FX_MODE_ROCKTAVES 174 // audio enhanced +#define MODE_COUNT 175 -class WS2812FX { - typedef uint16_t (WS2812FX::*mode_ptr)(void); +typedef enum mapping1D2D { + M12_Pixels = 0, + M12_pBar = 1, + M12_pArc = 2, + M12_pCorner = 3 +} mapping1D2D_t; - // pre show callback - typedef void (*show_callback) (void); +// segment, 72 bytes +typedef struct Segment { + public: + uint16_t start; // start index / start X coordinate 2D (left) + uint16_t stop; // stop index / stop X coordinate 2D (right); segment is invalid if stop == 0 + uint16_t offset; + uint8_t speed; + uint8_t intensity; + uint8_t palette; + uint8_t mode; + union { + uint16_t options; //bit pattern: msb first: [transposed mirrorY reverseY] transitional (tbd) paused needspixelstate mirrored on reverse selected + struct { + bool selected : 1; // 0 : selected + bool reverse : 1; // 1 : reversed + bool on : 1; // 2 : is On + bool mirror : 1; // 3 : mirrored + bool freeze : 1; // 4 : paused/frozen + bool reset : 1; // 5 : indicates that Segment runtime requires reset + bool transitional: 1; // 6 : transitional (there is transition occuring) + bool reverse_y : 1; // 7 : reversed Y (2D) + bool mirror_y : 1; // 8 : mirrored Y (2D) + bool transpose : 1; // 9 : transposed (2D, swapped X & Y) + uint8_t map1D2D : 3; // 10-12 : mapping for 1D effect on 2D (0-use as strip, 1-expand vertically, 2-circular/arc, 3-rectangular/corner, ...) + uint8_t soundSim : 3; // 13-15 : 0-7 sound simulation types + }; + }; + uint8_t grouping, spacing; + uint8_t opacity; + uint32_t colors[NUM_COLORS]; + uint8_t cct; //0==1900K, 255==10091K + uint8_t custom1, custom2; // custom FX parameters/sliders + struct { + uint8_t custom3 : 5; // reduced range slider (0-31) + bool check1 : 1; // checkmark 1 + bool check2 : 1; // checkmark 2 + bool check3 : 1; // checkmark 3 + }; + uint8_t startY; // start Y coodrinate 2D (top); there should be no more than 255 rows + uint8_t stopY; // stop Y coordinate 2D (bottom); there should be no more than 255 rows + char *name; + + // runtime data + unsigned long next_time; // millis() of next update + uint32_t step; // custom "step" var + uint32_t call; // call counter + uint16_t aux0; // custom var + uint16_t aux1; // custom var + byte* data; + CRGB* leds; + static CRGB *_globalLeds; + + private: + union { + uint8_t _capabilities; + struct { + bool _isRGB : 1; + bool _hasW : 1; + bool _isCCT : 1; + bool _manualW : 1; + uint8_t _reserved : 4; + }; + }; + uint16_t _dataLen; + static uint16_t _usedSegmentData; + + // transition data, valid only if transitional==true, holds values during transition + struct Transition { + uint32_t _colorT[NUM_COLORS]; + uint8_t _briT; // temporary brightness + uint8_t _cctT; // temporary CCT + CRGBPalette16 _palT; // temporary palette + uint8_t _prevPaletteBlends; // number of previous palette blends (there are max 255 belnds possible) + uint8_t _modeP; // previous mode/effect + //uint16_t _aux0, _aux1; // previous mode/effect runtime data + //uint32_t _step, _call; // previous mode/effect runtime data + //byte *_data; // previous mode/effect runtime data + uint32_t _start; + uint16_t _dur; + Transition(uint16_t dur=750) + : _briT(255) + , _cctT(127) + , _palT(CRGBPalette16(CRGB::Black)) + , _prevPaletteBlends(0) + , _modeP(FX_MODE_STATIC) + , _start(millis()) + , _dur(dur) + {} + Transition(uint16_t d, uint8_t b, uint8_t c, const uint32_t *o) + : _briT(b) + , _cctT(c) + , _palT(CRGBPalette16(CRGB::Black)) + , _prevPaletteBlends(0) + , _modeP(FX_MODE_STATIC) + , _start(millis()) + , _dur(d) + { + for (size_t i=0; i> n) & 0x01); } + inline bool isSelected(void) const { return selected; } + inline bool isActive(void) const { return stop > start; } + inline bool is2D(void) const { return (width()>1 && height()>1); } + inline uint16_t width(void) const { return stop - start; } // segment width in physical pixels (length if 1D) + inline uint16_t height(void) const { return stopY - startY; } // segment height (if 2D) in physical pixels + inline uint16_t length(void) const { return width() * height(); } // segment length (count) in physical pixels + inline uint16_t groupLength(void) const { return grouping + spacing; } + inline uint8_t getLightCapabilities(void) const { return _capabilities; } + + static uint16_t getUsedSegmentData(void) { return _usedSegmentData; } + static void addUsedSegmentData(int len) { _usedSegmentData += len; } + + bool setColor(uint8_t slot, uint32_t c); //returns true if changed + void setCCT(uint16_t k); + void setOpacity(uint8_t o); + void setOption(uint8_t n, bool val); + void setMode(uint8_t fx, bool loadDefaults = false); + void setPalette(uint8_t pal); + uint8_t differs(Segment& b) const; + void refreshLightCapabilities(void); + + // runtime data functions + inline uint16_t dataSize(void) const { return _dataLen; } + bool allocateData(size_t len); + void deallocateData(void); + void resetIfRequired(void); + /** + * Flags that before the next effect is calculated, + * the internal segment state should be reset. + * Call resetIfRequired before calling the next effect function. + * Safe to call from interrupts and network requests. + */ + inline void markForReset(void) { reset = true; } // setOption(SEG_OPTION_RESET, true) + void setUpLeds(void); // set up leds[] array for loseless getPixelColor() + + // transition functions + void startTransition(uint16_t dur); // transition has to start before actual segment values change + void handleTransition(void); + uint16_t progress(void); //transition progression between 0-65535 + uint8_t currentBri(uint8_t briNew, bool useCct = false); + uint8_t currentMode(uint8_t modeNew); + uint32_t currentColor(uint8_t slot, uint32_t colorNew); + CRGBPalette16 &loadPalette(CRGBPalette16 &tgt, uint8_t pal); + CRGBPalette16 ¤tPalette(CRGBPalette16 &tgt, uint8_t paletteID); + + // 1D strip + uint16_t virtualLength(void) const; + void setPixelColor(int n, uint32_t c); // set relative pixel within segment with color + void setPixelColor(int n, byte r, byte g, byte b, byte w = 0) { setPixelColor(n, RGBW32(r,g,b,w)); } // automatically inline + void setPixelColor(int n, CRGB c) { setPixelColor(n, RGBW32(c.r,c.g,c.b,0)); } // automatically inline + void setPixelColor(float i, uint32_t c, bool aa = true); + void setPixelColor(float i, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0, bool aa = true) { setPixelColor(i, RGBW32(r,g,b,w), aa); } + void setPixelColor(float i, CRGB c, bool aa = true) { setPixelColor(i, RGBW32(c.r,c.g,c.b,0), aa); } + uint32_t getPixelColor(int i); + // 1D support functions (some implement 2D as well) + void blur(uint8_t); + void fill(uint32_t c); + void fade_out(uint8_t r); + void fadeToBlackBy(uint8_t fadeBy); + void blendPixelColor(int n, uint32_t color, uint8_t blend); + void blendPixelColor(int n, CRGB c, uint8_t blend) { blendPixelColor(n, RGBW32(c.r,c.g,c.b,0), blend); } + void addPixelColor(int n, uint32_t color); + void addPixelColor(int n, byte r, byte g, byte b, byte w = 0) { addPixelColor(n, RGBW32(r,g,b,w)); } // automatically inline + void addPixelColor(int n, CRGB c) { addPixelColor(n, RGBW32(c.r,c.g,c.b,0)); } // automatically inline + void fadePixelColor(uint16_t n, uint8_t fade); + uint8_t get_random_wheel_index(uint8_t pos); + uint32_t color_from_palette(uint16_t, bool mapping, bool wrap, uint8_t mcol, uint8_t pbri = 255); + uint32_t color_wheel(uint8_t pos); + + // 2D matrix + uint16_t virtualWidth(void) const; + uint16_t virtualHeight(void) const; + uint16_t nrOfVStrips(void) const; + #ifndef WLED_DISABLE_2D + uint16_t XY(uint16_t x, uint16_t y); // support function to get relative index within segment (for leds[]) + void setPixelColorXY(int x, int y, uint32_t c); // set relative pixel within segment with color + void setPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) { setPixelColorXY(x, y, RGBW32(r,g,b,w)); } // automatically inline + void setPixelColorXY(int x, int y, CRGB c) { setPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0)); } // automatically inline + void setPixelColorXY(float x, float y, uint32_t c, bool aa = true); + void setPixelColorXY(float x, float y, byte r, byte g, byte b, byte w = 0, bool aa = true) { setPixelColorXY(x, y, RGBW32(r,g,b,w), aa); } + void setPixelColorXY(float x, float y, CRGB c, bool aa = true) { setPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), aa); } + uint32_t getPixelColorXY(uint16_t x, uint16_t y); + // 2D support functions + void blendPixelColorXY(uint16_t x, uint16_t y, uint32_t color, uint8_t blend); + void blendPixelColorXY(uint16_t x, uint16_t y, CRGB c, uint8_t blend) { blendPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0), blend); } + void addPixelColorXY(int x, int y, uint32_t color); + void addPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) { addPixelColorXY(x, y, RGBW32(r,g,b,w)); } // automatically inline + void addPixelColorXY(int x, int y, CRGB c) { addPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0)); } + void fadePixelColorXY(uint16_t x, uint16_t y, uint8_t fade); + void box_blur(uint16_t i, bool vertical, fract8 blur_amount); // 1D box blur (with weight) + void blurRow(uint16_t row, fract8 blur_amount); + void blurCol(uint16_t col, fract8 blur_amount); + void moveX(int8_t delta); + void moveY(int8_t delta); + void move(uint8_t dir, uint8_t delta); + void fill_circle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB c); + void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint32_t c); + void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, CRGB c) { drawLine(x0, y0, x1, y1, RGBW32(c.r,c.g,c.b,0)); } // automatic inline + void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, uint32_t color); + void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, CRGB c) { drawCharacter(chr, x, y, w, h, RGBW32(c.r,c.g,c.b,0)); } // automatic inline + void wu_pixel(uint32_t x, uint32_t y, CRGB c); + void blur1d(fract8 blur_amount); // blur all rows in 1 dimension + void blur2d(fract8 blur_amount) { blur(blur_amount); } + void fill_solid(CRGB c) { fill(RGBW32(c.r,c.g,c.b,0)); } + void nscale8(uint8_t scale); + #else + uint16_t XY(uint16_t x, uint16_t y) { return x; } + void setPixelColorXY(int x, int y, uint32_t c) { setPixelColor(x, c); } + void setPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) { setPixelColor(x, RGBW32(r,g,b,w)); } + void setPixelColorXY(int x, int y, CRGB c) { setPixelColor(x, RGBW32(c.r,c.g,c.b,0)); } + void setPixelColorXY(float x, float y, uint32_t c, bool aa = true) { setPixelColor(x, c, aa); } + void setPixelColorXY(float x, float y, byte r, byte g, byte b, byte w = 0, bool aa = true) { setPixelColor(x, RGBW32(r,g,b,w), aa); } + void setPixelColorXY(float x, float y, CRGB c, bool aa = true) { setPixelColor(x, RGBW32(c.r,c.g,c.b,0), aa); } + uint32_t getPixelColorXY(uint16_t x, uint16_t y) { return getPixelColor(x); } + void blendPixelColorXY(uint16_t x, uint16_t y, uint32_t c, uint8_t blend) { blendPixelColor(x, c, blend); } + void blendPixelColorXY(uint16_t x, uint16_t y, CRGB c, uint8_t blend) { blendPixelColor(x, RGBW32(c.r,c.g,c.b,0), blend); } + void addPixelColorXY(int x, int y, uint32_t color) { addPixelColor(x, color); } + void addPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) { addPixelColor(x, RGBW32(r,g,b,w)); } + void addPixelColorXY(int x, int y, CRGB c) { addPixelColor(x, RGBW32(c.r,c.g,c.b,0)); } + void fadePixelColorXY(uint16_t x, uint16_t y, uint8_t fade) { fadePixelColor(x, fade); } + void box_blur(uint16_t i, bool vertical, fract8 blur_amount) {} + void blurRow(uint16_t row, fract8 blur_amount) {} + void blurCol(uint16_t col, fract8 blur_amount) {} + void moveX(int8_t delta) {} + void moveY(int8_t delta) {} + void move(uint8_t dir, uint8_t delta) {} + void fill_circle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB c) {} + void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint32_t c) {} + void drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, CRGB c) {} + void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, uint32_t color) {} + void drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, CRGB color) {} + void wu_pixel(uint32_t x, uint32_t y, CRGB c) {} + #endif +} segment; +//static int segSize = sizeof(Segment); + +// main "strip" class +class WS2812FX { // 96 bytes + typedef uint16_t (*mode_ptr)(void); // pointer to mode function + typedef void (*show_callback)(void); // pre show callback + typedef struct ModeData { + uint8_t _id; // mode (effect) id + mode_ptr _fcn; // mode (effect) function + const char *_data; // mode (effect) name and its UI control data + ModeData(uint8_t id, uint16_t (*fcn)(void), const char *data) : _id(id), _fcn(fcn), _data(data) {} + } mode_data_t; static WS2812FX* instance; - // segment parameters public: - typedef struct Segment { // 31 (32 in memory) bytes - uint16_t start; - uint16_t stop; //segment invalid if stop == 0 - uint16_t offset; - uint8_t speed; - uint8_t intensity; - uint8_t palette; - uint8_t mode; - uint8_t options; //bit pattern: msb first: transitional needspixelstate tbd tbd (paused) on reverse selected - uint8_t grouping, spacing; - uint8_t opacity; - uint32_t colors[NUM_COLORS]; - uint8_t cct; //0==1900K, 255==10091K - uint8_t _capabilities; - char *name; - bool setColor(uint8_t slot, uint32_t c, uint8_t segn) { //returns true if changed - if (slot >= NUM_COLORS || segn >= MAX_NUM_SEGMENTS) return false; - if (c == colors[slot]) return false; - uint8_t b = (slot == 1) ? cct : opacity; - ColorTransition::startTransition(b, colors[slot], instance->_transitionDur, segn, slot); - colors[slot] = c; return true; - } - void setCCT(uint16_t k, uint8_t segn) { - if (segn >= MAX_NUM_SEGMENTS) return; - if (k > 255) { //kelvin value, convert to 0-255 - if (k < 1900) k = 1900; - if (k > 10091) k = 10091; - k = (k - 1900) >> 5; - } - if (cct == k) return; - ColorTransition::startTransition(cct, colors[1], instance->_transitionDur, segn, 1); - cct = k; - } - void setOpacity(uint8_t o, uint8_t segn) { - if (segn >= MAX_NUM_SEGMENTS) return; - if (opacity == o) return; - ColorTransition::startTransition(opacity, colors[0], instance->_transitionDur, segn, 0); - opacity = o; - } - void setOption(uint8_t n, bool val, uint8_t segn = 255) - { - bool prevOn = false; - if (n == SEG_OPTION_ON) { - prevOn = getOption(SEG_OPTION_ON); - if (!val && prevOn) { //fade off - ColorTransition::startTransition(opacity, colors[0], instance->_transitionDur, segn, 0); - } - } - if (val) { - options |= 0x01 << n; - } else - { - options &= ~(0x01 << n); - } - - if (n == SEG_OPTION_ON && val && !prevOn) { //fade on - ColorTransition::startTransition(0, colors[0], instance->_transitionDur, segn, 0); - } - } - bool getOption(uint8_t n) - { - return ((options >> n) & 0x01); - } - inline bool isSelected() - { - return getOption(0); - } - inline bool isActive() - { - return stop > start; - } - inline uint16_t length() - { - return stop - start; - } - inline uint16_t groupLength() - { - return grouping + spacing; - } - uint16_t virtualLength() - { - uint16_t groupLen = groupLength(); - uint16_t vLength = (length() + groupLen - 1) / groupLen; - if (options & MIRROR) - vLength = (vLength + 1) /2; // divide by 2 if mirror, leave at least a single LED - return vLength; - } - uint8_t differs(Segment& b); - inline uint8_t getLightCapabilities() {return _capabilities;} - void refreshLightCapabilities(); - } segment; - - // segment runtime parameters - typedef struct Segment_runtime { // 28 bytes - unsigned long next_time; // millis() of next update - uint32_t step; // custom "step" var - uint32_t call; // call counter - uint16_t aux0; // custom var - uint16_t aux1; // custom var - byte* data = nullptr; - bool allocateData(uint16_t len){ - if (data && _dataLen == len) return true; //already allocated - deallocateData(); - if (WS2812FX::instance->_usedSegmentData + len > MAX_SEGMENT_DATA) return false; //not enough memory - // if possible use SPI RAM on ESP32 - #if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_PSRAM) - if (psramFound()) - data = (byte*) ps_malloc(len); - else - #endif - data = (byte*) malloc(len); - if (!data) return false; //allocation failed - WS2812FX::instance->_usedSegmentData += len; - _dataLen = len; - memset(data, 0, len); - return true; - } - void deallocateData(){ - free(data); - data = nullptr; - WS2812FX::instance->_usedSegmentData -= _dataLen; - _dataLen = 0; - } - - /** - * If reset of this segment was request, clears runtime - * settings of this segment. - * Must not be called while an effect mode function is running - * because it could access the data buffer and this method - * may free that data buffer. - */ - void resetIfRequired() { - if (_requiresReset) { - next_time = 0; step = 0; call = 0; aux0 = 0; aux1 = 0; - deallocateData(); - _requiresReset = false; - } - } - - /** - * Flags that before the next effect is calculated, - * the internal segment state should be reset. - * Call resetIfRequired before calling the next effect function. - * Safe to call from interrupts and network requests. - */ - inline void markForReset() { _requiresReset = true; } - private: - uint16_t _dataLen = 0; - bool _requiresReset = false; - } segment_runtime; - - typedef struct ColorTransition { // 12 bytes - uint32_t colorOld = 0; - uint32_t transitionStart; - uint16_t transitionDur; - uint8_t segment = 0xFF; //lower 6 bits: the segment this transition is for (255 indicates transition not in use/available) upper 2 bits: color channel - uint8_t briOld = 0; - static void startTransition(uint8_t oldBri, uint32_t oldCol, uint16_t dur, uint8_t segn, uint8_t slot) { - if (segn >= MAX_NUM_SEGMENTS || slot >= NUM_COLORS || dur == 0) return; - if (instance->_brightness == 0) return; //do not need transitions if master bri is off - if (!instance->_segments[segn].getOption(SEG_OPTION_ON)) return; //not if segment is off either - uint8_t tIndex = 0xFF; //none found - uint16_t tProgression = 0; - uint8_t s = segn + (slot << 6); //merge slot and segment into one byte - - for (uint8_t i = 0; i < MAX_NUM_TRANSITIONS; i++) { - uint8_t tSeg = instance->transitions[i].segment; - //see if this segment + color already has a running transition - if (tSeg == s) { - tIndex = i; break; - } - if (tSeg == 0xFF) { //free transition - tIndex = i; tProgression = 0xFFFF; - } - } - - if (tIndex == 0xFF) { //no slot found yet - for (uint8_t i = 0; i < MAX_NUM_TRANSITIONS; i++) { - //find most progressed transition to overwrite - uint16_t prog = instance->transitions[i].progress(); - if (prog > tProgression) { - tIndex = i; tProgression = prog; - } - } - } - - ColorTransition& t = instance->transitions[tIndex]; - if (t.segment == s) //this is an active transition on the same segment+color - { - bool wasTurningOff = (oldBri == 0); - t.briOld = t.currentBri(wasTurningOff, slot); - t.colorOld = t.currentColor(oldCol); - } else { - t.briOld = oldBri; - t.colorOld = oldCol; - uint8_t prevSeg = t.segment & 0x3F; - if (prevSeg < MAX_NUM_SEGMENTS) instance->_segments[prevSeg].setOption(SEG_OPTION_TRANSITIONAL, false); - } - t.transitionDur = dur; - t.transitionStart = millis(); - t.segment = s; - instance->_segments[segn].setOption(SEG_OPTION_TRANSITIONAL, true); - //refresh immediately, required for Solid mode - if (instance->_segment_runtimes[segn].next_time > t.transitionStart + 22) instance->_segment_runtimes[segn].next_time = t.transitionStart; - } - uint16_t progress(bool allowEnd = false) { //transition progression between 0-65535 - uint32_t timeNow = millis(); - if (timeNow - transitionStart > transitionDur) { - if (allowEnd) { - uint8_t segn = segment & 0x3F; - if (segn < MAX_NUM_SEGMENTS) instance->_segments[segn].setOption(SEG_OPTION_TRANSITIONAL, false); - segment = 0xFF; - } - return 0xFFFF; - } - uint32_t elapsed = timeNow - transitionStart; - uint32_t prog = elapsed * 0xFFFF / transitionDur; - return (prog > 0xFFFF) ? 0xFFFF : prog; - } - uint32_t currentColor(uint32_t colorNew) { - return instance->color_blend(colorOld, colorNew, progress(true), true); - } - uint8_t currentBri(bool turningOff = false, uint8_t slot = 0) { - uint8_t segn = segment & 0x3F; - if (segn >= MAX_NUM_SEGMENTS) return 0; - uint8_t briNew = instance->_segments[segn].opacity; - if (slot == 0) { - if (!instance->_segments[segn].getOption(SEG_OPTION_ON) || turningOff) briNew = 0; - } else { //transition slot 1 brightness for CCT transition - briNew = instance->_segments[segn].cct; - } - uint32_t prog = progress() + 1; - return ((briNew * prog) + (briOld * (0x10000 - prog))) >> 16; - } - } color_transition; - - WS2812FX() { + WS2812FX() : + paletteFade(0), + paletteBlend(0), + milliampsPerLed(55), + cctBlending(0), + ablMilliampsMax(ABL_MILLIAMPS_DEFAULT), + currentMilliamps(0), + now(millis()), + timebase(0), + isMatrix(false), +#ifndef WLED_DISABLE_2D + hPanels(1), + vPanels(1), + panelH(8), + panelW(8), + matrixWidth(DEFAULT_LED_COUNT), + matrixHeight(1), + matrix{0,0,0,0}, + panel{{0,0,0,0}}, +#endif + // semi-private (just obscured) used in effect functions through macros + _currentPalette(CRGBPalette16(CRGB::Black)), + _colors_t{0,0,0}, + _virtualSegmentLength(0), + // true private variables + _length(DEFAULT_LED_COUNT), + _brightness(DEFAULT_BRIGHTNESS), + _transitionDur(750), + _targetFps(WLED_FPS), + _frametime(FRAMETIME_FIXED), + _cumulativeFps(2), + _isServicing(false), + _isOffRefreshRequired(false), + _hasWhiteChannel(false), + _triggered(false), + _modeCount(MODE_COUNT), + _callback(nullptr), + customMappingTable(nullptr), + customMappingSize(0), + _lastShow(0), + _segment_index(0), + _mainSegment(0) + { WS2812FX::instance = this; - //assign each member of the _mode[] array to its respective function reference - _mode[FX_MODE_STATIC] = &WS2812FX::mode_static; - _mode[FX_MODE_BLINK] = &WS2812FX::mode_blink; - _mode[FX_MODE_COLOR_WIPE] = &WS2812FX::mode_color_wipe; - _mode[FX_MODE_COLOR_WIPE_RANDOM] = &WS2812FX::mode_color_wipe_random; - _mode[FX_MODE_RANDOM_COLOR] = &WS2812FX::mode_random_color; - _mode[FX_MODE_COLOR_SWEEP] = &WS2812FX::mode_color_sweep; - _mode[FX_MODE_DYNAMIC] = &WS2812FX::mode_dynamic; - _mode[FX_MODE_RAINBOW] = &WS2812FX::mode_rainbow; - _mode[FX_MODE_RAINBOW_CYCLE] = &WS2812FX::mode_rainbow_cycle; - _mode[FX_MODE_SCAN] = &WS2812FX::mode_scan; - _mode[FX_MODE_DUAL_SCAN] = &WS2812FX::mode_dual_scan; - _mode[FX_MODE_FADE] = &WS2812FX::mode_fade; - _mode[FX_MODE_THEATER_CHASE] = &WS2812FX::mode_theater_chase; - _mode[FX_MODE_THEATER_CHASE_RAINBOW] = &WS2812FX::mode_theater_chase_rainbow; - _mode[FX_MODE_SAW] = &WS2812FX::mode_saw; - _mode[FX_MODE_TWINKLE] = &WS2812FX::mode_twinkle; - _mode[FX_MODE_DISSOLVE] = &WS2812FX::mode_dissolve; - _mode[FX_MODE_DISSOLVE_RANDOM] = &WS2812FX::mode_dissolve_random; - _mode[FX_MODE_SPARKLE] = &WS2812FX::mode_sparkle; - _mode[FX_MODE_FLASH_SPARKLE] = &WS2812FX::mode_flash_sparkle; - _mode[FX_MODE_HYPER_SPARKLE] = &WS2812FX::mode_hyper_sparkle; - _mode[FX_MODE_STROBE] = &WS2812FX::mode_strobe; - _mode[FX_MODE_STROBE_RAINBOW] = &WS2812FX::mode_strobe_rainbow; - _mode[FX_MODE_MULTI_STROBE] = &WS2812FX::mode_multi_strobe; - _mode[FX_MODE_BLINK_RAINBOW] = &WS2812FX::mode_blink_rainbow; - _mode[FX_MODE_ANDROID] = &WS2812FX::mode_android; - _mode[FX_MODE_CHASE_COLOR] = &WS2812FX::mode_chase_color; - _mode[FX_MODE_CHASE_RANDOM] = &WS2812FX::mode_chase_random; - _mode[FX_MODE_CHASE_RAINBOW] = &WS2812FX::mode_chase_rainbow; - _mode[FX_MODE_CHASE_FLASH] = &WS2812FX::mode_chase_flash; - _mode[FX_MODE_CHASE_FLASH_RANDOM] = &WS2812FX::mode_chase_flash_random; - _mode[FX_MODE_CHASE_RAINBOW_WHITE] = &WS2812FX::mode_chase_rainbow_white; - _mode[FX_MODE_COLORFUL] = &WS2812FX::mode_colorful; - _mode[FX_MODE_TRAFFIC_LIGHT] = &WS2812FX::mode_traffic_light; - _mode[FX_MODE_COLOR_SWEEP_RANDOM] = &WS2812FX::mode_color_sweep_random; - _mode[FX_MODE_RUNNING_COLOR] = &WS2812FX::mode_running_color; - _mode[FX_MODE_AURORA] = &WS2812FX::mode_aurora; - _mode[FX_MODE_RUNNING_RANDOM] = &WS2812FX::mode_running_random; - _mode[FX_MODE_LARSON_SCANNER] = &WS2812FX::mode_larson_scanner; - _mode[FX_MODE_COMET] = &WS2812FX::mode_comet; - _mode[FX_MODE_FIREWORKS] = &WS2812FX::mode_fireworks; - _mode[FX_MODE_RAIN] = &WS2812FX::mode_rain; - _mode[FX_MODE_TETRIX] = &WS2812FX::mode_tetrix; - _mode[FX_MODE_FIRE_FLICKER] = &WS2812FX::mode_fire_flicker; - _mode[FX_MODE_GRADIENT] = &WS2812FX::mode_gradient; - _mode[FX_MODE_LOADING] = &WS2812FX::mode_loading; - _mode[FX_MODE_POLICE] = &WS2812FX::mode_police; - _mode[FX_MODE_FAIRY] = &WS2812FX::mode_fairy; - _mode[FX_MODE_TWO_DOTS] = &WS2812FX::mode_two_dots; - _mode[FX_MODE_FAIRYTWINKLE] = &WS2812FX::mode_fairytwinkle; - _mode[FX_MODE_RUNNING_DUAL] = &WS2812FX::mode_running_dual; - _mode[FX_MODE_HALLOWEEN] = &WS2812FX::mode_halloween; - _mode[FX_MODE_TRICOLOR_CHASE] = &WS2812FX::mode_tricolor_chase; - _mode[FX_MODE_TRICOLOR_WIPE] = &WS2812FX::mode_tricolor_wipe; - _mode[FX_MODE_TRICOLOR_FADE] = &WS2812FX::mode_tricolor_fade; - _mode[FX_MODE_BREATH] = &WS2812FX::mode_breath; - _mode[FX_MODE_RUNNING_LIGHTS] = &WS2812FX::mode_running_lights; - _mode[FX_MODE_LIGHTNING] = &WS2812FX::mode_lightning; - _mode[FX_MODE_ICU] = &WS2812FX::mode_icu; - _mode[FX_MODE_MULTI_COMET] = &WS2812FX::mode_multi_comet; - _mode[FX_MODE_DUAL_LARSON_SCANNER] = &WS2812FX::mode_dual_larson_scanner; - _mode[FX_MODE_RANDOM_CHASE] = &WS2812FX::mode_random_chase; - _mode[FX_MODE_OSCILLATE] = &WS2812FX::mode_oscillate; - _mode[FX_MODE_FIRE_2012] = &WS2812FX::mode_fire_2012; - _mode[FX_MODE_PRIDE_2015] = &WS2812FX::mode_pride_2015; - _mode[FX_MODE_BPM] = &WS2812FX::mode_bpm; - _mode[FX_MODE_JUGGLE] = &WS2812FX::mode_juggle; - _mode[FX_MODE_PALETTE] = &WS2812FX::mode_palette; - _mode[FX_MODE_COLORWAVES] = &WS2812FX::mode_colorwaves; - _mode[FX_MODE_FILLNOISE8] = &WS2812FX::mode_fillnoise8; - _mode[FX_MODE_NOISE16_1] = &WS2812FX::mode_noise16_1; - _mode[FX_MODE_NOISE16_2] = &WS2812FX::mode_noise16_2; - _mode[FX_MODE_NOISE16_3] = &WS2812FX::mode_noise16_3; - _mode[FX_MODE_NOISE16_4] = &WS2812FX::mode_noise16_4; - _mode[FX_MODE_COLORTWINKLE] = &WS2812FX::mode_colortwinkle; - _mode[FX_MODE_LAKE] = &WS2812FX::mode_lake; - _mode[FX_MODE_METEOR] = &WS2812FX::mode_meteor; - _mode[FX_MODE_METEOR_SMOOTH] = &WS2812FX::mode_meteor_smooth; - _mode[FX_MODE_RAILWAY] = &WS2812FX::mode_railway; - _mode[FX_MODE_RIPPLE] = &WS2812FX::mode_ripple; - _mode[FX_MODE_TWINKLEFOX] = &WS2812FX::mode_twinklefox; - _mode[FX_MODE_TWINKLECAT] = &WS2812FX::mode_twinklecat; - _mode[FX_MODE_HALLOWEEN_EYES] = &WS2812FX::mode_halloween_eyes; - _mode[FX_MODE_STATIC_PATTERN] = &WS2812FX::mode_static_pattern; - _mode[FX_MODE_TRI_STATIC_PATTERN] = &WS2812FX::mode_tri_static_pattern; - _mode[FX_MODE_SPOTS] = &WS2812FX::mode_spots; - _mode[FX_MODE_SPOTS_FADE] = &WS2812FX::mode_spots_fade; - _mode[FX_MODE_GLITTER] = &WS2812FX::mode_glitter; - _mode[FX_MODE_CANDLE] = &WS2812FX::mode_candle; - _mode[FX_MODE_STARBURST] = &WS2812FX::mode_starburst; - _mode[FX_MODE_EXPLODING_FIREWORKS] = &WS2812FX::mode_exploding_fireworks; - _mode[FX_MODE_BOUNCINGBALLS] = &WS2812FX::mode_bouncing_balls; - _mode[FX_MODE_SINELON] = &WS2812FX::mode_sinelon; - _mode[FX_MODE_SINELON_DUAL] = &WS2812FX::mode_sinelon_dual; - _mode[FX_MODE_SINELON_RAINBOW] = &WS2812FX::mode_sinelon_rainbow; - _mode[FX_MODE_POPCORN] = &WS2812FX::mode_popcorn; - _mode[FX_MODE_DRIP] = &WS2812FX::mode_drip; - _mode[FX_MODE_PLASMA] = &WS2812FX::mode_plasma; - _mode[FX_MODE_PERCENT] = &WS2812FX::mode_percent; - _mode[FX_MODE_RIPPLE_RAINBOW] = &WS2812FX::mode_ripple_rainbow; - _mode[FX_MODE_HEARTBEAT] = &WS2812FX::mode_heartbeat; - _mode[FX_MODE_PACIFICA] = &WS2812FX::mode_pacifica; - _mode[FX_MODE_CANDLE_MULTI] = &WS2812FX::mode_candle_multi; - _mode[FX_MODE_SOLID_GLITTER] = &WS2812FX::mode_solid_glitter; - _mode[FX_MODE_SUNRISE] = &WS2812FX::mode_sunrise; - _mode[FX_MODE_PHASED] = &WS2812FX::mode_phased; - _mode[FX_MODE_TWINKLEUP] = &WS2812FX::mode_twinkleup; - _mode[FX_MODE_NOISEPAL] = &WS2812FX::mode_noisepal; - _mode[FX_MODE_SINEWAVE] = &WS2812FX::mode_sinewave; - _mode[FX_MODE_PHASEDNOISE] = &WS2812FX::mode_phased_noise; - _mode[FX_MODE_FLOW] = &WS2812FX::mode_flow; - _mode[FX_MODE_CHUNCHUN] = &WS2812FX::mode_chunchun; - _mode[FX_MODE_DANCING_SHADOWS] = &WS2812FX::mode_dancing_shadows; - _mode[FX_MODE_WASHING_MACHINE] = &WS2812FX::mode_washing_machine; - _mode[FX_MODE_CANDY_CANE] = &WS2812FX::mode_candy_cane; - _mode[FX_MODE_BLENDS] = &WS2812FX::mode_blends; - _mode[FX_MODE_TV_SIMULATOR] = &WS2812FX::mode_tv_simulator; - _mode[FX_MODE_DYNAMIC_SMOOTH] = &WS2812FX::mode_dynamic_smooth; - - _brightness = DEFAULT_BRIGHTNESS; - currentPalette = CRGBPalette16(CRGB::Black); - targetPalette = CloudColors_p; - ablMilliampsMax = ABL_MILLIAMPS_DEFAULT; - currentMilliamps = 0; - timebase = 0; - resetSegments(); + _mode.reserve(_modeCount); // allocate memory to prevent initial fragmentation (does not increase size()) + _modeData.reserve(_modeCount); // allocate memory to prevent initial fragmentation (does not increase size()) + if (_mode.capacity() <= 1 || _modeData.capacity() <= 1) _modeCount = 1; // memory allocation failed only show Solid + else setupEffectData(); } + ~WS2812FX() { + if (customMappingTable) delete[] customMappingTable; + _mode.clear(); + _modeData.clear(); + _segments.clear(); + customPalettes.clear(); + if (useLedsArray && Segment::_globalLeds) free(Segment::_globalLeds); + } + + static WS2812FX* getInstance(void) { return instance; } + void +#ifdef WLED_DEBUG + printSize(), +#endif finalizeInit(), service(void), - blur(uint8_t), - fill(uint32_t), - fade_out(uint8_t r), setMode(uint8_t segid, uint8_t m), setColor(uint8_t slot, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0), setColor(uint8_t slot, uint32_t c), setCCT(uint16_t k), setBrightness(uint8_t b, bool direct = false), setRange(uint16_t i, uint16_t i2, uint32_t col), - setShowCallback(show_callback cb), - setTransition(uint16_t t), setTransitionMode(bool t), - calcGammaTable(float), - trigger(void), - setSegment(uint8_t n, uint16_t start, uint16_t stop, uint8_t grouping = 0, uint8_t spacing = 0, uint16_t offset = UINT16_MAX), + purgeSegments(bool force = false), + setSegment(uint8_t n, uint16_t start, uint16_t stop, uint8_t grouping = 1, uint8_t spacing = 0, uint16_t offset = UINT16_MAX, uint16_t startY=0, uint16_t stopY=1), setMainSegmentId(uint8_t n), restartRuntime(), resetSegments(), makeAutoSegments(bool forceReset = false), fixInvalidSegments(), - setPixelColor(uint16_t n, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0), + setPixelColor(int n, uint32_t c), show(void), - setTargetFps(uint8_t fps), + setTargetFps(uint8_t fps), deserializeMap(uint8_t n=0); - inline void setPixelColor(uint16_t n, uint32_t c) {setPixelColor(n, byte(c>>16), byte(c>>8), byte(c), byte(c>>24));} + void fill(uint32_t c) { for (int i = 0; i < _length; i++) setPixelColor(i, c); } // fill whole strip with color (inline) + void addEffect(uint8_t id, mode_ptr mode_fn, const char *mode_name); // add effect to the list; defined in FX.cpp + void setupEffectData(void); // add default effects to the list; defined in FX.cpp + + // outsmart the compiler :) by correctly overloading + inline void setPixelColor(int n, uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0) { setPixelColor(n, RGBW32(r,g,b,w)); } + inline void setPixelColor(int n, CRGB c) { setPixelColor(n, c.red, c.green, c.blue); } + inline void trigger(void) { _triggered = true; } // Forces the next frame to be computed on all active segments. + inline void setShowCallback(show_callback cb) { _callback = cb; } + inline void setTransition(uint16_t t) { _transitionDur = t; } + inline void appendSegment(const Segment &seg = Segment()) { _segments.push_back(seg); } bool - gammaCorrectBri = false, - gammaCorrectCol = true, checkSegmentAlignment(void), hasRGBWBus(void), hasCCTBus(void), // return true if the strip is being sent pixel updates - isUpdating(void); + isUpdating(void), + useLedsArray = false; + + inline bool isServicing(void) { return _isServicing; } + inline bool hasWhiteChannel(void) {return _hasWhiteChannel;} + inline bool isOffRefreshRequired(void) {return _isOffRefreshRequired;} uint8_t - paletteFade = 0, - paletteBlend = 0, - milliampsPerLed = 55, - autoWhiteMode = RGBW_MODE_DUAL, - cctBlending = 0, - getBrightness(void), - getModeCount(void), - getPaletteCount(void), - getMaxSegments(void), + paletteFade, + paletteBlend, + milliampsPerLed, + cctBlending, getActiveSegmentsNum(void), getFirstSelectedSegId(void), - getMainSegmentId(void), getLastActiveSegmentId(void), - getTargetFps(void), - setPixelSegment(uint8_t n), - gamma8(uint8_t), - gamma8_cal(uint8_t, float), - get_random_wheel_index(uint8_t); + setPixelSegment(uint8_t n); - inline uint8_t sin_gap(uint16_t in) { - if (in & 0x100) return 0; - return sin8(in + 192); // correct phase shift of sine so that it starts and stops at 0 - } - - int8_t - tristate_square8(uint8_t x, uint8_t pulsewidth, uint8_t attdec); + inline uint8_t getBrightness(void) { return _brightness; } + inline uint8_t getMaxSegments(void) { return MAX_NUM_SEGMENTS; } // returns maximum number of supported segments (fixed value) + inline uint8_t getSegmentsNum(void) { return _segments.size(); } // returns currently present segments + inline uint8_t getCurrSegmentId(void) { return _segment_index; } + inline uint8_t getMainSegmentId(void) { return _mainSegment; } + inline uint8_t getPaletteCount() { return 13 + GRADIENT_PALETTE_COUNT; } + inline uint8_t getTargetFps() { return _targetFps; } + inline uint8_t getModeCount() { return _modeCount; } uint16_t ablMilliampsMax, currentMilliamps, - triwave16(uint16_t), - getLengthTotal(void), getLengthPhysical(void), getFps(); + inline uint16_t getFrameTime(void) { return _frametime; } + inline uint16_t getMinShowDelay(void) { return MIN_SHOW_DELAY; } + inline uint16_t getLengthTotal(void) { return _length; } + inline uint16_t getTransition(void) { return _transitionDur; } + uint32_t now, timebase, - color_wheel(uint8_t), - color_from_palette(uint16_t, bool mapping, bool wrap, uint8_t mcol, uint8_t pbri = 255), - color_blend(uint32_t,uint32_t,uint16_t,bool b16=false), currentColor(uint32_t colorNew, uint8_t tNr), - gamma32(uint32_t), - getLastShow(void), getPixelColor(uint16_t); - WS2812FX::Segment - &getSegment(uint8_t n), - &getFirstSelectedSeg(void), - &getMainSegment(void); + inline uint32_t getLastShow(void) { return _lastShow; } + inline uint32_t segColor(uint8_t i) { return _colors_t[i]; } - WS2812FX::Segment* - getSegments(void); + const char * + getModeData(uint8_t id = 0) { return (id && id<_modeCount) ? _modeData[id] : PSTR("Solid"); } - // builtin modes - uint16_t - mode_static(void), - mode_blink(void), - mode_blink_rainbow(void), - mode_strobe(void), - mode_strobe_rainbow(void), - mode_color_wipe(void), - mode_color_sweep(void), - mode_color_wipe_random(void), - mode_color_sweep_random(void), - mode_random_color(void), - mode_dynamic(void), - mode_breath(void), - mode_fade(void), - mode_scan(void), - mode_dual_scan(void), - mode_theater_chase(void), - mode_theater_chase_rainbow(void), - mode_rainbow(void), - mode_rainbow_cycle(void), - mode_running_lights(void), - mode_saw(void), - mode_twinkle(void), - mode_dissolve(void), - mode_dissolve_random(void), - mode_sparkle(void), - mode_flash_sparkle(void), - mode_hyper_sparkle(void), - mode_multi_strobe(void), - mode_android(void), - mode_chase_color(void), - mode_chase_random(void), - mode_chase_rainbow(void), - mode_chase_flash(void), - mode_chase_flash_random(void), - mode_chase_rainbow_white(void), - mode_colorful(void), - mode_traffic_light(void), - mode_running_color(void), - mode_aurora(void), - mode_running_random(void), - mode_larson_scanner(void), - mode_comet(void), - mode_fireworks(void), - mode_rain(void), - mode_tetrix(void), - mode_halloween(void), - mode_fire_flicker(void), - mode_gradient(void), - mode_loading(void), - mode_police(void), - mode_fairy(void), - mode_two_dots(void), - mode_fairytwinkle(void), - mode_running_dual(void), - mode_bicolor_chase(void), - mode_tricolor_chase(void), - mode_tricolor_wipe(void), - mode_tricolor_fade(void), - mode_lightning(void), - mode_icu(void), - mode_multi_comet(void), - mode_dual_larson_scanner(void), - mode_random_chase(void), - mode_oscillate(void), - mode_fire_2012(void), - mode_pride_2015(void), - mode_bpm(void), - mode_juggle(void), - mode_palette(void), - mode_colorwaves(void), - mode_fillnoise8(void), - mode_noise16_1(void), - mode_noise16_2(void), - mode_noise16_3(void), - mode_noise16_4(void), - mode_colortwinkle(void), - mode_lake(void), - mode_meteor(void), - mode_meteor_smooth(void), - mode_railway(void), - mode_ripple(void), - mode_twinklefox(void), - mode_twinklecat(void), - mode_halloween_eyes(void), - mode_static_pattern(void), - mode_tri_static_pattern(void), - mode_spots(void), - mode_spots_fade(void), - mode_glitter(void), - mode_candle(void), - mode_starburst(void), - mode_exploding_fireworks(void), - mode_bouncing_balls(void), - mode_sinelon(void), - mode_sinelon_dual(void), - mode_sinelon_rainbow(void), - mode_popcorn(void), - mode_drip(void), - mode_plasma(void), - mode_percent(void), - mode_ripple_rainbow(void), - mode_heartbeat(void), - mode_pacifica(void), - mode_candle_multi(void), - mode_solid_glitter(void), - mode_sunrise(void), - mode_phased(void), - mode_twinkleup(void), - mode_noisepal(void), - mode_sinewave(void), - mode_phased_noise(void), - mode_flow(void), - mode_chunchun(void), - mode_dancing_shadows(void), - mode_washing_machine(void), - mode_candy_cane(void), - mode_blends(void), - mode_tv_simulator(void), - mode_dynamic_smooth(void); + const char ** + getModeDataSrc(void) { return &(_modeData[0]); } // vectors use arrays for underlying data - private: - uint32_t crgb_to_col(CRGB fastled); - CRGB col_to_crgb(uint32_t); - CRGBPalette16 currentPalette; - CRGBPalette16 targetPalette; - - uint16_t _length, _virtualSegmentLength; - uint16_t _rand16seed; - uint8_t _brightness; - uint16_t _usedSegmentData = 0; - uint16_t _transitionDur = 750; - - uint8_t _targetFps = 42; - uint16_t _frametime = (1000/42); - uint16_t _cumulativeFps = 2; + Segment& getSegment(uint8_t id); + inline Segment& getFirstSelectedSeg(void) { return _segments[getFirstSelectedSegId()]; } + inline Segment& getMainSegment(void) { return _segments[getMainSegmentId()]; } + inline Segment* getSegments(void) { return &(_segments[0]); } + // 2D support (panels) bool - _isOffRefreshRequired = false, //periodic refresh is required for the strip to remain off. - _hasWhiteChannel = false, - _triggered; + isMatrix; - mode_ptr _mode[MODE_COUNT]; // SRAM footprint: 4 bytes per element +#ifndef WLED_DISABLE_2D + #define WLED_MAX_PANELS 64 + uint8_t + hPanels, + vPanels; - show_callback _callback = nullptr; - - // mode helper functions uint16_t - blink(uint32_t, uint32_t, bool strobe, bool), - candle(bool), - color_wipe(bool, bool), - dynamic(bool), - scan(bool), - running_base(bool,bool), - larson_scanner(bool), - sinelon_base(bool,bool), - dissolve(uint32_t), - chase(uint32_t, uint32_t, uint32_t, bool), - gradient_base(bool), - ripple_base(bool), - police_base(uint32_t, uint32_t), - running(uint32_t, uint32_t, bool theatre=false), - tricolor_chase(uint32_t, uint32_t), - twinklefox_base(bool), - spots_base(uint16_t), - phased_base(uint8_t); + panelH, + panelW, + matrixWidth, + matrixHeight; - CRGB twinklefox_one_twinkle(uint32_t ms, uint8_t salt, bool cat); - CRGB pacifica_one_layer(uint16_t i, CRGBPalette16& p, uint16_t cistart, uint16_t wavescale, uint8_t bri, uint16_t ioff); + typedef struct panel_bitfield_t { + bool bottomStart : 1; // starts at bottom? + bool rightStart : 1; // starts on right? + bool vertical : 1; // is vertical? + bool serpentine : 1; // is serpentine? + } Panel; + Panel + matrix, + panel[WLED_MAX_PANELS]; +#endif void - blendPixelColor(uint16_t n, uint32_t color, uint8_t blend), - startTransition(uint8_t oldBri, uint32_t oldCol, uint16_t dur, uint8_t segn, uint8_t slot), - estimateCurrentAndLimitBri(void), - load_gradient_palette(uint8_t), - handle_palette(void); + setUpMatrix(), + setPixelColorXY(int x, int y, uint32_t c); - uint16_t* customMappingTable = nullptr; - uint16_t customMappingSize = 0; - - uint32_t _lastPaletteChange = 0; - uint32_t _lastShow = 0; + // outsmart the compiler :) by correctly overloading + inline void setPixelColorXY(int x, int y, byte r, byte g, byte b, byte w = 0) { setPixelColorXY(x, y, RGBW32(r,g,b,w)); } // automatically inline + inline void setPixelColorXY(int x, int y, CRGB c) { setPixelColorXY(x, y, RGBW32(c.r,c.g,c.b,0)); } - uint32_t _colors_t[3]; - uint8_t _bri_t; - bool _no_rgb = false; + uint32_t + getPixelColorXY(uint16_t, uint16_t); + + // end 2D support + + void loadCustomPalettes(void); // loads custom palettes from JSON + CRGBPalette16 _currentPalette; // palette used for current effect (includes transition) + std::vector customPalettes; // TODO: move custom palettes out of WS2812FX class + + // using public variables to reduce code size increase due to inline function getSegment() (with bounds checking) + // and color transitions + uint32_t _colors_t[3]; // color used for effect (includes transition) + uint16_t _virtualSegmentLength; + + std::vector _segments; + friend class Segment; + + private: + uint16_t _length; + uint8_t _brightness; + uint16_t _transitionDur; + + uint8_t _targetFps; + uint16_t _frametime; + uint16_t _cumulativeFps; + + // will require only 1 byte + struct { + bool _isServicing : 1; + bool _isOffRefreshRequired : 1; //periodic refresh is required for the strip to remain off. + bool _hasWhiteChannel : 1; + bool _triggered : 1; + }; + + uint8_t _modeCount; + std::vector _mode; // SRAM footprint: 4 bytes per element + std::vector _modeData; // mode (effect) name and its slider control data array + + show_callback _callback; + + uint16_t* customMappingTable; + uint16_t customMappingSize; - uint8_t _segment_index = 0; - uint8_t _segment_index_palette_last = 99; + uint32_t _lastShow; + + uint8_t _segment_index; uint8_t _mainSegment; - segment _segments[MAX_NUM_SEGMENTS] = { // SRAM footprint: 24 bytes per element - // start, stop, offset, speed, intensity, palette, mode, options, grouping, spacing, opacity (unused), color[], capabilities - {0, 7, 0, DEFAULT_SPEED, 128, 0, DEFAULT_MODE, NO_OPTIONS, 1, 0, 255, {DEFAULT_COLOR}, 0} - }; - segment_runtime _segment_runtimes[MAX_NUM_SEGMENTS]; // SRAM footprint: 28 bytes per element - friend class Segment_runtime; - - ColorTransition transitions[MAX_NUM_TRANSITIONS]; //12 bytes per element - friend class ColorTransition; - - uint16_t - transitionProgress(uint8_t tNr); - - public: - inline bool hasWhiteChannel(void) {return _hasWhiteChannel;} - inline bool isOffRefreshRequired(void) {return _isOffRefreshRequired;} + void + estimateCurrentAndLimitBri(void); }; -//10 names per line -const char JSON_mode_names[] PROGMEM = R"=====([ -"Solid","Blink","Breathe","Wipe","Wipe Random","Random Colors","Sweep","Dynamic","Colorloop","Rainbow", -"Scan","Scan Dual","Fade","Theater","Theater Rainbow","Running","Saw","Twinkle","Dissolve","Dissolve Rnd", -"Sparkle","Sparkle Dark","Sparkle+","Strobe","Strobe Rainbow","Strobe Mega","Blink Rainbow","Android","Chase","Chase Random", -"Chase Rainbow","Chase Flash","Chase Flash Rnd","Rainbow Runner","Colorful","Traffic Light","Sweep Random","Chase 2","Aurora","Stream", -"Scanner","Lighthouse","Fireworks","Rain","Tetrix","Fire Flicker","Gradient","Loading","Police","Fairy", -"Two Dots","Fairytwinkle","Running Dual","Halloween","Chase 3","Tri Wipe","Tri Fade","Lightning","ICU","Multi Comet", -"Scanner Dual","Stream 2","Oscillate","Pride 2015","Juggle","Palette","Fire 2012","Colorwaves","Bpm","Fill Noise", -"Noise 1","Noise 2","Noise 3","Noise 4","Colortwinkles","Lake","Meteor","Meteor Smooth","Railway","Ripple", -"Twinklefox","Twinklecat","Halloween Eyes","Solid Pattern","Solid Pattern Tri","Spots","Spots Fade","Glitter","Candle","Fireworks Starburst", -"Fireworks 1D","Bouncing Balls","Sinelon","Sinelon Dual","Sinelon Rainbow","Popcorn","Drip","Plasma","Percent","Ripple Rainbow", -"Heartbeat","Pacifica","Candle Multi", "Solid Glitter","Sunrise","Phased","Twinkleup","Noise Pal", "Sine","Phased Noise", -"Flow","Chunchun","Dancing Shadows","Washing Machine","Candy Cane","Blends","TV Simulator","Dynamic Smooth" -])====="; - - -const char JSON_palette_names[] PROGMEM = R"=====([ -"Default","* Random Cycle","* Color 1","* Colors 1&2","* Color Gradient","* Colors Only","Party","Cloud","Lava","Ocean", -"Forest","Rainbow","Rainbow Bands","Sunset","Rivendell","Breeze","Red & Blue","Yellowout","Analogous","Splash", -"Pastel","Sunset 2","Beech","Vintage","Departure","Landscape","Beach","Sherbet","Hult","Hult 64", -"Drywet","Jul","Grintage","Rewhi","Tertiary","Fire","Icefire","Cyane","Light Pink","Autumn", -"Magenta","Magred","Yelmag","Yelblu","Orange & Teal","Tiamat","April Night","Orangery","C9","Sakura", -"Aurora","Atlantica","C9 2","C9 New","Temperature","Aurora 2","Retro Clown","Candy","Toxy Reaf","Fairy Reaf", -"Semi Blue","Pink Candy","Red Reaf","Aqua Flash","Yelblu Hot","Lite Light","Red Flash","Blink Red","Red Shift","Red Tide", -"Candy2" -])====="; +extern const char JSON_mode_names[]; +extern const char JSON_palette_names[]; #endif diff --git a/wled00/FX_2Dfcn.cpp b/wled00/FX_2Dfcn.cpp new file mode 100644 index 000000000..7159bcb05 --- /dev/null +++ b/wled00/FX_2Dfcn.cpp @@ -0,0 +1,517 @@ +/* + FX_2Dfcn.cpp contains all 2D utility functions + + LICENSE + The MIT License (MIT) + Copyright (c) 2022 Blaz Kristan (https://blaz.at/home) + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + + Parts of the code adapted from WLED Sound Reactive +*/ +#include "wled.h" +#include "FX.h" +#include "palettes.h" + +// setUpMatrix() - constructs ledmap array from matrix of panels with WxH pixels +// this converts physical (possibly irregular) LED arrangement into well defined +// array of logical pixels: fist entry corresponds to left-topmost logical pixel +// followed by horizontal pixels, when matrixWidth logical pixels are added they +// are followed by next row (down) of matrixWidth pixels (and so forth) +// note: matrix may be comprised of multiple panels each with different orientation +// but ledmap takes care of that. ledmap is constructed upon initialization +// so matrix should disable regular ledmap processing +void WS2812FX::setUpMatrix() { +#ifndef WLED_DISABLE_2D + // erase old ledmap, just in case. + if (customMappingTable != nullptr) delete[] customMappingTable; + customMappingTable = nullptr; + customMappingSize = 0; + + if (isMatrix) { + matrixWidth = hPanels * panelW; + matrixHeight = vPanels * panelH; + + // safety check + if (matrixWidth * matrixHeight > MAX_LEDS) { + matrixWidth = _length; + matrixHeight = 1; + isMatrix = false; + return; + } + + customMappingSize = matrixWidth * matrixHeight; + customMappingTable = new uint16_t[customMappingSize]; + + if (customMappingTable != nullptr) { + uint16_t startL; // index in custom mapping array (logical strip) + uint16_t startP; // position of 1st pixel of panel on (virtual) strip + uint16_t x, y, offset; + uint8_t h = matrix.vertical ? vPanels : hPanels; + uint8_t v = matrix.vertical ? hPanels : vPanels; + + for (uint8_t j=0, p=0; j= _length) return; + if (index < customMappingSize) index = customMappingTable[index]; + busses.setPixelColor(index, col); +} + +// returns RGBW values of pixel +uint32_t WS2812FX::getPixelColorXY(uint16_t x, uint16_t y) { +#ifndef WLED_DISABLE_2D + uint16_t index = (y * matrixWidth + x); +#else + uint16_t index = x; +#endif + if (index >= _length) return 0; + if (index < customMappingSize) index = customMappingTable[index]; + return busses.getPixelColor(index); +} + +/////////////////////////////////////////////////////////// +// Segment:: routines +/////////////////////////////////////////////////////////// + +#ifndef WLED_DISABLE_2D + +// XY(x,y) - gets pixel index within current segment (often used to reference leds[] array element) +uint16_t IRAM_ATTR Segment::XY(uint16_t x, uint16_t y) { + uint16_t width = virtualWidth(); // segment width in logical pixels + uint16_t height = virtualHeight(); // segment height in logical pixels + return (x%width) + (y%height) * width; +} + +void IRAM_ATTR Segment::setPixelColorXY(int x, int y, uint32_t col) +{ + if (!strip.isMatrix) return; // not a matrix set-up + if (x >= virtualWidth() || y >= virtualHeight() || x<0 || y<0) return; // if pixel would fall out of virtual segment just exit + + if (leds) leds[XY(x,y)] = col; + + uint8_t _bri_t = currentBri(on ? opacity : 0); + if (_bri_t < 255) { + byte r = scale8(R(col), _bri_t); + byte g = scale8(G(col), _bri_t); + byte b = scale8(B(col), _bri_t); + byte w = scale8(W(col), _bri_t); + col = RGBW32(r, g, b, w); + } + + if (reverse ) x = virtualWidth() - x - 1; + if (reverse_y) y = virtualHeight() - y - 1; + if (transpose) { uint16_t t = x; x = y; y = t; } // swap X & Y if segment transposed + + x *= groupLength(); // expand to physical pixels + y *= groupLength(); // expand to physical pixels + if (x >= width() || y >= height()) return; // if pixel would fall out of segment just exit + + for (int j = 0; j < grouping; j++) { // groupping vertically + for (int g = 0; g < grouping; g++) { // groupping horizontally + uint16_t xX = (x+g), yY = (y+j); + if (xX >= width() || yY >= height()) continue; // we have reached one dimension's end + + strip.setPixelColorXY(start + xX, startY + yY, col); + + if (mirror) { //set the corresponding horizontally mirrored pixel + if (transpose) strip.setPixelColorXY(start + xX, startY + height() - yY - 1, col); + else strip.setPixelColorXY(start + width() - xX - 1, startY + yY, col); + } + if (mirror_y) { //set the corresponding vertically mirrored pixel + if (transpose) strip.setPixelColorXY(start + width() - xX - 1, startY + yY, col); + else strip.setPixelColorXY(start + xX, startY + height() - yY - 1, col); + } + if (mirror_y && mirror) { //set the corresponding vertically AND horizontally mirrored pixel + strip.setPixelColorXY(width() - xX - 1, height() - yY - 1, col); + } + } + } +} + +// anti-aliased version of setPixelColorXY() +void Segment::setPixelColorXY(float x, float y, uint32_t col, bool aa) +{ + if (!strip.isMatrix) return; // not a matrix set-up + if (x<0.0f || x>1.0f || y<0.0f || y>1.0f) return; // not normalized + + const uint16_t cols = virtualWidth(); + const uint16_t rows = virtualHeight(); + + float fX = x * (cols-1); + float fY = y * (rows-1); + if (aa) { + uint16_t xL = roundf(fX-0.49f); + uint16_t xR = roundf(fX+0.49f); + uint16_t yT = roundf(fY-0.49f); + uint16_t yB = roundf(fY+0.49f); + float dL = (fX - xL)*(fX - xL); + float dR = (xR - fX)*(xR - fX); + float dT = (fY - yT)*(fY - yT); + float dB = (yB - fY)*(yB - fY); + uint32_t cXLYT = getPixelColorXY(xL, yT); + uint32_t cXRYT = getPixelColorXY(xR, yT); + uint32_t cXLYB = getPixelColorXY(xL, yB); + uint32_t cXRYB = getPixelColorXY(xR, yB); + + if (xL!=xR && yT!=yB) { + setPixelColorXY(xL, yT, color_blend(col, cXLYT, uint8_t(sqrtf(dL*dT)*255.0f))); // blend TL pixel + setPixelColorXY(xR, yT, color_blend(col, cXRYT, uint8_t(sqrtf(dR*dT)*255.0f))); // blend TR pixel + setPixelColorXY(xL, yB, color_blend(col, cXLYB, uint8_t(sqrtf(dL*dB)*255.0f))); // blend BL pixel + setPixelColorXY(xR, yB, color_blend(col, cXRYB, uint8_t(sqrtf(dR*dB)*255.0f))); // blend BR pixel + } else if (xR!=xL && yT==yB) { + setPixelColorXY(xR, yT, color_blend(col, cXLYT, uint8_t(dL*255.0f))); // blend L pixel + setPixelColorXY(xR, yT, color_blend(col, cXRYT, uint8_t(dR*255.0f))); // blend R pixel + } else if (xR==xL && yT!=yB) { + setPixelColorXY(xR, yT, color_blend(col, cXLYT, uint8_t(dT*255.0f))); // blend T pixel + setPixelColorXY(xL, yB, color_blend(col, cXLYB, uint8_t(dB*255.0f))); // blend B pixel + } else { + setPixelColorXY(xL, yT, col); // exact match (x & y land on a pixel) + } + } else { + setPixelColorXY(uint16_t(roundf(fX)), uint16_t(roundf(fY)), col); + } +} + +// returns RGBW values of pixel +uint32_t Segment::getPixelColorXY(uint16_t x, uint16_t y) { + int i = XY(x,y); + if (leds) return RGBW32(leds[i].r, leds[i].g, leds[i].b, 0); + if (reverse ) x = virtualWidth() - x - 1; + if (reverse_y) y = virtualHeight() - y - 1; + if (transpose) { uint16_t t = x; x = y; y = t; } // swap X & Y if segment transposed + x *= groupLength(); // expand to physical pixels + y *= groupLength(); // expand to physical pixels + if (x >= width() || y >= height()) return 0; + return strip.getPixelColorXY(start + x, startY + y); +} + +// Blends the specified color with the existing pixel color. +void Segment::blendPixelColorXY(uint16_t x, uint16_t y, uint32_t color, uint8_t blend) { + setPixelColorXY(x, y, color_blend(getPixelColorXY(x,y), color, blend)); +} + +// Adds the specified color with the existing pixel color perserving color balance. +void Segment::addPixelColorXY(int x, int y, uint32_t color) { + setPixelColorXY(x, y, color_add(getPixelColorXY(x,y), color)); +} + +void Segment::fadePixelColorXY(uint16_t x, uint16_t y, uint8_t fade) { + CRGB pix = CRGB(getPixelColorXY(x,y)).nscale8_video(fade); + setPixelColor(x, y, pix); +} + +// blurRow: perform a blur on a row of a rectangular matrix +void Segment::blurRow(uint16_t row, fract8 blur_amount) { + const uint16_t cols = virtualWidth(); + const uint16_t rows = virtualHeight(); + + if (row >= rows) return; + // blur one row + uint8_t keep = 255 - blur_amount; + uint8_t seep = blur_amount >> 1; + CRGB carryover = CRGB::Black; + for (uint16_t x = 0; x < cols; x++) { + CRGB cur = getPixelColorXY(x, row); + CRGB part = cur; + part.nscale8(seep); + cur.nscale8(keep); + cur += carryover; + if (x) { + CRGB prev = CRGB(getPixelColorXY(x-1, row)) + part; + setPixelColorXY(x-1, row, prev); + } + setPixelColorXY(x, row, cur); + carryover = part; + } +} + +// blurCol: perform a blur on a column of a rectangular matrix +void Segment::blurCol(uint16_t col, fract8 blur_amount) { + const uint16_t cols = virtualWidth(); + const uint16_t rows = virtualHeight(); + + if (col >= cols) return; + // blur one column + uint8_t keep = 255 - blur_amount; + uint8_t seep = blur_amount >> 1; + CRGB carryover = CRGB::Black; + for (uint16_t i = 0; i < rows; i++) { + CRGB cur = getPixelColorXY(col, i); + CRGB part = cur; + part.nscale8(seep); + cur.nscale8(keep); + cur += carryover; + if (i) { + CRGB prev = CRGB(getPixelColorXY(col, i-1)) + part; + setPixelColorXY(col, i-1, prev); + } + setPixelColorXY(col, i, cur); + carryover = part; + } +} + +// 1D Box blur (with added weight - blur_amount: [0=no blur, 255=max blur]) +void Segment::box_blur(uint16_t i, bool vertical, fract8 blur_amount) { + const uint16_t cols = virtualWidth(); + const uint16_t rows = virtualHeight(); + const uint16_t dim1 = vertical ? rows : cols; + const uint16_t dim2 = vertical ? cols : rows; + if (i >= dim2) return; + const float seep = blur_amount/255.f; + const float keep = 3.f - 2.f*seep; + // 1D box blur + CRGB tmp[dim1]; + for (uint16_t j = 0; j < dim1; j++) { + uint16_t x = vertical ? i : j; + uint16_t y = vertical ? j : i; + uint16_t xp = vertical ? x : x-1; + uint16_t yp = vertical ? y-1 : y; + uint16_t xn = vertical ? x : x+1; + uint16_t yn = vertical ? y+1 : y; + CRGB curr = getPixelColorXY(x,y); + CRGB prev = (xp<0 || yp<0) ? CRGB::Black : getPixelColorXY(xp,yp); + CRGB next = ((vertical && yn>=dim1) || (!vertical && xn>=dim1)) ? CRGB::Black : getPixelColorXY(xn,yn); + uint16_t r, g, b; + r = (curr.r*keep + (prev.r + next.r)*seep) / 3; + g = (curr.g*keep + (prev.g + next.g)*seep) / 3; + b = (curr.b*keep + (prev.b + next.b)*seep) / 3; + tmp[j] = CRGB(r,g,b); + } + for (uint16_t j = 0; j < dim1; j++) { + uint16_t x = vertical ? i : j; + uint16_t y = vertical ? j : i; + setPixelColorXY(x, y, tmp[j]); + } +} + +// blur1d: one-dimensional blur filter. Spreads light to 2 line neighbors. +// blur2d: two-dimensional blur filter. Spreads light to 8 XY neighbors. +// +// 0 = no spread at all +// 64 = moderate spreading +// 172 = maximum smooth, even spreading +// +// 173..255 = wider spreading, but increasing flicker +// +// Total light is NOT entirely conserved, so many repeated +// calls to 'blur' will also result in the light fading, +// eventually all the way to black; this is by design so that +// it can be used to (slowly) clear the LEDs to black. + +void Segment::blur1d(fract8 blur_amount) { + const uint16_t rows = virtualHeight(); + for (uint16_t y = 0; y < rows; y++) blurRow(y, blur_amount); +} + +void Segment::moveX(int8_t delta) { + const uint16_t cols = virtualWidth(); + const uint16_t rows = virtualHeight(); + if (!delta) return; + if (delta > 0) { + for (uint8_t y = 0; y < rows; y++) for (uint8_t x = 0; x < cols-1; x++) { + if (x + delta >= cols) break; + setPixelColorXY(x, y, getPixelColorXY((x + delta)%cols, y)); + } + } else { + for (uint8_t y = 0; y < rows; y++) for (int16_t x = cols-1; x >= 0; x--) { + if (x + delta < 0) break; + setPixelColorXY(x, y, getPixelColorXY(x + delta, y)); + } + } +} + +void Segment::moveY(int8_t delta) { + const uint16_t cols = virtualWidth(); + const uint16_t rows = virtualHeight(); + if (!delta) return; + if (delta > 0) { + for (uint8_t x = 0; x < cols; x++) for (uint8_t y = 0; y < rows-1; y++) { + if (y + delta >= rows) break; + setPixelColorXY(x, y, getPixelColorXY(x, (y + delta))); + } + } else { + for (uint8_t x = 0; x < cols; x++) for (int16_t y = rows-1; y >= 0; y--) { + if (y + delta < 0) break; + setPixelColorXY(x, y, getPixelColorXY(x, y + delta)); + } + } +} + +// move() - move all pixels in desired direction delta number of pixels +// @param dir direction: 0=left, 1=left-up, 2=up, 3=right-up, 4=right, 5=right-down, 6=down, 7=left-down +// @param delta number of pixels to move +void Segment::move(uint8_t dir, uint8_t delta) { + if (delta==0) return; + switch (dir) { + case 0: moveX( delta); break; + case 1: moveX( delta); moveY( delta); break; + case 2: moveY( delta); break; + case 3: moveX(-delta); moveY( delta); break; + case 4: moveX(-delta); break; + case 5: moveX(-delta); moveY(-delta); break; + case 6: moveY(-delta); break; + case 7: moveX( delta); moveY(-delta); break; + } +} + +// by stepko, taken from https://editor.soulmatelights.com/gallery/573-blobs +void Segment::fill_circle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB col) { + const uint16_t cols = virtualWidth(); + const uint16_t rows = virtualHeight(); + for (int16_t y = -radius; y <= radius; y++) { + for (int16_t x = -radius; x <= radius; x++) { + if (x * x + y * y <= radius * radius && + int16_t(cx)+x>=0 && int16_t(cy)+y>=0 && + int16_t(cx)+x= cols || x1 >= cols || y0 >= rows || y1 >= rows) return; + const int16_t dx = abs(x1-x0), sx = x0dy ? dx : -dy)/2, e2; + for (;;) { + addPixelColorXY(x0,y0,c); + if (x0==x1 && y0==y1) break; + e2 = err; + if (e2 >-dx) { err -= dy; x0 += sx; } + if (e2 < dy) { err += dx; y0 += sy; } + } +} + +#include "src/font/console_font_4x6.h" +#include "src/font/console_font_5x8.h" +#include "src/font/console_font_5x12.h" +#include "src/font/console_font_6x8.h" +#include "src/font/console_font_7x9.h" + +// draws a raster font character on canvas +// only supports: 4x6=24, 5x8=40, 5x12=60, 6x8=48 and 7x9=63 fonts ATM +void Segment::drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, uint32_t color) { + if (chr < 32 || chr > 126) return; // only ASCII 32-126 supported + chr -= 32; // align with font table entries + const uint16_t cols = virtualWidth(); + const uint16_t rows = virtualHeight(); + const int font = w*h; + + //if (w<5 || w>6 || h!=8) return; + for (int i = 0; i= rows) break; // drawing off-screen + uint8_t bits = 0; + switch (font) { + case 24: bits = pgm_read_byte_near(&console_font_4x6[(chr * h) + i]); break; // 5x8 font + case 40: bits = pgm_read_byte_near(&console_font_5x8[(chr * h) + i]); break; // 5x8 font + case 48: bits = pgm_read_byte_near(&console_font_6x8[(chr * h) + i]); break; // 6x8 font + case 63: bits = pgm_read_byte_near(&console_font_7x9[(chr * h) + i]); break; // 7x9 font + case 60: bits = pgm_read_byte_near(&console_font_5x12[(chr * h) + i]); break; // 5x12 font + default: return; + } + for (int j = 0; j= 0 || x0 < cols) && ((bits>>(j+(8-w))) & 0x01)) { // bit set & drawing on-screen + addPixelColorXY(x0, y0, color); + } + } + } +} + +#define WU_WEIGHT(a,b) ((uint8_t) (((a)*(b)+(a)+(b))>>8)) +void Segment::wu_pixel(uint32_t x, uint32_t y, CRGB c) { //awesome wu_pixel procedure by reddit u/sutaburosu + // extract the fractional parts and derive their inverses + uint8_t xx = x & 0xff, yy = y & 0xff, ix = 255 - xx, iy = 255 - yy; + // calculate the intensities for each affected pixel + uint8_t wu[4] = {WU_WEIGHT(ix, iy), WU_WEIGHT(xx, iy), + WU_WEIGHT(ix, yy), WU_WEIGHT(xx, yy)}; + // multiply the intensities by the colour, and saturating-add them to the pixels + for (int i = 0; i < 4; i++) { + CRGB led = getPixelColorXY((x >> 8) + (i & 1), (y >> 8) + ((i >> 1) & 1)); + led.r = qadd8(led.r, c.r * wu[i] >> 8); + led.g = qadd8(led.g, c.g * wu[i] >> 8); + led.b = qadd8(led.b, c.b * wu[i] >> 8); + setPixelColorXY(int((x >> 8) + (i & 1)), int((y >> 8) + ((i >> 1) & 1)), led); + } +} +#undef WU_WEIGHT + +#endif // WLED_DISABLE_2D diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index a79def615..5cd0242c9 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -69,15 +69,843 @@ #error "Max segments must be at least max number of busses!" #endif + +/////////////////////////////////////////////////////////////////////////////// +// Segment class implementation +/////////////////////////////////////////////////////////////////////////////// +uint16_t Segment::_usedSegmentData = 0U; // amount of RAM all segments use for their data[] +CRGB *Segment::_globalLeds = nullptr; + +// copy constructor +Segment::Segment(const Segment &orig) { + DEBUG_PRINTLN(F("-- Copy segment constructor --")); + memcpy(this, &orig, sizeof(Segment)); + name = nullptr; + data = nullptr; + _dataLen = 0; + _t = nullptr; + if (leds && !Segment::_globalLeds) leds = nullptr; + if (orig.name) { name = new char[strlen(orig.name)+1]; if (name) strcpy(name, orig.name); } + if (orig.data) { if (allocateData(orig._dataLen)) memcpy(data, orig.data, orig._dataLen); } + if (orig._t) { _t = new Transition(orig._t->_dur, orig._t->_briT, orig._t->_cctT, orig._t->_colorT); } + if (orig.leds && !Segment::_globalLeds) { leds = (CRGB*)malloc(sizeof(CRGB)*length()); if (leds) memcpy(leds, orig.leds, sizeof(CRGB)*length()); } +} + +// move constructor +Segment::Segment(Segment &&orig) noexcept { + DEBUG_PRINTLN(F("-- Move segment constructor --")); + memcpy(this, &orig, sizeof(Segment)); + orig.name = nullptr; + orig.data = nullptr; + orig._dataLen = 0; + orig._t = nullptr; + orig.leds = nullptr; +} + +// copy assignment +Segment& Segment::operator= (const Segment &orig) { + DEBUG_PRINTLN(F("-- Copying segment --")); + if (this != &orig) { + // clean destination + if (name) delete[] name; + if (_t) delete _t; + if (leds && !Segment::_globalLeds) free(leds); + deallocateData(); + // copy source + memcpy(this, &orig, sizeof(Segment)); + // erase pointers to allocated data + name = nullptr; + data = nullptr; + _dataLen = 0; + _t = nullptr; + if (!Segment::_globalLeds) leds = nullptr; + // copy source data + if (orig.name) { name = new char[strlen(orig.name)+1]; if (name) strcpy(name, orig.name); } + if (orig.data) { if (allocateData(orig._dataLen)) memcpy(data, orig.data, orig._dataLen); } + if (orig._t) { _t = new Transition(orig._t->_dur, orig._t->_briT, orig._t->_cctT, orig._t->_colorT); } + if (orig.leds && !Segment::_globalLeds) { leds = (CRGB*)malloc(sizeof(CRGB)*length()); if (leds) memcpy(leds, orig.leds, sizeof(CRGB)*length()); } + } + return *this; +} + +// move assignment +Segment& Segment::operator= (Segment &&orig) noexcept { + DEBUG_PRINTLN(F("-- Moving segment --")); + if (this != &orig) { + if (name) delete[] name; // free old name + deallocateData(); // free old runtime data + if (_t) delete _t; + if (leds && !Segment::_globalLeds) free(leds); + memcpy(this, &orig, sizeof(Segment)); + orig.name = nullptr; + orig.data = nullptr; + orig._dataLen = 0; + orig._t = nullptr; + orig.leds = nullptr; + } + return *this; +} + +bool Segment::allocateData(size_t len) { + if (data && _dataLen == len) return true; //already allocated + deallocateData(); + if (Segment::getUsedSegmentData() + len > MAX_SEGMENT_DATA) return false; //not enough memory + // if possible use SPI RAM on ESP32 + #if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_PSRAM) + if (psramFound()) + data = (byte*) ps_malloc(len); + else + #endif + data = (byte*) malloc(len); + if (!data) return false; //allocation failed + Segment::addUsedSegmentData(len); + _dataLen = len; + memset(data, 0, len); + return true; +} + +void Segment::deallocateData() { + if (!data) return; + free(data); + data = nullptr; + Segment::addUsedSegmentData(-_dataLen); + _dataLen = 0; +} + +/** + * If reset of this segment was requested, clears runtime + * settings of this segment. + * Must not be called while an effect mode function is running + * because it could access the data buffer and this method + * may free that data buffer. + */ +void Segment::resetIfRequired() { + if (reset) { + if (leds && !Segment::_globalLeds) { free(leds); leds = nullptr; } + //if (_t) { delete _t; _t = nullptr; transitional = false; } + next_time = 0; step = 0; call = 0; aux0 = 0; aux1 = 0; + reset = false; // setOption(SEG_OPTION_RESET, false); + } +} + +void Segment::setUpLeds() { + // deallocation happens in resetIfRequired() as it is called when segment changes or in destructor + if (Segment::_globalLeds) + #ifndef WLED_DISABLE_2D + leds = &Segment::_globalLeds[start + startY*strip.matrixWidth]; // TODO: remove this hack + #else + leds = &Segment::_globalLeds[start]; + #endif + else if (!leds) { + #if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_PSRAM) + if (psramFound()) + leds = (CRGB*)ps_malloc(sizeof(CRGB)*length()); + else + #endif + leds = (CRGB*)malloc(sizeof(CRGB)*length()); + } +} + +CRGBPalette16 &Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) { + static unsigned long _lastPaletteChange = 0; // perhaps it should be per segment + static CRGBPalette16 randomPalette = CRGBPalette16(DEFAULT_COLOR); + byte tcp[72]; + if (pal < 245 && pal > GRADIENT_PALETTE_COUNT+13) pal = 0; + if (pal > 245 && (strip.customPalettes.size() == 0 || 255U-pal > strip.customPalettes.size()-1)) pal = 0; + //default palette. Differs depending on effect + if (pal == 0) switch (mode) { + case FX_MODE_FIRE_2012 : pal = 35; break; // heat palette + case FX_MODE_COLORWAVES : pal = 26; break; // landscape 33 + case FX_MODE_FILLNOISE8 : pal = 9; break; // ocean colors + case FX_MODE_NOISE16_1 : pal = 20; break; // Drywet + case FX_MODE_NOISE16_2 : pal = 43; break; // Blue cyan yellow + case FX_MODE_NOISE16_3 : pal = 35; break; // heat palette + case FX_MODE_NOISE16_4 : pal = 26; break; // landscape 33 + case FX_MODE_GLITTER : pal = 11; break; // rainbow colors + case FX_MODE_SUNRISE : pal = 35; break; // heat palette + case FX_MODE_FLOW : pal = 6; break; // party + } + switch (pal) { + case 0: //default palette. Exceptions for specific effects above + targetPalette = PartyColors_p; break; + case 1: //periodically replace palette with a random one. Doesn't work with multiple FastLED segments + if (millis() - _lastPaletteChange > 5000 /*+ ((uint32_t)(255-intensity))*100*/) { + randomPalette = CRGBPalette16( + CHSV(random8(), random8(160, 255), random8(128, 255)), + CHSV(random8(), random8(160, 255), random8(128, 255)), + CHSV(random8(), random8(160, 255), random8(128, 255)), + CHSV(random8(), random8(160, 255), random8(128, 255))); + _lastPaletteChange = millis(); + } + targetPalette = randomPalette; break; + case 2: {//primary color only + CRGB prim = gamma32(colors[0]); + targetPalette = CRGBPalette16(prim); break;} + case 3: {//primary + secondary + CRGB prim = gamma32(colors[0]); + CRGB sec = gamma32(colors[1]); + targetPalette = CRGBPalette16(prim,prim,sec,sec); break;} + case 4: {//primary + secondary + tertiary + CRGB prim = gamma32(colors[0]); + CRGB sec = gamma32(colors[1]); + CRGB ter = gamma32(colors[2]); + targetPalette = CRGBPalette16(ter,sec,prim); break;} + case 5: {//primary + secondary (+tert if not off), more distinct + CRGB prim = gamma32(colors[0]); + CRGB sec = gamma32(colors[1]); + if (colors[2]) { + CRGB ter = gamma32(colors[2]); + targetPalette = CRGBPalette16(prim,prim,prim,prim,prim,sec,sec,sec,sec,sec,ter,ter,ter,ter,ter,prim); + } else { + targetPalette = CRGBPalette16(prim,prim,prim,prim,prim,prim,prim,prim,sec,sec,sec,sec,sec,sec,sec,sec); + } + break;} + case 6: //Party colors + targetPalette = PartyColors_p; break; + case 7: //Cloud colors + targetPalette = CloudColors_p; break; + case 8: //Lava colors + targetPalette = LavaColors_p; break; + case 9: //Ocean colors + targetPalette = OceanColors_p; break; + case 10: //Forest colors + targetPalette = ForestColors_p; break; + case 11: //Rainbow colors + targetPalette = RainbowColors_p; break; + case 12: //Rainbow stripe colors + targetPalette = RainbowStripeColors_p; break; + default: //progmem palettes + if (pal>245) { + targetPalette = strip.customPalettes[255-pal]; // we checked bounds above + } else { + memcpy_P(tcp, (byte*)pgm_read_dword(&(gGradientPalettes[pal-13])), 72); + targetPalette.loadDynamicGradientPalette(tcp); + } + break; + } + return targetPalette; +} + +void Segment::startTransition(uint16_t dur) { + if (transitional || _t) return; // already in transition no need to store anything + + // starting a transition has to occur before change so we get current values 1st + uint8_t _briT = currentBri(on ? opacity : 0); + uint8_t _cctT = currentBri(cct, true); + CRGBPalette16 _palT; loadPalette(_palT, palette); + uint8_t _modeP = mode; + uint32_t _colorT[NUM_COLORS]; + for (size_t i=0; i_briT = _briT; + _t->_cctT = _cctT; + _t->_palT = _palT; + _t->_modeP = _modeP; + for (size_t i=0; i_colorT[i] = _colorT[i]; + transitional = true; // setOption(SEG_OPTION_TRANSITIONAL, true); +} + +// transition progression between 0-65535 +uint16_t Segment::progress() { + if (!transitional || !_t) return 0xFFFFU; + uint32_t timeNow = millis(); + if (timeNow - _t->_start > _t->_dur || _t->_dur == 0) return 0xFFFFU; + return (timeNow - _t->_start) * 0xFFFFU / _t->_dur; +} + +uint8_t Segment::currentBri(uint8_t briNew, bool useCct) { + if (transitional && _t) { + uint32_t prog = progress() + 1; + if (useCct) return ((briNew * prog) + _t->_cctT * (0x10000 - prog)) >> 16; + else return ((briNew * prog) + _t->_briT * (0x10000 - prog)) >> 16; + } else { + return briNew; + } +} + +uint8_t Segment::currentMode(uint8_t newMode) { + if (transitional && _t) { + return _t->_modeP; + } else { + return newMode; + } +} + +uint32_t Segment::currentColor(uint8_t slot, uint32_t colorNew) { + return transitional && _t ? color_blend(_t->_colorT[slot], colorNew, progress(), true) : colorNew; +} + +CRGBPalette16 &Segment::currentPalette(CRGBPalette16 &targetPalette, uint8_t pal) { + loadPalette(targetPalette, pal); + if (transitional && _t && progress() < 0xFFFFU) { + // blend palettes + // there are about 255 blend passes of 48 "blends" to completely blend two palettes (in _dur time) + // minimum blend time is 100ms maximum is 65535ms + uint32_t timeMS = millis() - _t->_start; + uint16_t noOfBlends = (255U * timeMS / _t->_dur) - _t->_prevPaletteBlends; + for (int i=0; i_prevPaletteBlends++) nblendPaletteTowardPalette(_t->_palT, targetPalette, 48); + targetPalette = _t->_palT; // copy transitioning/temporary palette + } + return targetPalette; +} + +void Segment::handleTransition() { + if (!transitional) return; + unsigned long maxWait = millis() + 20; + if (mode == FX_MODE_STATIC && next_time > maxWait) next_time = maxWait; + if (progress() == 0xFFFFU) { + if (_t) { + if (_t->_modeP != mode) markForReset(); + delete _t; + _t = nullptr; + } + transitional = false; // finish transitioning segment + } +} + +bool Segment::setColor(uint8_t slot, uint32_t c) { //returns true if changed + if (slot >= NUM_COLORS || c == colors[slot]) return false; + if (fadeTransition) startTransition(strip.getTransition()); // start transition prior to change + colors[slot] = c; + return true; +} + +void Segment::setCCT(uint16_t k) { + if (k > 255) { //kelvin value, convert to 0-255 + if (k < 1900) k = 1900; + if (k > 10091) k = 10091; + k = (k - 1900) >> 5; + } + if (cct == k) return; + if (fadeTransition) startTransition(strip.getTransition()); // start transition prior to change + cct = k; +} + +void Segment::setOpacity(uint8_t o) { + if (opacity == o) return; + if (fadeTransition) startTransition(strip.getTransition()); // start transition prior to change + opacity = o; +} + +void Segment::setOption(uint8_t n, bool val) { + bool prevOn = on; + if (fadeTransition && n == SEG_OPTION_ON && val != prevOn) startTransition(strip.getTransition()); // start transition prior to change + if (val) options |= 0x01 << n; + else options &= ~(0x01 << n); +} + +void Segment::setMode(uint8_t fx, bool loadDefaults) { + // if we have a valid mode & is not reserved + if (fx < strip.getModeCount() && strncmp_P("RSVD", strip.getModeData(fx), 4)) { + if (fx != mode) { + startTransition(strip.getTransition()); // set effect transitions + //markForReset(); // transition will handle this + mode = fx; + + // load default values from effect string + if (loadDefaults) { + int16_t sOpt; + sOpt = extractModeDefaults(fx, "sx"); if (sOpt >= 0) speed = sOpt; + sOpt = extractModeDefaults(fx, "ix"); if (sOpt >= 0) intensity = sOpt; + sOpt = extractModeDefaults(fx, "c1"); if (sOpt >= 0) custom1 = sOpt; + sOpt = extractModeDefaults(fx, "c2"); if (sOpt >= 0) custom2 = sOpt; + sOpt = extractModeDefaults(fx, "c3"); if (sOpt >= 0) custom3 = sOpt; + sOpt = extractModeDefaults(fx, "mp12"); if (sOpt >= 0) map1D2D = constrain(sOpt, 0, 7); + sOpt = extractModeDefaults(fx, "ssim"); if (sOpt >= 0) soundSim = constrain(sOpt, 0, 7); + sOpt = extractModeDefaults(fx, "rev"); if (sOpt >= 0) reverse = (bool)sOpt; + sOpt = extractModeDefaults(fx, "mi"); if (sOpt >= 0) mirror = (bool)sOpt; // NOTE: setting this option is a risky business + sOpt = extractModeDefaults(fx, "rY"); if (sOpt >= 0) reverse_y = (bool)sOpt; + sOpt = extractModeDefaults(fx, "mY"); if (sOpt >= 0) mirror_y = (bool)sOpt; // NOTE: setting this option is a risky business + sOpt = extractModeDefaults(fx, "pal"); + if (sOpt >= 0 && (size_t)sOpt < strip.getPaletteCount() + strip.customPalettes.size()) { + if (sOpt != palette) { + palette = sOpt; + } + } + } + } + } +} + +void Segment::setPalette(uint8_t pal) { + if (pal < strip.getPaletteCount()) { + if (pal != palette) { + if (strip.paletteFade) startTransition(strip.getTransition()); + palette = pal; + } + } +} + +// 2D matrix +uint16_t Segment::virtualWidth() const { + uint16_t groupLen = groupLength(); + uint16_t vWidth = ((transpose ? height() : width()) + groupLen - 1) / groupLen; + if (mirror) vWidth = (vWidth + 1) /2; // divide by 2 if mirror, leave at least a single LED + return vWidth; +} + +uint16_t Segment::virtualHeight() const { + uint16_t groupLen = groupLength(); + uint16_t vHeight = ((transpose ? width() : height()) + groupLen - 1) / groupLen; + if (mirror_y) vHeight = (vHeight + 1) /2; // divide by 2 if mirror, leave at least a single LED + return vHeight; +} + +uint16_t Segment::nrOfVStrips() const { + uint16_t vLen = 1; +#ifndef WLED_DISABLE_2D + if (is2D()) { + switch (map1D2D) { + case M12_pBar: + vLen = virtualWidth(); + break; + } + } +#endif + return vLen; +} + +// 1D strip +uint16_t Segment::virtualLength() const { +#ifndef WLED_DISABLE_2D + if (is2D()) { + uint16_t vW = virtualWidth(); + uint16_t vH = virtualHeight(); + uint16_t vLen = vW * vH; // use all pixels from segment + switch (map1D2D) { + case M12_pBar: + vLen = vH; + break; + case M12_pCorner: + case M12_pArc: + vLen = max(vW,vH); // get the longest dimension + break; + } + return vLen; + } +#endif + uint16_t groupLen = groupLength(); + uint16_t vLength = (length() + groupLen - 1) / groupLen; + if (mirror) vLength = (vLength + 1) /2; // divide by 2 if mirror, leave at least a single LED + return vLength; +} + +void IRAM_ATTR Segment::setPixelColor(int i, uint32_t col) +{ + int vStrip = i>>16; // hack to allow running on virtual strips (2D segment columns/rows) + i &= 0xFFFF; + + if (i >= virtualLength() || i<0) return; // if pixel would fall out of segment just exit + +#ifndef WLED_DISABLE_2D + if (is2D()) { // if this does not work use strip.isMatrix + uint16_t vH = virtualHeight(); // segment height in logical pixels + uint16_t vW = virtualWidth(); + switch (map1D2D) { + case M12_Pixels: + // use all available pixels as a long strip + setPixelColorXY(i % vW, i / vW, col); + break; + case M12_pBar: + // expand 1D effect vertically or have it play on virtual strips + if (vStrip>0) setPixelColorXY(vStrip - 1, vH - i - 1, col); + else for (int x = 0; x < vW; x++) setPixelColorXY(x, vH - i - 1, col); + break; + case M12_pArc: + // expand in circular fashion from center + if (i==0) + setPixelColorXY(0, 0, col); + else { + float step = HALF_PI / (2.85f*i); + for (float rad = 0.0f; rad <= HALF_PI+step/2; rad += step) { + // may want to try float version as well (with or without antialiasing) + int x = roundf(sin_t(rad) * i); + int y = roundf(cos_t(rad) * i); + setPixelColorXY(x, y, col); + } + } + break; + case M12_pCorner: + for (int x = 0; x <= i; x++) setPixelColorXY(x, i, col); + for (int y = 0; y < i; y++) setPixelColorXY(i, y, col); + break; + } + return; + } else if (strip.isMatrix && (width()==1 || height()==1)) { // TODO remove this hack + // we have a vertical or horizontal 1D segment (WARNING: virtual...() may be transposed) + int x = 0, y = 0; + if (virtualHeight()>1) y = i; + if (virtualWidth() >1) x = i; + setPixelColorXY(x, y, col); + return; + } +#endif + + if (leds) leds[i] = col; + + uint16_t len = length(); + uint8_t _bri_t = currentBri(on ? opacity : 0); + if (_bri_t < 255) { + byte r = scale8(R(col), _bri_t); + byte g = scale8(G(col), _bri_t); + byte b = scale8(B(col), _bri_t); + byte w = scale8(W(col), _bri_t); + col = RGBW32(r, g, b, w); + } + + // expand pixel (taking into account start, grouping, spacing [and offset]) + i = i * groupLength(); + if (reverse) { // is segment reversed? + if (mirror) { // is segment mirrored? + i = (len - 1) / 2 - i; //only need to index half the pixels + } else { + i = (len - 1) - i; + } + } + i += start; // starting pixel in a group + + // set all the pixels in the group + for (int j = 0; j < grouping; j++) { + uint16_t indexSet = i + ((reverse) ? -j : j); + if (indexSet >= start && indexSet < stop) { + if (mirror) { //set the corresponding mirrored pixel + uint16_t indexMir = stop - indexSet + start - 1; + indexMir += offset; // offset/phase + if (indexMir >= stop) indexMir -= len; // wrap + strip.setPixelColor(indexMir, col); + } + indexSet += offset; // offset/phase + if (indexSet >= stop) indexSet -= len; // wrap + strip.setPixelColor(indexSet, col); + } + } +} + +// anti-aliased normalized version of setPixelColor() +void Segment::setPixelColor(float i, uint32_t col, bool aa) +{ + int vStrip = int(i/10.0f); // hack to allow running on virtual strips (2D segment columns/rows) + i -= int(i); + + if (i<0.0f || i>1.0f) return; // not normalized + + float fC = i * (virtualLength()-1); + if (aa) { + uint16_t iL = roundf(fC-0.49f); + uint16_t iR = roundf(fC+0.49f); + float dL = (fC - iL)*(fC - iL); + float dR = (iR - fC)*(iR - fC); + uint32_t cIL = getPixelColor(iL | (vStrip<<16)); + uint32_t cIR = getPixelColor(iR | (vStrip<<16)); + if (iR!=iL) { + // blend L pixel + cIL = color_blend(col, cIL, uint8_t(dL*255.0f)); + setPixelColor(iL | (vStrip<<16), cIL); + // blend R pixel + cIR = color_blend(col, cIR, uint8_t(dR*255.0f)); + setPixelColor(iR | (vStrip<<16), cIR); + } else { + // exact match (x & y land on a pixel) + setPixelColor(iL | (vStrip<<16), col); + } + } else { + setPixelColor(uint16_t(roundf(fC)) | (vStrip<<16), col); + } +} + +uint32_t Segment::getPixelColor(int i) +{ + int vStrip = i>>16; + i &= 0xFFFF; + +#ifndef WLED_DISABLE_2D + if (is2D()) { // if this does not work use strip.isMatrix + uint16_t vH = virtualHeight(); // segment height in logical pixels + uint16_t vW = virtualWidth(); + switch (map1D2D) { + case M12_Pixels: + return getPixelColorXY(i % vW, i / vW); + break; + case M12_pBar: + if (vStrip>0) return getPixelColorXY(vStrip - 1, vH - i -1); + else return getPixelColorXY(0, vH - i -1); + break; + case M12_pArc: + case M12_pCorner: + // use longest dimension + return vW>vH ? getPixelColorXY(i, 0) : getPixelColorXY(0, i); + break; + } + return 0; + } +#endif + + if (leds) return RGBW32(leds[i].r, leds[i].g, leds[i].b, 0); + + if (reverse) i = virtualLength() - i - 1; + i *= groupLength(); + i += start; + /* offset/phase */ + i += offset; + if (i >= stop) i -= length(); + return strip.getPixelColor(i); +} + +uint8_t Segment::differs(Segment& b) const { + uint8_t d = 0; + if (start != b.start) d |= SEG_DIFFERS_BOUNDS; + if (stop != b.stop) d |= SEG_DIFFERS_BOUNDS; + if (offset != b.offset) d |= SEG_DIFFERS_GSO; + if (grouping != b.grouping) d |= SEG_DIFFERS_GSO; + if (spacing != b.spacing) d |= SEG_DIFFERS_GSO; + if (opacity != b.opacity) d |= SEG_DIFFERS_BRI; + if (mode != b.mode) d |= SEG_DIFFERS_FX; + if (speed != b.speed) d |= SEG_DIFFERS_FX; + if (intensity != b.intensity) d |= SEG_DIFFERS_FX; + if (palette != b.palette) d |= SEG_DIFFERS_FX; + if (custom1 != b.custom1) d |= SEG_DIFFERS_FX; + if (custom2 != b.custom2) d |= SEG_DIFFERS_FX; + if (custom3 != b.custom3) d |= SEG_DIFFERS_FX; + if (startY != b.startY) d |= SEG_DIFFERS_BOUNDS; + if (stopY != b.stopY) d |= SEG_DIFFERS_BOUNDS; + + //bit pattern: msb first: [transposed mirrorY reverseY] transitional (tbd) paused needspixelstate mirrored on reverse selected + if ((options & 0b1111111110011110) != (b.options & 0b1111111110011110)) d |= SEG_DIFFERS_OPT; + if ((options & 0x01) != (b.options & 0x01)) d |= SEG_DIFFERS_SEL; + + for (uint8_t i = 0; i < NUM_COLORS; i++) if (colors[i] != b.colors[i]) d |= SEG_DIFFERS_COL; + + return d; +} + +void Segment::refreshLightCapabilities() { + uint8_t capabilities = 0x01; + + for (uint8_t b = 0; b < busses.getNumBusses(); b++) { + Bus *bus = busses.getBus(b); + if (bus == nullptr || bus->getLength()==0) break; + if (!bus->isOk()) continue; + if (bus->getStart() >= stop) continue; + if (bus->getStart() + bus->getLength() <= start) continue; + + uint8_t type = bus->getType(); + if (type == TYPE_ANALOG_1CH || (!cctFromRgb && type == TYPE_ANALOG_2CH)) capabilities &= 0xFE; // does not support RGB + if (bus->isRgbw()) capabilities |= 0x02; // segment supports white channel + if (!cctFromRgb) { + switch (type) { + case TYPE_ANALOG_5CH: + case TYPE_ANALOG_2CH: + capabilities |= 0x04; //segment supports white CCT + } + } + if (correctWB && type != TYPE_ANALOG_1CH) capabilities |= 0x04; //white balance correction (uses CCT slider) + uint8_t aWM = Bus::getAutoWhiteMode()<255 ? Bus::getAutoWhiteMode() : bus->getAWMode(); + bool whiteSlider = (aWM == RGBW_MODE_DUAL || aWM == RGBW_MODE_MANUAL_ONLY); // white slider allowed + if (bus->isRgbw() && (whiteSlider || !(capabilities & 0x01))) capabilities |= 0x08; // allow white channel adjustments (AWM allows or is not RGB) + } + _capabilities = capabilities; +} + +/* + * Fills segment with color + */ +void Segment::fill(uint32_t c) { + const uint16_t cols = is2D() ? virtualWidth() : virtualLength(); + const uint16_t rows = virtualHeight(); // will be 1 for 1D + for(uint16_t y = 0; y < rows; y++) for (uint16_t x = 0; x < cols; x++) { + if (is2D()) setPixelColorXY(x, y, c); + else setPixelColor(x, c); + } +} + +// Blends the specified color with the existing pixel color. +void Segment::blendPixelColor(int n, uint32_t color, uint8_t blend) { + setPixelColor(n, color_blend(getPixelColor(n), color, blend)); +} + +// Adds the specified color with the existing pixel color perserving color balance. +void Segment::addPixelColor(int n, uint32_t color) { + setPixelColor(n, color_add(getPixelColor(n), color)); +} + +void Segment::fadePixelColor(uint16_t n, uint8_t fade) { + CRGB pix = CRGB(getPixelColor(n)).nscale8_video(fade); + setPixelColor(n, pix); +} + +/* + * fade out function, higher rate = quicker fade + */ +void Segment::fade_out(uint8_t rate) { + const uint16_t cols = is2D() ? virtualWidth() : virtualLength(); + const uint16_t rows = virtualHeight(); // will be 1 for 1D + + rate = (255-rate) >> 1; + float mappedRate = float(rate) +1.1; + + 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); + + for (uint16_t y = 0; y < rows; y++) for (uint16_t x = 0; x < cols; x++) { + color = is2D() ? getPixelColorXY(x, y) : getPixelColor(x); + 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); + } +} + +// fades all pixels to black using nscale8() +void Segment::fadeToBlackBy(uint8_t fadeBy) { + const uint16_t cols = is2D() ? virtualWidth() : virtualLength(); + const uint16_t rows = virtualHeight(); // will be 1 for 1D + + for (uint16_t y = 0; y < rows; y++) for (uint16_t x = 0; x < cols; x++) { + if (is2D()) setPixelColorXY(x, y, CRGB(getPixelColorXY(x,y)).nscale8(255-fadeBy)); + else setPixelColor(x, CRGB(getPixelColor(x)).nscale8(255-fadeBy)); + } +} + +/* + * blurs segment content, source: FastLED colorutils.cpp + */ +void Segment::blur(uint8_t blur_amount) +{ +#ifndef WLED_DISABLE_2D + if (is2D()) { + // compatibility with 2D + const uint16_t cols = virtualWidth(); + const uint16_t rows = virtualHeight(); + for (uint16_t i = 0; i < rows; i++) blurRow(i, blur_amount); // blur all rows + for (uint16_t k = 0; k < cols; k++) blurCol(k, blur_amount); // blur all columns + return; + } +#endif + uint8_t keep = 255 - blur_amount; + uint8_t seep = blur_amount >> 1; + CRGB carryover = CRGB::Black; + for(uint16_t i = 0; i < virtualLength(); i++) + { + CRGB cur = CRGB(getPixelColor(i)); + CRGB part = cur; + part.nscale8(seep); + cur.nscale8(keep); + cur += carryover; + if(i > 0) { + uint32_t c = getPixelColor(i-1); + uint8_t r = R(c); + uint8_t g = G(c); + uint8_t b = B(c); + setPixelColor(i-1, qadd8(r, part.red), qadd8(g, part.green), qadd8(b, part.blue)); + } + setPixelColor(i,cur.red, cur.green, cur.blue); + carryover = part; + } +} + +/* + * Put a value 0 to 255 in to get a color value. + * The colours are a transition r -> g -> b -> back to r + * Inspired by the Adafruit examples. + */ +uint32_t Segment::color_wheel(uint8_t pos) { // TODO + if (palette) return color_from_palette(pos, false, true, 0); + pos = 255 - pos; + if(pos < 85) { + return ((uint32_t)(255 - pos * 3) << 16) | ((uint32_t)(0) << 8) | (pos * 3); + } else if(pos < 170) { + pos -= 85; + return ((uint32_t)(0) << 16) | ((uint32_t)(pos * 3) << 8) | (255 - pos * 3); + } else { + pos -= 170; + return ((uint32_t)(pos * 3) << 16) | ((uint32_t)(255 - pos * 3) << 8) | (0); + } +} + +/* + * Returns a new, random wheel index with a minimum distance of 42 from pos. + */ +uint8_t Segment::get_random_wheel_index(uint8_t pos) { + uint8_t r = 0, x = 0, y = 0, d = 0; + + while(d < 42) { + r = random8(); + x = abs(pos - r); + y = 255 - x; + d = MIN(x, y); + } + return r; +} + +/* + * Gets a single color from the currently selected palette. + * @param i Palette Index (if mapping is true, the full palette will be _virtualSegmentLength long, if false, 255). Will wrap around automatically. + * @param mapping if true, LED position in segment is considered for color + * @param wrap FastLED palettes will usally wrap back to the start smoothly. Set false to get a hard edge + * @param mcol If the default palette 0 is selected, return the standard color 0, 1 or 2 instead. If >2, Party palette is used instead + * @param pbri Value to scale the brightness of the returned color by. Default is 255. (no scaling) + * @returns Single color from palette + */ +uint32_t Segment::color_from_palette(uint16_t i, bool mapping, bool wrap, uint8_t mcol, uint8_t pbri) +{ + // default palette or no RGB support on segment + if ((palette == 0 && mcol < NUM_COLORS) || !(_capabilities & 0x01)) { + uint32_t color = (transitional && _t) ? _t->_colorT[mcol] : colors[mcol]; + color = gamma32(color); + if (pbri == 255) return color; + return RGBW32(scale8_video(R(color),pbri), scale8_video(G(color),pbri), scale8_video(B(color),pbri), scale8_video(W(color),pbri)); + } + + uint8_t paletteIndex = i; + if (mapping && virtualLength() > 1) paletteIndex = (i*255)/(virtualLength() -1); + if (!wrap) paletteIndex = scale8(paletteIndex, 240); //cut off blend at palette "end" + CRGB fastled_col; + CRGBPalette16 curPal; + if (transitional && _t) curPal = _t->_palT; + else loadPalette(curPal, palette); + fastled_col = ColorFromPalette(curPal, paletteIndex, pbri, (strip.paletteBlend == 3)? NOBLEND:LINEARBLEND); // NOTE: paletteBlend should be global + + return RGBW32(fastled_col.r, fastled_col.g, fastled_col.b, 0); +} + + +/////////////////////////////////////////////////////////////////////////////// +// WS2812FX class implementation +/////////////////////////////////////////////////////////////////////////////// + //do not call this method from system context (network callback) void WS2812FX::finalizeInit(void) { //reset segment runtimes - for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++) { - _segment_runtimes[i].markForReset(); - _segment_runtimes[i].resetIfRequired(); + for (segment &seg : _segments) { + seg.markForReset(); + seg.resetIfRequired(); } + // for the lack of better place enumerate ledmaps here + // if we do it in json.cpp (serializeInfo()) we are getting flashes on LEDs + // unfortunately this means we do not get updates after uploads + enumerateLedmaps(); + _hasWhiteChannel = _isOffRefreshRequired = false; //if busses failed to load, add default (fresh install, FS issue, ...) @@ -93,7 +921,7 @@ void WS2812FX::finalizeInit(void) uint16_t start = prevLen; uint16_t count = defCounts[(i < defNumCounts) ? i : defNumCounts -1]; prevLen += count; - BusConfig defCfg = BusConfig(DEFAULT_LED_TYPE, defPin, start, count, DEFAULT_LED_COLOR_ORDER); + BusConfig defCfg = BusConfig(DEFAULT_LED_TYPE, defPin, start, count, DEFAULT_LED_COLOR_ORDER, false, 0, RGBW_MODE_MANUAL_ONLY); busses.add(defCfg); } } @@ -118,8 +946,23 @@ void WS2812FX::finalizeInit(void) #endif } - //segments are created in makeAutoSegments(); + //initialize leds array. TBD: realloc if nr of leds change + if (Segment::_globalLeds) { + purgeSegments(true); + free(Segment::_globalLeds); + Segment::_globalLeds = nullptr; + } + if (useLedsArray) { + #if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_PSRAM) + if (psramFound()) + Segment::_globalLeds = (CRGB*) ps_malloc(sizeof(CRGB) * _length); + else + #endif + Segment::_globalLeds = (CRGB*) malloc(sizeof(CRGB) * _length); + memset(Segment::_globalLeds, 0, sizeof(CRGB) * _length); + } + //segments are created in makeAutoSegments(); setBrightness(_brightness); } @@ -129,54 +972,44 @@ void WS2812FX::service() { if (nowUp - _lastShow < MIN_SHOW_DELAY) return; bool doShow = false; - for(uint8_t i=0; i < MAX_NUM_SEGMENTS; i++) - { - //if (realtimeMode && useMainSegmentOnly && i == getMainSegmentId()) continue; + _isServicing = true; + _segment_index = 0; + for (segment &seg : _segments) { + // reset the segment runtime data if needed + seg.resetIfRequired(); - _segment_index = i; - - // reset the segment runtime data if needed, called before isActive to ensure deleted - // segment's buffers are cleared - SEGENV.resetIfRequired(); - - if (!SEGMENT.isActive()) continue; + if (!seg.isActive()) continue; // last condition ensures all solid segments are updated at the same time - if(nowUp > SEGENV.next_time || _triggered || (doShow && SEGMENT.mode == 0)) + if(nowUp > seg.next_time || _triggered || (doShow && seg.mode == FX_MODE_STATIC)) { - if (SEGMENT.grouping == 0) SEGMENT.grouping = 1; //sanity check + if (seg.grouping == 0) seg.grouping = 1; //sanity check doShow = true; uint16_t delay = FRAMETIME; - if (!SEGMENT.getOption(SEG_OPTION_FREEZE)) { //only run effect function if not frozen - _virtualSegmentLength = SEGMENT.virtualLength(); - _bri_t = SEGMENT.opacity; _colors_t[0] = SEGMENT.colors[0]; _colors_t[1] = SEGMENT.colors[1]; _colors_t[2] = SEGMENT.colors[2]; - uint8_t _cct_t = SEGMENT.cct; - if (!IS_SEGMENT_ON) _bri_t = 0; - for (uint8_t t = 0; t < MAX_NUM_TRANSITIONS; t++) { - if ((transitions[t].segment & 0x3F) != i) continue; - uint8_t slot = transitions[t].segment >> 6; - if (slot == 0) _bri_t = transitions[t].currentBri(); - if (slot == 1) _cct_t = transitions[t].currentBri(false, 1); - _colors_t[slot] = transitions[t].currentColor(SEGMENT.colors[slot]); - } - if (!cctFromRgb || correctWB) busses.setSegmentCCT(_cct_t, correctWB); - for (uint8_t c = 0; c < NUM_COLORS; c++) { - _colors_t[c] = gamma32(_colors_t[c]); - } - handle_palette(); + if (!seg.freeze) { //only run effect function if not frozen + _virtualSegmentLength = seg.virtualLength(); + _colors_t[0] = seg.currentColor(0, seg.colors[0]); + _colors_t[1] = seg.currentColor(1, seg.colors[1]); + _colors_t[2] = seg.currentColor(2, seg.colors[2]); + seg.currentPalette(_currentPalette, seg.palette); - // if segment is not RGB capable, force None auto white mode - // If not RGB capable, also treat palette as if default (0), as palettes set white channel to 0 - _no_rgb = !(SEGMENT.getLightCapabilities() & 0x01); - if (_no_rgb) Bus::setAutoWhiteMode(RGBW_MODE_MANUAL_ONLY); - delay = (this->*_mode[SEGMENT.mode])(); //effect function - if (SEGMENT.mode != FX_MODE_HALLOWEEN_EYES) SEGENV.call++; - Bus::setAutoWhiteMode(strip.autoWhiteMode); + if (!cctFromRgb || correctWB) busses.setSegmentCCT(seg.currentBri(seg.cct, true), correctWB); + for (uint8_t c = 0; c < NUM_COLORS; c++) _colors_t[c] = gamma32(_colors_t[c]); + + // effect blending (execute previous effect) + // actual code may be a bit more involved as effects have runtime data including allocated memory + //if (seg.transitional && seg._modeP) (*_mode[seg._modeP])(progress()); + delay = (*_mode[seg.currentMode(seg.mode)])(); + if (seg.mode != FX_MODE_HALLOWEEN_EYES) seg.call++; + if (seg.transitional && delay > FRAMETIME) delay = FRAMETIME; // foce faster updates during transition + + seg.handleTransition(); } - SEGENV.next_time = nowUp + delay; + seg.next_time = nowUp + delay; } + _segment_index++; } _virtualSegmentLength = 0; busses.setSegmentCCT(-1); @@ -185,65 +1018,21 @@ void WS2812FX::service() { show(); } _triggered = false; + _isServicing = false; } -void IRAM_ATTR WS2812FX::setPixelColor(uint16_t i, byte r, byte g, byte b, byte w) +void IRAM_ATTR WS2812FX::setPixelColor(int i, uint32_t col) { - uint8_t segIdx; + if (i >= _length) return; + if (i < customMappingSize) i = customMappingTable[i]; + busses.setPixelColor(i, col); +} - if (SEGLEN) { // SEGLEN!=0 -> from segment/FX - //color_blend(getpixel, col, _bri_t); (pseudocode for future blending of segments) - if (_bri_t < 255) { - r = scale8(r, _bri_t); - g = scale8(g, _bri_t); - b = scale8(b, _bri_t); - w = scale8(w, _bri_t); - } - segIdx = _segment_index; - } else // from live/realtime - segIdx = _mainSegment; - - if (SEGLEN || (realtimeMode && useMainSegmentOnly)) { - uint32_t col = RGBW32(r, g, b, w); - uint16_t len = _segments[segIdx].length(); - - // get physical pixel address (taking into account start, grouping, spacing [and offset]) - i = i * _segments[segIdx].groupLength(); - if (_segments[segIdx].options & REVERSE) { // is segment reversed? - if (_segments[segIdx].options & MIRROR) { // is segment mirrored? - i = (len - 1) / 2 - i; //only need to index half the pixels - } else { - i = (len - 1) - i; - } - } - i += _segments[segIdx].start; - - // set all the pixels in the group - for (uint16_t j = 0; j < _segments[segIdx].grouping; j++) { - uint16_t indexSet = i + ((_segments[segIdx].options & REVERSE) ? -j : j); - if (indexSet >= _segments[segIdx].start && indexSet < _segments[segIdx].stop) { - - if (_segments[segIdx].options & MIRROR) { //set the corresponding mirrored pixel - uint16_t indexMir = _segments[segIdx].stop - indexSet + _segments[segIdx].start - 1; - indexMir += _segments[segIdx].offset; // offset/phase - - if (indexMir >= _segments[segIdx].stop) indexMir -= len; - if (indexMir < customMappingSize) indexMir = customMappingTable[indexMir]; - - busses.setPixelColor(indexMir, col); - } - indexSet += _segments[segIdx].offset; // offset/phase - - if (indexSet >= _segments[segIdx].stop) indexSet -= len; - if (indexSet < customMappingSize) indexSet = customMappingTable[indexSet]; - - busses.setPixelColor(indexSet, col); - } - } - } else { - if (i < customMappingSize) i = customMappingTable[i]; - busses.setPixelColor(i, RGBW32(r, g, b, w)); - } +uint32_t WS2812FX::getPixelColor(uint16_t i) +{ + if (i >= _length) return 0; + if (i < customMappingSize) i = customMappingTable[i]; + return busses.getPixelColor(i); } @@ -367,65 +1156,38 @@ uint16_t WS2812FX::getFps() { return _cumulativeFps +1; } -uint8_t WS2812FX::getTargetFps() { - return _targetFps; -} - void WS2812FX::setTargetFps(uint8_t fps) { - if (fps > 0 && fps <= 120) _targetFps = fps; - _frametime = 1000 / _targetFps; -} - -/** - * Forces the next frame to be computed on all active segments. - */ -void WS2812FX::trigger() { - _triggered = true; + if (fps > 0 && fps <= 120) _targetFps = fps; + _frametime = 1000 / _targetFps; } void WS2812FX::setMode(uint8_t segid, uint8_t m) { - if (segid >= MAX_NUM_SEGMENTS) return; + if (segid >= _segments.size()) return; - if (m >= MODE_COUNT) m = MODE_COUNT - 1; + if (m >= getModeCount()) m = getModeCount() - 1; - if (_segments[segid].mode != m) - { - _segment_runtimes[segid].markForReset(); + if (_segments[segid].mode != m) { + _segments[segid].startTransition(_transitionDur); // set effect transitions + //_segments[segid].markForReset(); _segments[segid].mode = m; } } -uint8_t WS2812FX::getModeCount() -{ - return MODE_COUNT; -} - -uint8_t WS2812FX::getPaletteCount() -{ - return 13 + GRADIENT_PALETTE_COUNT; -} - -void WS2812FX::setColor(uint8_t slot, uint8_t r, uint8_t g, uint8_t b, uint8_t w) { - setColor(slot, RGBW32(r, g, b, w)); -} - //applies to all active and selected segments void WS2812FX::setColor(uint8_t slot, uint32_t c) { if (slot >= NUM_COLORS) return; - for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++) - { - if (_segments[i].isActive() && _segments[i].isSelected()) { - _segments[i].setColor(slot, c, i); + for (segment &seg : _segments) { + if (seg.isActive() && seg.isSelected()) { + seg.setColor(slot, c); } } } void WS2812FX::setCCT(uint16_t k) { - for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++) - { - if (_segments[i].isActive() && _segments[i].isSelected()) { - _segments[i].setCCT(k, i); + for (segment &seg : _segments) { + if (seg.isActive() && seg.isSelected()) { + seg.setCCT(k); } } } @@ -435,60 +1197,40 @@ void WS2812FX::setBrightness(uint8_t b, bool direct) { if (_brightness == b) return; _brightness = b; if (_brightness == 0) { //unfreeze all segments on power off - for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++) - { - _segments[i].setOption(SEG_OPTION_FREEZE, false); + for (segment &seg : _segments) { + seg.freeze = false; } } if (direct) { // would be dangerous if applied immediately (could exceed ABL), but will not output until the next show() busses.setBrightness(b); } else { - unsigned long t = millis(); - if (_segment_runtimes[0].next_time > t + 22 && t - _lastShow > MIN_SHOW_DELAY) show(); //apply brightness change immediately if no refresh soon + unsigned long t = millis(); + if (_segments[0].next_time > t + 22 && t - _lastShow > MIN_SHOW_DELAY) show(); //apply brightness change immediately if no refresh soon } } -uint8_t WS2812FX::getBrightness(void) { - return _brightness; -} - -uint8_t WS2812FX::getMaxSegments(void) { - return MAX_NUM_SEGMENTS; -} - uint8_t WS2812FX::getFirstSelectedSegId(void) { - for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++) - { - if (_segments[i].isActive() && _segments[i].isSelected()) return i; + size_t i = 0; + for (segment &seg : _segments) { + if (seg.isActive() && seg.isSelected()) return i; + i++; } // if none selected, use the main segment return getMainSegmentId(); } void WS2812FX::setMainSegmentId(uint8_t n) { - if (n >= MAX_NUM_SEGMENTS) return; - //use supplied n if active, or first active - if (_segments[n].isActive()) { - _mainSegment = n; return; - } - for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++) - { - if (_segments[i].isActive()) { - _mainSegment = i; return; - } - } _mainSegment = 0; + if (n < _segments.size()) { + _mainSegment = n; + } return; } -uint8_t WS2812FX::getMainSegmentId(void) { - return _mainSegment; -} - uint8_t WS2812FX::getLastActiveSegmentId(void) { - for (uint8_t i = MAX_NUM_SEGMENTS -1; i > 0; i--) { + for (size_t i = _segments.size() -1; i > 0; i--) { if (_segments[i].isActive()) return i; } return 0; @@ -496,63 +1238,15 @@ uint8_t WS2812FX::getLastActiveSegmentId(void) { uint8_t WS2812FX::getActiveSegmentsNum(void) { uint8_t c = 0; - for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++) - { + for (size_t i = 0; i < _segments.size(); i++) { if (_segments[i].isActive()) c++; } return c; } -uint32_t WS2812FX::getPixelColor(uint16_t i) -{ - // get physical pixel - i = i * SEGMENT.groupLength();; - if (IS_REVERSE) { - if (IS_MIRROR) i = (SEGMENT.length() - 1) / 2 - i; //only need to index half the pixels - else i = (SEGMENT.length() - 1) - i; - } - i += SEGMENT.start; - - if (SEGLEN) { - /* offset/phase */ - i += SEGMENT.offset; - if (i >= SEGMENT.stop) i -= SEGMENT.length(); - } - - if (i < customMappingSize) i = customMappingTable[i]; - if (i >= _length) return 0; - - return busses.getPixelColor(i); -} - -WS2812FX::Segment& WS2812FX::getSegment(uint8_t id) { - if (id >= MAX_NUM_SEGMENTS) return _segments[getMainSegmentId()]; - return _segments[id]; -} - -WS2812FX::Segment& WS2812FX::getFirstSelectedSeg(void) { - return _segments[getFirstSelectedSegId()]; -} - -WS2812FX::Segment& WS2812FX::getMainSegment(void) { - return _segments[getMainSegmentId()]; -} - -WS2812FX::Segment* WS2812FX::getSegments(void) { - return _segments; -} - -uint32_t WS2812FX::getLastShow(void) { - return _lastShow; -} - -uint16_t WS2812FX::getLengthTotal(void) { - return _length; -} - uint16_t WS2812FX::getLengthPhysical(void) { uint16_t len = 0; - for (uint8_t b = 0; b < busses.getNumBusses(); b++) { + for (size_t b = 0; b < busses.getNumBusses(); b++) { Bus *bus = busses.getBus(b); if (bus->getType() >= TYPE_NET_DDP_RGB) continue; //exclude non-physical network busses len += bus->getLength(); @@ -560,72 +1254,11 @@ uint16_t WS2812FX::getLengthPhysical(void) { return len; } -uint8_t WS2812FX::Segment::differs(Segment& b) { - uint8_t d = 0; - if (start != b.start) d |= SEG_DIFFERS_BOUNDS; - if (stop != b.stop) d |= SEG_DIFFERS_BOUNDS; - if (offset != b.offset) d |= SEG_DIFFERS_GSO; - if (grouping != b.grouping) d |= SEG_DIFFERS_GSO; - if (spacing != b.spacing) d |= SEG_DIFFERS_GSO; - if (opacity != b.opacity) d |= SEG_DIFFERS_BRI; - if (mode != b.mode) d |= SEG_DIFFERS_FX; - if (speed != b.speed) d |= SEG_DIFFERS_FX; - if (intensity != b.intensity) d |= SEG_DIFFERS_FX; - if (palette != b.palette) d |= SEG_DIFFERS_FX; - - if ((options & 0b00101110) != (b.options & 0b00101110)) d |= SEG_DIFFERS_OPT; - if ((options & 0x01) != (b.options & 0x01)) d |= SEG_DIFFERS_SEL; - - for (uint8_t i = 0; i < NUM_COLORS; i++) - { - if (colors[i] != b.colors[i]) d |= SEG_DIFFERS_COL; - } - - return d; -} - -void WS2812FX::Segment::refreshLightCapabilities() { - if (!isActive()) { - _capabilities = 0; return; - } - uint8_t capabilities = 0; - uint8_t awm = instance->autoWhiteMode; - bool whiteSlider = (awm == RGBW_MODE_DUAL || awm == RGBW_MODE_MANUAL_ONLY); - bool segHasValidBus = false; - - for (uint8_t b = 0; b < busses.getNumBusses(); b++) { - Bus *bus = busses.getBus(b); - if (bus == nullptr || bus->getLength()==0) break; - if (bus->getStart() >= stop) continue; - if (bus->getStart() + bus->getLength() <= start) continue; - - segHasValidBus = true; - uint8_t type = bus->getType(); - if (type != TYPE_ANALOG_1CH && (cctFromRgb || type != TYPE_ANALOG_2CH)) - { - capabilities |= 0x01; // segment supports RGB (full color) - } - if (bus->isRgbw() && whiteSlider) capabilities |= 0x02; // segment supports white channel - if (!cctFromRgb) { - switch (type) { - case TYPE_ANALOG_5CH: - case TYPE_ANALOG_2CH: - capabilities |= 0x04; //segment supports white CCT - } - } - if (correctWB && type != TYPE_ANALOG_1CH) capabilities |= 0x04; //white balance correction (uses CCT slider) - } - // if seg has any bus, but no bus has RGB, it by definition supports white (at least for now) - // In case of no RGB, disregard auto white mode and always show a white slider - if (segHasValidBus && !(capabilities & 0x01)) capabilities |= 0x02; // segment supports white channel - _capabilities = capabilities; -} - //used for JSON API info.leds.rgbw. Little practical use, deprecate with info.leds.rgbw. //returns if there is an RGBW bus (supports RGB and White, not only white) //not influenced by auto-white mode, also true if white slider does not affect output white channel bool WS2812FX::hasRGBWBus(void) { - for (uint8_t b = 0; b < busses.getNumBusses(); b++) { + for (size_t b = 0; b < busses.getNumBusses(); b++) { Bus *bus = busses.getBus(b); if (bus == nullptr || bus->getLength()==0) break; switch (bus->getType()) { @@ -635,12 +1268,12 @@ bool WS2812FX::hasRGBWBus(void) { return true; } } - return false; + return false; } bool WS2812FX::hasCCTBus(void) { - if (cctFromRgb && !correctWB) return false; - for (uint8_t b = 0; b < busses.getNumBusses(); b++) { + if (cctFromRgb && !correctWB) return false; + for (size_t b = 0; b < busses.getNumBusses(); b++) { Bus *bus = busses.getBus(b); if (bus == nullptr || bus->getLength()==0) break; switch (bus->getType()) { @@ -649,83 +1282,112 @@ bool WS2812FX::hasCCTBus(void) { return true; } } - return false; + return false; } -void WS2812FX::setSegment(uint8_t n, uint16_t i1, uint16_t i2, uint8_t grouping, uint8_t spacing, uint16_t offset) { - if (n >= MAX_NUM_SEGMENTS) return; +void WS2812FX::purgeSegments(bool force) { + // remove all inactive segments (from the back) + int deleted = 0; + if (_segments.size() <= 1) return; + for (size_t i = _segments.size()-1; i > 0; i--) + if (_segments[i].stop == 0 || force) { + DEBUG_PRINT(F("Purging segment segment: ")); DEBUG_PRINTLN(i); + deleted++; + _segments.erase(_segments.begin() + i); + } + if (deleted) { + _segments.shrink_to_fit(); + if (_mainSegment >= _segments.size()) setMainSegmentId(0); + } +} + +Segment& WS2812FX::getSegment(uint8_t id) { + return _segments[id >= _segments.size() ? getMainSegmentId() : id]; // vectors +} + +void WS2812FX::setSegment(uint8_t n, uint16_t i1, uint16_t i2, uint8_t grouping, uint8_t spacing, uint16_t offset, uint16_t startY, uint16_t stopY) { + if (n >= _segments.size()) return; Segment& seg = _segments[n]; //return if neither bounds nor grouping have changed bool boundsUnchanged = (seg.start == i1 && seg.stop == i2); + if (isMatrix) { + boundsUnchanged &= (seg.startY == startY && seg.stopY == stopY); + } if (boundsUnchanged - && (!grouping || (seg.grouping == grouping && seg.spacing == spacing)) - && (offset == UINT16_MAX || offset == seg.offset)) return; + && (!grouping || (seg.grouping == grouping && seg.spacing == spacing)) + && (offset == UINT16_MAX || offset == seg.offset)) return; - if (seg.stop) setRange(seg.start, seg.stop -1, 0); //turn old segment range off + //if (seg.stop) setRange(seg.start, seg.stop -1, BLACK); //turn old segment range off + if (seg.stop) seg.fill(BLACK); //turn old segment range off if (i2 <= i1) //disable segment { + // disabled segments should get removed using purgeSegments() + DEBUG_PRINT(F("-- Segment ")); DEBUG_PRINT(n); DEBUG_PRINTLN(F(" marked inactive.")); seg.stop = 0; - if (seg.name) { - delete[] seg.name; - seg.name = nullptr; - } + //if (seg.name) { + // delete[] seg.name; + // seg.name = nullptr; + //} // if main segment is deleted, set first active as main segment if (n == _mainSegment) setMainSegmentId(0); + seg.markForReset(); return; } - if (i1 < _length) seg.start = i1; - seg.stop = i2; - if (i2 > _length) seg.stop = _length; + if (isMatrix) { + #ifndef WLED_DISABLE_2D + if (i1 < matrixWidth) seg.start = i1; + seg.stop = i2 > matrixWidth ? matrixWidth : i2; + if (startY < matrixHeight) seg.startY = startY; + seg.stopY = stopY > matrixHeight ? matrixHeight : MAX(1,stopY); + #endif + } else { + if (i1 < _length) seg.start = i1; + seg.stop = i2 > _length ? _length : i2; + seg.startY = 0; + seg.stopY = 1; + } if (grouping) { seg.grouping = grouping; seg.spacing = spacing; } - if (offset < UINT16_MAX) seg.offset = offset; - _segment_runtimes[n].markForReset(); + if (offset < UINT16_MAX) seg.offset = offset; + seg.markForReset(); if (!boundsUnchanged) seg.refreshLightCapabilities(); } void WS2812FX::restartRuntime() { - for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++) { - _segment_runtimes[i].markForReset(); - } + for (segment &seg : _segments) seg.markForReset(); } void WS2812FX::resetSegments() { - for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++) if (_segments[i].name) delete[] _segments[i].name; + _segments.clear(); // destructs all Segment as part of clearing + #ifndef WLED_DISABLE_2D + segment seg = isMatrix ? Segment(0, matrixWidth, 0, matrixHeight) : Segment(0, _length); + #else + segment seg = Segment(0, _length); + #endif + _segments.push_back(seg); _mainSegment = 0; - memset(_segments, 0, sizeof(_segments)); - //memset(_segment_runtimes, 0, sizeof(_segment_runtimes)); - _segment_index = 0; - _segments[0].mode = DEFAULT_MODE; - _segments[0].colors[0] = DEFAULT_COLOR; - _segments[0].start = 0; - _segments[0].speed = DEFAULT_SPEED; - _segments[0].intensity = DEFAULT_INTENSITY; - _segments[0].stop = _length; - _segments[0].grouping = 1; - _segments[0].setOption(SEG_OPTION_SELECTED, 1); - _segments[0].setOption(SEG_OPTION_ON, 1); - _segments[0].opacity = 255; - _segments[0].cct = 127; - - for (uint16_t i = 1; i < MAX_NUM_SEGMENTS; i++) - { - _segments[i].colors[0] = color_wheel(i*51); - _segments[i].grouping = 1; - _segments[i].setOption(SEG_OPTION_ON, 1); - _segments[i].opacity = 255; - _segments[i].cct = 127; - _segments[i].speed = DEFAULT_SPEED; - _segments[i].intensity = DEFAULT_INTENSITY; - _segment_runtimes[i].markForReset(); - } - _segment_runtimes[0].markForReset(); } void WS2812FX::makeAutoSegments(bool forceReset) { - if (autoSegments) { //make one segment per bus + if (isMatrix) { + #ifndef WLED_DISABLE_2D + // only create 1 2D segment + if (forceReset || getSegmentsNum() == 0) resetSegments(); // initialises 1 segment + else if (getActiveSegmentsNum() == 1) { + size_t i = getLastActiveSegmentId(); + _segments[i].start = 0; + _segments[i].stop = matrixWidth; + _segments[i].startY = 0; + _segments[i].stopY = matrixHeight; + _segments[i].grouping = 1; + _segments[i].spacing = 0; + _mainSegment = i; + } + #endif + } else if (autoSegments) { //make one segment per bus uint16_t segStarts[MAX_NUM_SEGMENTS] = {0}; uint16_t segStops [MAX_NUM_SEGMENTS] = {0}; uint8_t s = 0; @@ -733,10 +1395,10 @@ void WS2812FX::makeAutoSegments(bool forceReset) { Bus* b = busses.getBus(i); segStarts[s] = b->getStart(); - segStops[s] = segStarts[s] + b->getLength(); + segStops[s] = segStarts[s] + b->getLength(); //check for overlap with previous segments - for (uint8_t j = 0; j < s; j++) { + for (size_t j = 0; j < s; j++) { if (segStops[j] > segStarts[s] && segStarts[j] < segStops[s]) { //segments overlap, merge segStarts[j] = min(segStarts[s],segStarts[j]); @@ -746,21 +1408,21 @@ void WS2812FX::makeAutoSegments(bool forceReset) { } s++; } - for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++) { - setSegment(i, segStarts[i], segStops[i]); + _segments.clear(); + for (size_t i = 0; i < s; i++) { + Segment seg = Segment(segStarts[i], segStops[i]); + seg.selected = true; + _segments.push_back(seg); } + _mainSegment = 0; } else { + if (forceReset || getSegmentsNum() == 0) resetSegments(); //expand the main seg to the entire length, but only if there are no other segments, or reset is forced - uint8_t mainSeg = getMainSegmentId(); - - if (forceReset) { - for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++) { - setSegment(i, 0, 0); - } - } - - if (getActiveSegmentsNum() < 2) { - setSegment(mainSeg, 0, _length); + else if (getActiveSegmentsNum() == 1) { + size_t i = getLastActiveSegmentId(); + _segments[i].start = 0; + _segments[i].stop = _length; + _mainSegment = 0; } } @@ -769,26 +1431,23 @@ void WS2812FX::makeAutoSegments(bool forceReset) { void WS2812FX::fixInvalidSegments() { //make sure no segment is longer than total (sanity check) - for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++) - { - if (_segments[i].start >= _length) setSegment(i, 0, 0); - if (_segments[i].stop > _length) setSegment(i, _segments[i].start, _length); + for (size_t i = getSegmentsNum()-1; i > 0; i--) { + if (_segments[i].start >= _length) { _segments.erase(_segments.begin()+i); continue; } + if (_segments[i].stop > _length) _segments[i].stop = _length; // this is always called as the last step after finalizeInit(), update covered bus types - getSegment(i).refreshLightCapabilities(); + _segments[i].refreshLightCapabilities(); } } //true if all segments align with a bus, or if a segment covers the total length bool WS2812FX::checkSegmentAlignment() { - for (uint8_t i = 0; i < MAX_NUM_SEGMENTS; i++) - { - if (_segments[i].start >= _segments[i].stop) continue; //inactive segment - bool aligned = false; + bool aligned = false; + for (segment &seg : _segments) { for (uint8_t b = 0; bgetStart() && _segments[i].stop == bus->getStart() + bus->getLength()) aligned = true; + if (seg.start == bus->getStart() && seg.stop == bus->getStart() + bus->getLength()) aligned = true; } - if (_segments[i].start == 0 && _segments[i].stop == _length) aligned = true; + if (seg.start == 0 && seg.stop == _length) aligned = true; if (!aligned) return false; } return true; @@ -800,9 +1459,9 @@ bool WS2812FX::checkSegmentAlignment() { uint8_t WS2812FX::setPixelSegment(uint8_t n) { uint8_t prevSegId = _segment_index; - if (n < MAX_NUM_SEGMENTS) { + if (n < _segments.size()) { _segment_index = n; - _virtualSegmentLength = SEGMENT.virtualLength(); + _virtualSegmentLength = _segments[_segment_index].virtualLength(); } return prevSegId; } @@ -818,350 +1477,62 @@ void WS2812FX::setRange(uint16_t i, uint16_t i2, uint32_t col) } } -void WS2812FX::setShowCallback(show_callback cb) -{ - _callback = cb; -} - -void WS2812FX::setTransition(uint16_t t) -{ - _transitionDur = t; -} - void WS2812FX::setTransitionMode(bool t) { - unsigned long waitMax = millis() + 20; //refresh after 20 ms if transition enabled - for (uint16_t i = 0; i < MAX_NUM_SEGMENTS; i++) - { - _segments[i].setOption(SEG_OPTION_TRANSITIONAL, t); - - if (t && _segments[i].mode == FX_MODE_STATIC && _segment_runtimes[i].next_time > waitMax) - _segment_runtimes[i].next_time = waitMax; - } + for (segment &seg : _segments) if (!seg.transitional) seg.startTransition(t ? _transitionDur : 0); } -/* - * color blend function - */ -uint32_t IRAM_ATTR WS2812FX::color_blend(uint32_t color1, uint32_t color2, uint16_t blend, bool b16) { - if(blend == 0) return color1; - uint16_t blendmax = b16 ? 0xFFFF : 0xFF; - if(blend == blendmax) return color2; - uint8_t shift = b16 ? 16 : 8; - - uint32_t w1 = W(color1); - uint32_t r1 = R(color1); - uint32_t g1 = G(color1); - uint32_t b1 = B(color1); - - uint32_t w2 = W(color2); - uint32_t r2 = R(color2); - uint32_t g2 = G(color2); - uint32_t b2 = B(color2); - - uint32_t w3 = ((w2 * blend) + (w1 * (blendmax - blend))) >> shift; - uint32_t r3 = ((r2 * blend) + (r1 * (blendmax - blend))) >> shift; - uint32_t g3 = ((g2 * blend) + (g1 * (blendmax - blend))) >> shift; - uint32_t b3 = ((b2 * blend) + (b1 * (blendmax - blend))) >> shift; - - return RGBW32(r3, g3, b3, w3); -} - -/* - * Fills segment with color - */ -void WS2812FX::fill(uint32_t c) { - for(uint16_t i = 0; i < SEGLEN; i++) { - setPixelColor(i, c); - } -} - -/* - * Blends the specified color with the existing pixel color. - */ -void WS2812FX::blendPixelColor(uint16_t n, uint32_t color, uint8_t blend) +#ifdef WLED_DEBUG +void WS2812FX::printSize() { - setPixelColor(n, color_blend(getPixelColor(n), color, blend)); + size_t size = 0; + for (const Segment &seg : _segments) size += seg.getSize(); + DEBUG_PRINTF("Segments: %d -> %uB\n", _segments.size(), size); + DEBUG_PRINTF("Modes: %d*%d=%uB\n", sizeof(mode_ptr), _mode.size(), (_mode.capacity()*sizeof(mode_ptr))); + DEBUG_PRINTF("Data: %d*%d=%uB\n", sizeof(const char *), _modeData.size(), (_modeData.capacity()*sizeof(const char *))); + DEBUG_PRINTF("Map: %d*%d=%uB\n", sizeof(uint16_t), (int)customMappingSize, customMappingSize*sizeof(uint16_t)); + if (useLedsArray) DEBUG_PRINTF("Buffer: %d*%d=%uB\n", sizeof(CRGB), (int)_length, _length*sizeof(CRGB)); } +#endif -/* - * fade out function, higher rate = quicker fade - */ -void WS2812FX::fade_out(uint8_t rate) { - rate = (255-rate) >> 1; - float mappedRate = float(rate) +1.1; - - uint32_t color = SEGCOLOR(1); // target color - int w2 = W(color); - int r2 = R(color); - int g2 = G(color); - int b2 = B(color); - - for(uint16_t i = 0; i < SEGLEN; i++) { - color = getPixelColor(i); - 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; - - setPixelColor(i, r1 + rdelta, g1 + gdelta, b1 + bdelta, w1 + wdelta); - } -} - -/* - * blurs segment content, source: FastLED colorutils.cpp - */ -void WS2812FX::blur(uint8_t blur_amount) +void WS2812FX::loadCustomPalettes() { - uint8_t keep = 255 - blur_amount; - uint8_t seep = blur_amount >> 1; - CRGB carryover = CRGB::Black; - for(uint16_t i = 0; i < SEGLEN; i++) - { - CRGB cur = col_to_crgb(getPixelColor(i)); - CRGB part = cur; - part.nscale8(seep); - cur.nscale8(keep); - cur += carryover; - if(i > 0) { - uint32_t c = getPixelColor(i-1); - uint8_t r = R(c); - uint8_t g = G(c); - uint8_t b = B(c); - setPixelColor(i-1, qadd8(r, part.red), qadd8(g, part.green), qadd8(b, part.blue)); - } - setPixelColor(i,cur.red, cur.green, cur.blue); - carryover = part; - } -} - -uint16_t IRAM_ATTR WS2812FX::triwave16(uint16_t in) -{ - if (in < 0x8000) return in *2; - return 0xFFFF - (in - 0x8000)*2; -} - -/* - * Generates a tristate square wave w/ attac & decay - * @param x input value 0-255 - * @param pulsewidth 0-127 - * @param attdec attac & decay, max. pulsewidth / 2 - * @returns signed waveform value - */ -int8_t WS2812FX::tristate_square8(uint8_t x, uint8_t pulsewidth, uint8_t attdec) { - int8_t a = 127; - if (x > 127) { - a = -127; - x -= 127; - } - - if (x < attdec) { //inc to max - return (int16_t) x * a / attdec; - } - else if (x < pulsewidth - attdec) { //max - return a; - } - else if (x < pulsewidth) { //dec to 0 - return (int16_t) (pulsewidth - x) * a / attdec; - } - return 0; -} - -/* - * Put a value 0 to 255 in to get a color value. - * The colours are a transition r -> g -> b -> back to r - * Inspired by the Adafruit examples. - */ -uint32_t WS2812FX::color_wheel(uint8_t pos) { - if (SEGMENT.palette) return color_from_palette(pos, false, true, 0); - pos = 255 - pos; - if(pos < 85) { - return ((uint32_t)(255 - pos * 3) << 16) | ((uint32_t)(0) << 8) | (pos * 3); - } else if(pos < 170) { - pos -= 85; - return ((uint32_t)(0) << 16) | ((uint32_t)(pos * 3) << 8) | (255 - pos * 3); - } else { - pos -= 170; - return ((uint32_t)(pos * 3) << 16) | ((uint32_t)(255 - pos * 3) << 8) | (0); - } -} - -/* - * Returns a new, random wheel index with a minimum distance of 42 from pos. - */ -uint8_t WS2812FX::get_random_wheel_index(uint8_t pos) { - uint8_t r = 0, x = 0, y = 0, d = 0; - - while(d < 42) { - r = random8(); - x = abs(pos - r); - y = 255 - x; - d = MIN(x, y); - } - return r; -} - - -uint32_t IRAM_ATTR WS2812FX::crgb_to_col(CRGB fastled) -{ - return RGBW32(fastled.red, fastled.green, fastled.blue, 0); -} - - -CRGB IRAM_ATTR WS2812FX::col_to_crgb(uint32_t color) -{ - CRGB fastled_col; - fastled_col.red = R(color); - fastled_col.green = G(color); - fastled_col.blue = B(color); - return fastled_col; -} - - -void WS2812FX::load_gradient_palette(uint8_t index) -{ - byte i = constrain(index, 0, GRADIENT_PALETTE_COUNT -1); byte tcp[72]; //support gradient palettes with up to 18 entries - memcpy_P(tcp, (byte*)pgm_read_dword(&(gGradientPalettes[i])), 72); - targetPalette.loadDynamicGradientPalette(tcp); -} + CRGBPalette16 targetPalette; + for (int index = 0; index<10; index++) { + char fileName[32]; + sprintf_P(fileName, PSTR("/palette%d.json"), index); + StaticJsonDocument<1536> pDoc; // barely enough to fit 72 numbers + if (WLED_FS.exists(fileName)) { + DEBUG_PRINT(F("Reading palette from ")); + DEBUG_PRINTLN(fileName); -/* - * FastLED palette modes helper function. Limitation: Due to memory reasons, multiple active segments with FastLED will disable the Palette transitions - */ -void WS2812FX::handle_palette(void) -{ - bool singleSegmentMode = (_segment_index == _segment_index_palette_last); - _segment_index_palette_last = _segment_index; - - byte paletteIndex = SEGMENT.palette; - if (paletteIndex == 0) //default palette. Differs depending on effect - { - switch (SEGMENT.mode) - { - case FX_MODE_FIRE_2012 : paletteIndex = 35; break; //heat palette - case FX_MODE_COLORWAVES : paletteIndex = 26; break; //landscape 33 - case FX_MODE_FILLNOISE8 : paletteIndex = 9; break; //ocean colors - case FX_MODE_NOISE16_1 : paletteIndex = 20; break; //Drywet - case FX_MODE_NOISE16_2 : paletteIndex = 43; break; //Blue cyan yellow - case FX_MODE_NOISE16_3 : paletteIndex = 35; break; //heat palette - case FX_MODE_NOISE16_4 : paletteIndex = 26; break; //landscape 33 - case FX_MODE_GLITTER : paletteIndex = 11; break; //rainbow colors - case FX_MODE_SUNRISE : paletteIndex = 35; break; //heat palette - case FX_MODE_FLOW : paletteIndex = 6; break; //party + if (readObjectFromFile(fileName, nullptr, &pDoc)) { + JsonArray pal = pDoc[F("palette")]; + if (!pal.isNull() && pal.size()>7) { // not an empty palette (at least 2 entries) + size_t palSize = MIN(pal.size(), 72); + palSize -= palSize % 4; // make sure size is multiple of 4 + for (size_t i=0; i()<256; i+=4) { + tcp[ i ] = (uint8_t) pal[ i ].as(); // index + tcp[i+1] = (uint8_t) pal[i+1].as(); // R + tcp[i+2] = (uint8_t) pal[i+2].as(); // G + tcp[i+3] = (uint8_t) pal[i+3].as(); // B + DEBUG_PRINTF("%d(%d) : %d %d %d\n", i, int(tcp[i]), int(tcp[i+1]), int(tcp[i+2]), int(tcp[i+3])); + } + customPalettes.push_back(targetPalette.loadDynamicGradientPalette(tcp)); + } + } + } else { + break; } } - if (SEGMENT.mode >= FX_MODE_METEOR && paletteIndex == 0) paletteIndex = 4; - - switch (paletteIndex) - { - case 0: //default palette. Exceptions for specific effects above - targetPalette = PartyColors_p; break; - case 1: {//periodically replace palette with a random one. Doesn't work with multiple FastLED segments - if (!singleSegmentMode) - { - targetPalette = PartyColors_p; break; //fallback - } - if (millis() - _lastPaletteChange > 1000 + ((uint32_t)(255-SEGMENT.intensity))*100) - { - targetPalette = CRGBPalette16( - CHSV(random8(), 255, random8(128, 255)), - CHSV(random8(), 255, random8(128, 255)), - CHSV(random8(), 192, random8(128, 255)), - CHSV(random8(), 255, random8(128, 255))); - _lastPaletteChange = millis(); - } break;} - case 2: {//primary color only - CRGB prim = col_to_crgb(SEGCOLOR(0)); - targetPalette = CRGBPalette16(prim); break;} - case 3: {//primary + secondary - CRGB prim = col_to_crgb(SEGCOLOR(0)); - CRGB sec = col_to_crgb(SEGCOLOR(1)); - targetPalette = CRGBPalette16(prim,prim,sec,sec); break;} - case 4: {//primary + secondary + tertiary - CRGB prim = col_to_crgb(SEGCOLOR(0)); - CRGB sec = col_to_crgb(SEGCOLOR(1)); - CRGB ter = col_to_crgb(SEGCOLOR(2)); - targetPalette = CRGBPalette16(ter,sec,prim); break;} - case 5: {//primary + secondary (+tert if not off), more distinct - CRGB prim = col_to_crgb(SEGCOLOR(0)); - CRGB sec = col_to_crgb(SEGCOLOR(1)); - if (SEGCOLOR(2)) { - CRGB ter = col_to_crgb(SEGCOLOR(2)); - targetPalette = CRGBPalette16(prim,prim,prim,prim,prim,sec,sec,sec,sec,sec,ter,ter,ter,ter,ter,prim); - } else { - targetPalette = CRGBPalette16(prim,prim,prim,prim,prim,prim,prim,prim,sec,sec,sec,sec,sec,sec,sec,sec); - } - break;} - case 6: //Party colors - targetPalette = PartyColors_p; break; - case 7: //Cloud colors - targetPalette = CloudColors_p; break; - case 8: //Lava colors - targetPalette = LavaColors_p; break; - case 9: //Ocean colors - targetPalette = OceanColors_p; break; - case 10: //Forest colors - targetPalette = ForestColors_p; break; - case 11: //Rainbow colors - targetPalette = RainbowColors_p; break; - case 12: //Rainbow stripe colors - targetPalette = RainbowStripeColors_p; break; - default: //progmem palettes - load_gradient_palette(paletteIndex -13); - } - - if (singleSegmentMode && paletteFade && SEGENV.call > 0) //only blend if just one segment uses FastLED mode - { - nblendPaletteTowardPalette(currentPalette, targetPalette, 48); - } else - { - currentPalette = targetPalette; - } } - -/* - * Gets a single color from the currently selected palette. - * @param i Palette Index (if mapping is true, the full palette will be SEGLEN long, if false, 255). Will wrap around automatically. - * @param mapping if true, LED position in segment is considered for color - * @param wrap FastLED palettes will usally wrap back to the start smoothly. Set false to get a hard edge - * @param mcol If the default palette 0 is selected, return the standard color 0, 1 or 2 instead. If >2, Party palette is used instead - * @param pbri Value to scale the brightness of the returned color by. Default is 255. (no scaling) - * @returns Single color from palette - */ -uint32_t IRAM_ATTR WS2812FX::color_from_palette(uint16_t i, bool mapping, bool wrap, uint8_t mcol, uint8_t pbri) -{ - if ((SEGMENT.palette == 0 && mcol < 3) || _no_rgb) { - uint32_t color = SEGCOLOR(mcol); - if (pbri == 255) return color; - return RGBW32(scale8_video(R(color),pbri), scale8_video(G(color),pbri), scale8_video(B(color),pbri), scale8_video(W(color),pbri)); - } - - uint8_t paletteIndex = i; - if (mapping && SEGLEN > 1) paletteIndex = (i*255)/(SEGLEN -1); - if (!wrap) paletteIndex = scale8(paletteIndex, 240); //cut off blend at palette "end" - CRGB fastled_col; - fastled_col = ColorFromPalette(currentPalette, paletteIndex, pbri, (paletteBlend == 3)? NOBLEND:LINEARBLEND); - - return crgb_to_col(fastled_col); -} - - //load custom mapping table from JSON file (called from finalizeInit() or deserializeState()) void WS2812FX::deserializeMap(uint8_t n) { + if (isMatrix) return; // 2D support creates its own ledmap + char fileName[32]; strcpy_P(fileName, PSTR("/ledmap")); if (n) sprintf(fileName +7, "%d", n); @@ -1178,11 +1549,7 @@ void WS2812FX::deserializeMap(uint8_t n) { return; } - #ifdef WLED_USE_DYNAMIC_JSON - DynamicJsonDocument doc(JSON_BUFFER_SIZE); - #else if (!requestJSONBufferLock(7)) return; - #endif DEBUG_PRINT(F("Reading LED map from ")); DEBUG_PRINTLN(fileName); @@ -1211,58 +1578,22 @@ void WS2812FX::deserializeMap(uint8_t n) { releaseJSONBufferLock(); } -//gamma 2.8 lookup table used for color correction -byte gammaT[] = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, - 2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5, - 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, - 10, 10, 11, 11, 11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16, - 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 24, 24, 25, - 25, 26, 27, 27, 28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 35, 36, - 37, 38, 39, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 50, - 51, 52, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 66, 67, 68, - 69, 70, 72, 73, 74, 75, 77, 78, 79, 81, 82, 83, 85, 86, 87, 89, - 90, 92, 93, 95, 96, 98, 99,101,102,104,105,107,109,110,112,114, - 115,117,119,120,122,124,126,127,129,131,133,135,137,138,140,142, - 144,146,148,150,152,154,156,158,160,162,164,167,169,171,173,175, - 177,180,182,184,186,189,191,193,196,198,200,203,205,208,210,213, - 215,218,220,223,225,228,231,233,236,239,241,244,247,249,252,255 }; - -uint8_t WS2812FX::gamma8_cal(uint8_t b, float gamma) { - return (int)(pow((float)b / 255.0, gamma) * 255 + 0.5); -} - -void WS2812FX::calcGammaTable(float gamma) -{ - for (uint16_t i = 0; i < 256; i++) { - gammaT[i] = gamma8_cal(i, gamma); - } -} - -uint8_t WS2812FX::gamma8(uint8_t b) -{ - return gammaT[b]; -} - -uint32_t WS2812FX::gamma32(uint32_t color) -{ - if (!gammaCorrectCol) return color; - uint8_t w = W(color); - uint8_t r = R(color); - uint8_t g = G(color); - uint8_t b = B(color); - w = gammaT[w]; - r = gammaT[r]; - g = gammaT[g]; - b = gammaT[b]; - return RGBW32(r, g, b, w); -} WS2812FX* WS2812FX::instance = nullptr; //Bus static member definition, would belong in bus_manager.cpp int16_t Bus::_cct = -1; uint8_t Bus::_cctBlend = 0; -uint8_t Bus::_autoWhiteMode = RGBW_MODE_DUAL; +uint8_t Bus::_gAWM = 255; + +const char JSON_mode_names[] PROGMEM = R"=====(["Mode names have moved"])====="; +const char JSON_palette_names[] PROGMEM = R"=====([ +"Default","* Random Cycle","* Color 1","* Colors 1&2","* Color Gradient","* Colors Only","Party","Cloud","Lava","Ocean", +"Forest","Rainbow","Rainbow Bands","Sunset","Rivendell","Breeze","Red & Blue","Yellowout","Analogous","Splash", +"Pastel","Sunset 2","Beach","Vintage","Departure","Landscape","Beech","Sherbet","Hult","Hult 64", +"Drywet","Jul","Grintage","Rewhi","Tertiary","Fire","Icefire","Cyane","Light Pink","Autumn", +"Magenta","Magred","Yelmag","Yelblu","Orange & Teal","Tiamat","April Night","Orangery","C9","Sakura", +"Aurora","Atlantica","C9 2","C9 New","Temperature","Aurora 2","Retro Clown","Candy","Toxy Reaf","Fairy Reaf", +"Semi Blue","Pink Candy","Red Reaf","Aqua Flash","Yelblu Hot","Lite Light","Red Flash","Blink Red","Red Shift","Red Tide", +"Candy2" +])====="; diff --git a/wled00/NodeStruct.h b/wled00/NodeStruct.h index a0fd2f634..bc95d1e87 100644 --- a/wled00/NodeStruct.h +++ b/wled00/NodeStruct.h @@ -19,7 +19,6 @@ struct NodeStruct { String nodeName; IPAddress ip; - uint8_t unit; uint8_t age; uint8_t nodeType; uint32_t build; diff --git a/wled00/alexa.cpp b/wled00/alexa.cpp index e41b7e3fb..dacb46d5a 100644 --- a/wled00/alexa.cpp +++ b/wled00/alexa.cpp @@ -92,7 +92,7 @@ void onAlexaChange(EspalexaDevice* dev) } else { colorKtoRGB(k, rgbw); } - strip.setColor(0, rgbw[0], rgbw[1], rgbw[2], rgbw[3]); + strip.setColor(0, RGBW32(rgbw[0], rgbw[1], rgbw[2], rgbw[3])); } else { uint32_t color = espalexaDevice->getRGB(); strip.setColor(0, color); diff --git a/wled00/blynk.cpp b/wled00/blynk.cpp index b1619d816..c8103d8c4 100644 --- a/wled00/blynk.cpp +++ b/wled00/blynk.cpp @@ -1,5 +1,7 @@ #include "wled.h" +#ifndef WLED_DISABLE_BLYNK #include "src/dependencies/blynk/Blynk/BlynkHandlers.h" +#endif /* * Remote light control with the free Blynk app diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h index 612e2fcef..bfae90ac9 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -41,18 +41,19 @@ void colorRGBtoRGBW(byte* rgb); //temporary struct for passing bus configuration to bus struct BusConfig { - uint8_t type = TYPE_WS2812_RGB; + uint8_t type; uint16_t count; uint16_t start; uint8_t colorOrder; bool reversed; uint8_t skipAmount; bool refreshReq; + uint8_t autoWhite; uint8_t pins[5] = {LEDPIN, 255, 255, 255, 255}; - BusConfig(uint8_t busType, uint8_t* ppins, uint16_t pstart, uint16_t len = 1, uint8_t pcolorOrder = COL_ORDER_GRB, bool rev = false, uint8_t skip = 0) { + BusConfig(uint8_t busType, uint8_t* ppins, uint16_t pstart, uint16_t len = 1, uint8_t pcolorOrder = COL_ORDER_GRB, bool rev = false, uint8_t skip = 0, byte aw=RGBW_MODE_MANUAL_ONLY) { refreshReq = (bool) GET_BIT(busType,7); type = busType & 0x7F; // bit 7 may be/is hacked to include refresh info (1=refresh in off state, 0=no refresh) - count = len; start = pstart; colorOrder = pcolorOrder; reversed = rev; skipAmount = skip; + count = len; start = pstart; colorOrder = pcolorOrder; reversed = rev; skipAmount = skip; autoWhite = aw; uint8_t nPins = 1; if (type >= TYPE_NET_DDP_RGB && type < 96) nPins = 4; //virtual network bus. 4 "pins" store IP address else if (type > 47) nPins = 2; @@ -115,10 +116,11 @@ struct ColorOrderMap { inline uint8_t IRAM_ATTR getPixelColorOrder(uint16_t pix, uint8_t defaultColorOrder) const { if (_count == 0) return defaultColorOrder; - + // upper nibble containd W swap information + uint8_t swapW = defaultColorOrder >> 4; for (uint8_t i = 0; i < _count; i++) { if (pix >= _mappings[i].start && pix < (_mappings[i].start + _mappings[i].len)) { - return _mappings[i].colorOrder; + return _mappings[i].colorOrder | (swapW << 4); } } return defaultColorOrder; @@ -132,20 +134,26 @@ struct ColorOrderMap { //parent class of BusDigital, BusPwm, and BusNetwork class Bus { public: - Bus(uint8_t type, uint16_t start) { + Bus(uint8_t type, uint16_t start, uint8_t aw) + : _bri(255) + , _len(1) + , _valid(false) + , _needsRefresh(false) + { _type = type; _start = start; + _autoWhiteMode = Bus::isRgbw(_type) ? aw : RGBW_MODE_MANUAL_ONLY; }; virtual ~Bus() {} //throw the bus under the bus - virtual void show() {} + virtual void show() = 0; virtual bool canShow() { return true; } - virtual void setStatusPixel(uint32_t c) {} - virtual void setPixelColor(uint16_t pix, uint32_t c) {} + virtual void setStatusPixel(uint32_t c) {} + virtual void setPixelColor(uint16_t pix, uint32_t c) = 0; virtual uint32_t getPixelColor(uint16_t pix) { return 0; } - virtual void setBrightness(uint8_t b) {} - virtual void cleanup() {} + virtual void setBrightness(uint8_t b) { _bri = b; }; + virtual void cleanup() = 0; virtual uint8_t getPins(uint8_t* pinArray) { return 0; } virtual uint16_t getLength() { return _len; } virtual void setColorOrder() {} @@ -162,6 +170,16 @@ class Bus { static bool isRgbw(uint8_t type) { if (type == TYPE_SK6812_RGBW || type == TYPE_TM1814) return true; if (type > TYPE_ONOFF && type <= TYPE_ANALOG_5CH && type != TYPE_ANALOG_3CH) return true; + if (type == TYPE_NET_DDP_RGBW) return true; + return false; + } + virtual bool hasRGB() { + if (_type == TYPE_WS2812_1CH || _type == TYPE_WS2812_WWA || _type == TYPE_ANALOG_1CH || _type == TYPE_ANALOG_2CH || _type == TYPE_ONOFF) return false; + return true; + } + virtual bool hasWhite() { + if (_type == TYPE_SK6812_RGBW || _type == TYPE_TM1814 || _type == TYPE_WS2812_1CH || _type == TYPE_WS2812_WWA || + _type == TYPE_ANALOG_1CH || _type == TYPE_ANALOG_2CH || _type == TYPE_ANALOG_4CH || _type == TYPE_ANALOG_5CH || _type == TYPE_NET_DDP_RGBW) return true; return false; } static void setCCT(uint16_t cct) { @@ -175,32 +193,37 @@ class Bus { if (_cctBlend > WLED_MAX_CCT_BLEND) _cctBlend = WLED_MAX_CCT_BLEND; #endif } - inline static void setAutoWhiteMode(uint8_t m) { if (m < 4) _autoWhiteMode = m; } - inline static uint8_t getAutoWhiteMode() { return _autoWhiteMode; } + inline void setAWMode(uint8_t m) { if (m < 4) _autoWhiteMode = m; } + inline uint8_t getAWMode() { return _autoWhiteMode; } + inline static void setAutoWhiteMode(uint8_t m) { if (m < 4) _gAWM = m; else _gAWM = 255; } + inline static uint8_t getAutoWhiteMode() { return _gAWM; } bool reversed = false; protected: - uint8_t _type = TYPE_NONE; - uint8_t _bri = 255; - uint16_t _start = 0; - uint16_t _len = 1; - bool _valid = false; - bool _needsRefresh = false; - static uint8_t _autoWhiteMode; - static int16_t _cct; - static uint8_t _cctBlend; + uint8_t _type; + uint8_t _bri; + uint16_t _start; + uint16_t _len; + bool _valid; + bool _needsRefresh; + uint8_t _autoWhiteMode; + static uint8_t _gAWM; // definition in FX_fcn.cpp + static int16_t _cct; // definition in FX_fcn.cpp + static uint8_t _cctBlend; // definition in FX_fcn.cpp uint32_t autoWhiteCalc(uint32_t c) { - if (_autoWhiteMode == RGBW_MODE_MANUAL_ONLY) return c; + uint8_t aWM = _autoWhiteMode; + if (_gAWM < 255) aWM = _gAWM; + if (aWM == RGBW_MODE_MANUAL_ONLY) return c; uint8_t w = W(c); //ignore auto-white calculation if w>0 and mode DUAL (DUAL behaves as BRIGHTER if w==0) - if (w > 0 && _autoWhiteMode == RGBW_MODE_DUAL) return c; + if (w > 0 && aWM == RGBW_MODE_DUAL) return c; uint8_t r = R(c); uint8_t g = G(c); uint8_t b = B(c); w = r < g ? (r < b ? r : b) : (g < b ? g : b); - if (_autoWhiteMode == RGBW_MODE_AUTO_ACCURATE) { r -= w; g -= w; b -= w; } //subtract w in ACCURATE mode + if (aWM == RGBW_MODE_AUTO_ACCURATE) { r -= w; g -= w; b -= w; } //subtract w in ACCURATE mode return RGBW32(r, g, b, w); } }; @@ -208,7 +231,7 @@ class Bus { class BusDigital : public Bus { public: - BusDigital(BusConfig &bc, uint8_t nr, const ColorOrderMap &com) : Bus(bc.type, bc.start), _colorOrderMap(com) { + BusDigital(BusConfig &bc, uint8_t nr, const ColorOrderMap &com) : Bus(bc.type, bc.start, bc.autoWhite), _colorOrderMap(com) { if (!IS_DIGITAL(bc.type) || !bc.count) return; if (!pinManager.allocatePin(bc.pins[0], true, PinOwner::BusDigital)) return; _pins[0] = bc.pins[0]; @@ -227,7 +250,7 @@ class BusDigital : public Bus { _busPtr = PolyBus::create(_iType, _pins, _len, nr); _valid = (_busPtr != nullptr); _colorOrder = bc.colorOrder; - DEBUG_PRINTF("Successfully inited strip %u (len %u) with type %u and pins %u,%u (itype %u)\n",nr, _len, bc.type, _pins[0],_pins[1],_iType); + DEBUG_PRINTF("%successfully inited strip %u (len %u) with type %u and pins %u,%u (itype %u)\n", _valid?"S":"Uns", nr, _len, bc.type, _pins[0],_pins[1],_iType); }; inline void show() { @@ -245,7 +268,7 @@ class BusDigital : public Bus { if (_pins[0] == LED_BUILTIN || _pins[1] == LED_BUILTIN) PolyBus::begin(_busPtr, _iType, _pins); } #endif - _bri = b; + Bus::setBrightness(b); PolyBus::setBrightness(_busPtr, _iType, b); } @@ -287,7 +310,8 @@ class BusDigital : public Bus { } void setColorOrder(uint8_t colorOrder) { - if (colorOrder > 5) return; + // upper nibble contains W swap information + if ((colorOrder & 0x0F) > 5) return; _colorOrder = colorOrder; } @@ -325,7 +349,7 @@ class BusDigital : public Bus { class BusPwm : public Bus { public: - BusPwm(BusConfig &bc) : Bus(bc.type, bc.start) { + BusPwm(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWhite) { _valid = false; if (!IS_PWM(bc.type)) return; uint8_t numPins = NUM_PWM_PINS(bc.type); @@ -385,7 +409,7 @@ class BusPwm : public Bus { else ww = ((255-cct) * 255) / (255 - _cctBlend); if ((255-cct) < _cctBlend) cw = 255; - else cw = (cct * 255) / (255 - _cctBlend); + else cw = (cct * 255) / (255 - _cctBlend); ww = (w * ww) / 255; //brightness scaling cw = (w * cw) / 255; @@ -400,7 +424,6 @@ class BusPwm : public Bus { _data[0] = ww; break; case TYPE_ANALOG_5CH: //RGB + warm white + cold white - // perhaps a non-linear adjustment would be in order. need to test _data[4] = cw; w = ww; case TYPE_ANALOG_4CH: //RGBW @@ -431,10 +454,6 @@ class BusPwm : public Bus { } } - inline void setBrightness(uint8_t b) { - _bri = b; - } - uint8_t getPins(uint8_t* pinArray) { if (!_valid) return 0; uint8_t numPins = NUM_PWM_PINS(_type); @@ -444,7 +463,7 @@ class BusPwm : public Bus { return numPins; } - inline void cleanup() { + void cleanup() { deallocatePins(); } @@ -477,9 +496,66 @@ class BusPwm : public Bus { }; +class BusOnOff : public Bus { + public: + BusOnOff(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWhite) { + _valid = false; + if (bc.type != TYPE_ONOFF) return; + + uint8_t currentPin = bc.pins[0]; + if (!pinManager.allocatePin(currentPin, true, PinOwner::BusOnOff)) { + return; + } + _pin = currentPin; //store only after allocatePin() succeeds + pinMode(_pin, OUTPUT); + reversed = bc.reversed; + _valid = true; + }; + + void setPixelColor(uint16_t pix, uint32_t c) { + if (pix != 0 || !_valid) return; //only react to first pixel + c = autoWhiteCalc(c); + uint8_t r = R(c); + uint8_t g = G(c); + uint8_t b = B(c); + uint8_t w = W(c); + + _data = bool((r+g+b+w) && _bri) ? 0xFF : 0; + } + + uint32_t getPixelColor(uint16_t pix) { + if (!_valid) return 0; + return RGBW32(_data, _data, _data, _data); + } + + void show() { + if (!_valid) return; + digitalWrite(_pin, reversed ? !(bool)_data : (bool)_data); + } + + uint8_t getPins(uint8_t* pinArray) { + if (!_valid) return 0; + pinArray[0] = _pin; + return 1; + } + + void cleanup() { + pinManager.deallocatePin(_pin, PinOwner::BusOnOff); + } + + ~BusOnOff() { + cleanup(); + } + + private: + uint8_t _pin = 255; + uint8_t _data = 0; +}; + + class BusNetwork : public Bus { public: - BusNetwork(BusConfig &bc) : Bus(bc.type, bc.start) { + BusNetwork(BusConfig &bc) : Bus(bc.type, bc.start, bc.autoWhite) { _valid = false; // switch (bc.type) { // case TYPE_NET_ARTNET_RGB: @@ -494,9 +570,9 @@ class BusNetwork : public Bus { // _rgbw = false; // _UDPtype = 0; // break; -// default: - _rgbw = false; - _UDPtype = bc.type - TYPE_NET_DDP_RGB; +// default: // TYPE_NET_DDP_RGB / TYPE_NET_DDP_RGBW + _rgbw = bc.type == TYPE_NET_DDP_RGBW; + _UDPtype = 0; // break; // } _UDPchannels = _rgbw ? 4 : 3; @@ -509,6 +585,9 @@ class BusNetwork : public Bus { _valid = true; }; + bool hasRGB() { return true; } + bool hasWhite() { return _rgbw; } + void setPixelColor(uint16_t pix, uint32_t c) { if (!_valid || pix >= _len) return; if (isRgbw()) c = autoWhiteCalc(c); @@ -538,10 +617,6 @@ class BusNetwork : public Bus { return !_broadcastLock; } - inline void setBrightness(uint8_t b) { - _bri = b; - } - uint8_t getPins(uint8_t* pinArray) { for (uint8_t i = 0; i < 4; i++) { pinArray[i] = _client[i]; @@ -570,7 +645,6 @@ class BusNetwork : public Bus { private: IPAddress _client; - uint8_t _bri = 255; uint8_t _UDPtype; uint8_t _UDPchannels; bool _rgbw; @@ -581,9 +655,7 @@ class BusNetwork : public Bus { class BusManager { public: - BusManager() { - - }; + BusManager() {}; //utility to get the approx. memory usage of a given BusConfig static uint32_t memUsage(BusConfig &bc) { @@ -613,6 +685,8 @@ class BusManager { busses[numBusses] = new BusNetwork(bc); } else if (IS_DIGITAL(bc.type)) { busses[numBusses] = new BusDigital(bc, numBusses, colorOrderMap); + } else if (bc.type == TYPE_ONOFF) { + busses[numBusses] = new BusOnOff(bc); } else { busses[numBusses] = new BusPwm(bc); } diff --git a/wled00/bus_wrapper.h b/wled00/bus_wrapper.h index bda4de33a..1c2468d63 100644 --- a/wled00/bus_wrapper.h +++ b/wled00/bus_wrapper.h @@ -3,6 +3,21 @@ #include "NeoPixelBrightnessBus.h" +// temporary - these defines should actually be set in platformio.ini +// C3: I2S0 and I2S1 methods not supported (has one I2S bus) +// S2: I2S1 methods not supported (has one I2S bus) +// S3: I2S0 and I2S1 methods not supported yet (has two I2S buses) +// https://github.com/Makuna/NeoPixelBus/blob/b32f719e95ef3c35c46da5c99538017ef925c026/src/internal/Esp32_i2s.h#L4 +// https://github.com/Makuna/NeoPixelBus/blob/b32f719e95ef3c35c46da5c99538017ef925c026/src/internal/NeoEsp32RmtMethod.h#L857 + +#if !defined(WLED_NO_I2S0_PIXELBUS) && (defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C3)) +#define WLED_NO_I2S0_PIXELBUS +#endif +#if !defined(WLED_NO_I2S1_PIXELBUS) && (defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S2)) +#define WLED_NO_I2S1_PIXELBUS +#endif +// temporary end + //Hardware SPI Pins #define P_8266_HS_MOSI 13 #define P_8266_HS_CLK 14 @@ -33,45 +48,58 @@ #define I_8266_U1_TM1_4 14 #define I_8266_DM_TM1_4 15 #define I_8266_BB_TM1_4 16 +//TM1829 (RGB) +#define I_8266_U0_TM2_3 17 +#define I_8266_U1_TM2_3 18 +#define I_8266_DM_TM2_3 19 +#define I_8266_BB_TM2_3 20 /*** ESP32 Neopixel methods ***/ //RGB -#define I_32_RN_NEO_3 17 -#define I_32_I0_NEO_3 18 -#define I_32_I1_NEO_3 19 +#define I_32_RN_NEO_3 21 +#define I_32_I0_NEO_3 22 +#define I_32_I1_NEO_3 23 +#define I_32_BB_NEO_3 24 // bitbangging on ESP32 not recommended //RGBW -#define I_32_RN_NEO_4 20 -#define I_32_I0_NEO_4 21 -#define I_32_I1_NEO_4 22 +#define I_32_RN_NEO_4 25 +#define I_32_I0_NEO_4 26 +#define I_32_I1_NEO_4 27 +#define I_32_BB_NEO_4 28 // bitbangging on ESP32 not recommended //400Kbps -#define I_32_RN_400_3 23 -#define I_32_I0_400_3 24 -#define I_32_I1_400_3 25 +#define I_32_RN_400_3 29 +#define I_32_I0_400_3 30 +#define I_32_I1_400_3 31 +#define I_32_BB_400_3 32 // bitbangging on ESP32 not recommended //TM1814 (RGBW) -#define I_32_RN_TM1_4 26 -#define I_32_I0_TM1_4 27 -#define I_32_I1_TM1_4 28 +#define I_32_RN_TM1_4 33 +#define I_32_I0_TM1_4 34 +#define I_32_I1_TM1_4 35 +//Bit Bang theoratically possible, but very undesirable and not needed (no pin restrictions on RMT and I2S) +//TM1829 (RGB) +#define I_32_RN_TM2_3 36 +#define I_32_I0_TM2_3 37 +#define I_32_I1_TM2_3 38 //Bit Bang theoratically possible, but very undesirable and not needed (no pin restrictions on RMT and I2S) //APA102 -#define I_HS_DOT_3 29 //hardware SPI -#define I_SS_DOT_3 30 //soft SPI +#define I_HS_DOT_3 39 //hardware SPI +#define I_SS_DOT_3 40 //soft SPI //LPD8806 -#define I_HS_LPD_3 31 -#define I_SS_LPD_3 32 +#define I_HS_LPD_3 41 +#define I_SS_LPD_3 42 //WS2801 -#define I_HS_WS1_3 33 -#define I_SS_WS1_3 34 +#define I_HS_WS1_3 43 +#define I_SS_WS1_3 44 //P9813 -#define I_HS_P98_3 35 -#define I_SS_P98_3 36 +#define I_HS_P98_3 45 +#define I_SS_P98_3 46 //LPD6803 -#define I_HS_LPO_3 37 -#define I_SS_LPO_3 38 +#define I_HS_LPO_3 47 +#define I_SS_LPO_3 48 /*** ESP8266 Neopixel methods ***/ @@ -96,43 +124,60 @@ #define B_8266_U1_TM1_4 NeoPixelBrightnessBus #define B_8266_DM_TM1_4 NeoPixelBrightnessBus #define B_8266_BB_TM1_4 NeoPixelBrightnessBus +//TM1829 (RGB) +#define B_8266_U0_TM2_4 NeoPixelBrightnessBus +#define B_8266_U1_TM2_4 NeoPixelBrightnessBus +#define B_8266_DM_TM2_4 NeoPixelBrightnessBus +#define B_8266_BB_TM2_4 NeoPixelBrightnessBus #endif /*** ESP32 Neopixel methods ***/ #ifdef ARDUINO_ARCH_ESP32 //RGB #define B_32_RN_NEO_3 NeoPixelBrightnessBus -#ifndef CONFIG_IDF_TARGET_ESP32C3 +#ifndef WLED_NO_I2S0_PIXELBUS #define B_32_I0_NEO_3 NeoPixelBrightnessBus #endif -#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) +#ifndef WLED_NO_I2S1_PIXELBUS #define B_32_I1_NEO_3 NeoPixelBrightnessBus #endif +//#define B_32_BB_NEO_3 NeoPixelBrightnessBus // NeoEsp8266BitBang800KbpsMethod //RGBW #define B_32_RN_NEO_4 NeoPixelBrightnessBus -#ifndef CONFIG_IDF_TARGET_ESP32C3 +#ifndef WLED_NO_I2S0_PIXELBUS #define B_32_I0_NEO_4 NeoPixelBrightnessBus #endif -#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) +#ifndef WLED_NO_I2S1_PIXELBUS #define B_32_I1_NEO_4 NeoPixelBrightnessBus #endif +//#define B_32_BB_NEO_4 NeoPixelBrightnessBus // NeoEsp8266BitBang800KbpsMethod //400Kbps #define B_32_RN_400_3 NeoPixelBrightnessBus -#ifndef CONFIG_IDF_TARGET_ESP32C3 +#ifndef WLED_NO_I2S0_PIXELBUS #define B_32_I0_400_3 NeoPixelBrightnessBus #endif -#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) +#ifndef WLED_NO_I2S1_PIXELBUS #define B_32_I1_400_3 NeoPixelBrightnessBus #endif +//#define B_32_BB_400_3 NeoPixelBrightnessBus // NeoEsp8266BitBang400KbpsMethod //TM1814 (RGBW) #define B_32_RN_TM1_4 NeoPixelBrightnessBus -#ifndef CONFIG_IDF_TARGET_ESP32C3 +#ifndef WLED_NO_I2S0_PIXELBUS #define B_32_I0_TM1_4 NeoPixelBrightnessBus #endif -#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) +#ifndef WLED_NO_I2S1_PIXELBUS #define B_32_I1_TM1_4 NeoPixelBrightnessBus #endif //Bit Bang theoratically possible, but very undesirable and not needed (no pin restrictions on RMT and I2S) +//TM1829 (RGB) +#define B_32_RN_TM2_3 NeoPixelBrightnessBus +#ifndef WLED_NO_I2S0_PIXELBUS +#define B_32_I0_TM2_3 NeoPixelBrightnessBus +#endif +#ifndef WLED_NO_I2S1_PIXELBUS +#define B_32_I1_TM2_3 NeoPixelBrightnessBus +#endif +//Bit Bang theoratically possible, but very undesirable and not needed (no pin restrictions on RMT and I2S) #endif @@ -149,10 +194,21 @@ #define B_SS_LPO_3 NeoPixelBrightnessBus //WS2801 -//#define B_HS_WS1_3 NeoPixelBrightnessBus -//#define B_HS_WS1_3 NeoPixelBrightnessBus -//#define B_HS_WS1_3 NeoPixelBrightnessBus // 10MHz -#define B_HS_WS1_3 NeoPixelBrightnessBus //slower, more compatible +#if defined(WLED_WS2801_SPEED_KHZ) && WLED_WS2801_SPEED_KHZ==40000 +#define B_HS_WS1_3 NeoPixelBrightnessBus // fastest bus speed (not existing method?) +#elif defined(WLED_WS2801_SPEED_KHZ) && WLED_WS2801_SPEED_KHZ==20000 +#define B_HS_WS1_3 NeoPixelBrightnessBus // 20MHz +#elif defined(WLED_WS2801_SPEED_KHZ) && WLED_WS2801_SPEED_KHZ==10000 +#define B_HS_WS1_3 NeoPixelBrightnessBus // 10MHz +#elif defined(WLED_WS2801_SPEED_KHZ) && WLED_WS2801_SPEED_KHZ==2000 +#define B_HS_WS1_3 NeoPixelBrightnessBus //slower, more compatible +#elif defined(WLED_WS2801_SPEED_KHZ) && WLED_WS2801_SPEED_KHZ==1000 +#define B_HS_WS1_3 NeoPixelBrightnessBus //slower, more compatible +#elif defined(WLED_WS2801_SPEED_KHZ) && WLED_WS2801_SPEED_KHZ==500 +#define B_HS_WS1_3 NeoPixelBrightnessBus //slower, more compatible +#else +#define B_HS_WS1_3 NeoPixelBrightnessBus // 2MHz; slower, more compatible +#endif #define B_SS_WS1_3 NeoPixelBrightnessBus //P9813 @@ -190,6 +246,10 @@ class PolyBus { case I_8266_U1_TM1_4: beginTM1814(busPtr); break; case I_8266_DM_TM1_4: beginTM1814(busPtr); break; case I_8266_BB_TM1_4: beginTM1814(busPtr); break; + case I_8266_U0_TM2_3: (static_cast(busPtr))->Begin(); break; + case I_8266_U1_TM2_3: (static_cast(busPtr))->Begin(); break; + case I_8266_DM_TM2_3: (static_cast(busPtr))->Begin(); break; + case I_8266_BB_TM2_3: (static_cast(busPtr))->Begin(); break; case I_HS_DOT_3: (static_cast(busPtr))->Begin(); break; case I_HS_LPD_3: (static_cast(busPtr))->Begin(); break; case I_HS_LPO_3: (static_cast(busPtr))->Begin(); break; @@ -198,32 +258,38 @@ class PolyBus { #endif #ifdef ARDUINO_ARCH_ESP32 case I_32_RN_NEO_3: (static_cast(busPtr))->Begin(); break; - #ifndef CONFIG_IDF_TARGET_ESP32C3 + #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_NEO_3: (static_cast(busPtr))->Begin(); break; #endif - #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) + #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_NEO_3: (static_cast(busPtr))->Begin(); break; #endif +// case I_32_BB_NEO_3: (static_cast(busPtr))->Begin(); break; case I_32_RN_NEO_4: (static_cast(busPtr))->Begin(); break; - #ifndef CONFIG_IDF_TARGET_ESP32C3 + #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_NEO_4: (static_cast(busPtr))->Begin(); break; #endif - #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) + #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_NEO_4: (static_cast(busPtr))->Begin(); break; #endif +// case I_32_BB_NEO_4: (static_cast(busPtr))->Begin(); break; case I_32_RN_400_3: (static_cast(busPtr))->Begin(); break; - #ifndef CONFIG_IDF_TARGET_ESP32C3 + #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_400_3: (static_cast(busPtr))->Begin(); break; #endif - #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) + #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_400_3: (static_cast(busPtr))->Begin(); break; #endif +// case I_32_BB_400_3: (static_cast(busPtr))->Begin(); break; case I_32_RN_TM1_4: beginTM1814(busPtr); break; - #ifndef CONFIG_IDF_TARGET_ESP32C3 + case I_32_RN_TM2_3: (static_cast(busPtr))->Begin(); break; + #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_TM1_4: beginTM1814(busPtr); break; + case I_32_I0_TM2_3: (static_cast(busPtr))->Begin(); break; #endif - #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) + #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_TM1_4: beginTM1814(busPtr); break; + case I_32_I1_TM2_3: (static_cast(busPtr))->Begin(); break; #endif // ESP32 can (and should, to avoid inadvertantly driving the chip select signal) specify the pins used for SPI, but only in begin() case I_HS_DOT_3: (static_cast(busPtr))->Begin(pins[1], -1, pins[0], -1); break; @@ -260,35 +326,45 @@ class PolyBus { case I_8266_U1_TM1_4: busPtr = new B_8266_U1_TM1_4(len, pins[0]); break; case I_8266_DM_TM1_4: busPtr = new B_8266_DM_TM1_4(len, pins[0]); break; case I_8266_BB_TM1_4: busPtr = new B_8266_BB_TM1_4(len, pins[0]); break; + case I_8266_U0_TM2_3: busPtr = new B_8266_U0_TM2_4(len, pins[0]); break; + case I_8266_U1_TM2_3: busPtr = new B_8266_U1_TM2_4(len, pins[0]); break; + case I_8266_DM_TM2_3: busPtr = new B_8266_DM_TM2_4(len, pins[0]); break; + case I_8266_BB_TM2_3: busPtr = new B_8266_BB_TM2_4(len, pins[0]); break; #endif #ifdef ARDUINO_ARCH_ESP32 case I_32_RN_NEO_3: busPtr = new B_32_RN_NEO_3(len, pins[0], (NeoBusChannel)channel); break; - #ifndef CONFIG_IDF_TARGET_ESP32C3 + #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_NEO_3: busPtr = new B_32_I0_NEO_3(len, pins[0]); break; #endif - #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) + #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_NEO_3: busPtr = new B_32_I1_NEO_3(len, pins[0]); break; #endif +// case I_32_BB_NEO_3: busPtr = new B_32_BB_NEO_3(len, pins[0], (NeoBusChannel)channel); break; case I_32_RN_NEO_4: busPtr = new B_32_RN_NEO_4(len, pins[0], (NeoBusChannel)channel); break; - #ifndef CONFIG_IDF_TARGET_ESP32C3 + #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_NEO_4: busPtr = new B_32_I0_NEO_4(len, pins[0]); break; #endif - #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) + #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_NEO_4: busPtr = new B_32_I1_NEO_4(len, pins[0]); break; #endif +// case I_32_BB_NEO_4: busPtr = new B_32_BB_NEO_4(len, pins[0], (NeoBusChannel)channel); break; case I_32_RN_400_3: busPtr = new B_32_RN_400_3(len, pins[0], (NeoBusChannel)channel); break; - #ifndef CONFIG_IDF_TARGET_ESP32C3 + #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_400_3: busPtr = new B_32_I0_400_3(len, pins[0]); break; #endif - #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) + #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_400_3: busPtr = new B_32_I1_400_3(len, pins[0]); break; #endif +// case I_32_BB_400_3: busPtr = new B_32_BB_400_3(len, pins[0], (NeoBusChannel)channel); break; case I_32_RN_TM1_4: busPtr = new B_32_RN_TM1_4(len, pins[0], (NeoBusChannel)channel); break; - #ifndef CONFIG_IDF_TARGET_ESP32C3 + case I_32_RN_TM2_3: busPtr = new B_32_RN_TM2_3(len, pins[0], (NeoBusChannel)channel); break; + #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_TM1_4: busPtr = new B_32_I0_TM1_4(len, pins[0]); break; + case I_32_I0_TM2_3: busPtr = new B_32_I0_TM2_3(len, pins[0]); break; #endif - #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) + #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_TM1_4: busPtr = new B_32_I1_TM1_4(len, pins[0]); break; + case I_32_I1_TM2_3: busPtr = new B_32_I1_TM2_3(len, pins[0]); break; #endif #endif // for 2-wire: pins[1] is clk, pins[0] is dat. begin expects (len, clk, dat) @@ -326,35 +402,45 @@ class PolyBus { case I_8266_U1_TM1_4: (static_cast(busPtr))->Show(); break; case I_8266_DM_TM1_4: (static_cast(busPtr))->Show(); break; case I_8266_BB_TM1_4: (static_cast(busPtr))->Show(); break; + case I_8266_U0_TM2_3: (static_cast(busPtr))->Show(); break; + case I_8266_U1_TM2_3: (static_cast(busPtr))->Show(); break; + case I_8266_DM_TM2_3: (static_cast(busPtr))->Show(); break; + case I_8266_BB_TM2_3: (static_cast(busPtr))->Show(); break; #endif #ifdef ARDUINO_ARCH_ESP32 case I_32_RN_NEO_3: (static_cast(busPtr))->Show(); break; - #ifndef CONFIG_IDF_TARGET_ESP32C3 + #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_NEO_3: (static_cast(busPtr))->Show(); break; #endif - #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) + #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_NEO_3: (static_cast(busPtr))->Show(); break; #endif +// case I_32_BB_NEO_3: (static_cast(busPtr))->Show(); break; case I_32_RN_NEO_4: (static_cast(busPtr))->Show(); break; - #ifndef CONFIG_IDF_TARGET_ESP32C3 + #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_NEO_4: (static_cast(busPtr))->Show(); break; #endif - #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) + #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_NEO_4: (static_cast(busPtr))->Show(); break; #endif +// case I_32_BB_NEO_4: (static_cast(busPtr))->Show(); break; case I_32_RN_400_3: (static_cast(busPtr))->Show(); break; - #ifndef CONFIG_IDF_TARGET_ESP32C3 + #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_400_3: (static_cast(busPtr))->Show(); break; #endif - #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) + #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_400_3: (static_cast(busPtr))->Show(); break; #endif +// case I_32_BB_400_3: (static_cast(busPtr))->Show(); break; case I_32_RN_TM1_4: (static_cast(busPtr))->Show(); break; - #ifndef CONFIG_IDF_TARGET_ESP32C3 + case I_32_RN_TM2_3: (static_cast(busPtr))->Show(); break; + #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_TM1_4: (static_cast(busPtr))->Show(); break; + case I_32_I0_TM2_3: (static_cast(busPtr))->Show(); break; #endif - #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) + #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_TM1_4: (static_cast(busPtr))->Show(); break; + case I_32_I1_TM2_3: (static_cast(busPtr))->Show(); break; #endif #endif case I_HS_DOT_3: (static_cast(busPtr))->Show(); break; @@ -389,35 +475,45 @@ class PolyBus { case I_8266_U1_TM1_4: return (static_cast(busPtr))->CanShow(); break; case I_8266_DM_TM1_4: return (static_cast(busPtr))->CanShow(); break; case I_8266_BB_TM1_4: return (static_cast(busPtr))->CanShow(); break; + case I_8266_U0_TM2_3: return (static_cast(busPtr))->CanShow(); break; + case I_8266_U1_TM2_3: return (static_cast(busPtr))->CanShow(); break; + case I_8266_DM_TM2_3: return (static_cast(busPtr))->CanShow(); break; + case I_8266_BB_TM2_3: return (static_cast(busPtr))->CanShow(); break; #endif #ifdef ARDUINO_ARCH_ESP32 case I_32_RN_NEO_3: return (static_cast(busPtr))->CanShow(); break; - #ifndef CONFIG_IDF_TARGET_ESP32C3 + #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_NEO_3: return (static_cast(busPtr))->CanShow(); break; #endif - #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) + #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_NEO_3: return (static_cast(busPtr))->CanShow(); break; #endif +// case I_32_BB_NEO_3: return (static_cast(busPtr))->CanShow(); break; case I_32_RN_NEO_4: return (static_cast(busPtr))->CanShow(); break; - #ifndef CONFIG_IDF_TARGET_ESP32C3 + #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_NEO_4: return (static_cast(busPtr))->CanShow(); break; #endif - #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) + #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_NEO_4: return (static_cast(busPtr))->CanShow(); break; #endif +// case I_32_BB_NEO_4: return (static_cast(busPtr))->CanShow(); break; case I_32_RN_400_3: return (static_cast(busPtr))->CanShow(); break; - #ifndef CONFIG_IDF_TARGET_ESP32C3 + #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_400_3: return (static_cast(busPtr))->CanShow(); break; #endif - #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) + #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_400_3: return (static_cast(busPtr))->CanShow(); break; #endif +// case I_32_BB_400_3: return (static_cast(busPtr))->CanShow(); break; case I_32_RN_TM1_4: return (static_cast(busPtr))->CanShow(); break; - #ifndef CONFIG_IDF_TARGET_ESP32C3 + case I_32_RN_TM2_3: return (static_cast(busPtr))->CanShow(); break; + #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_TM1_4: return (static_cast(busPtr))->CanShow(); break; + case I_32_I0_TM2_3: return (static_cast(busPtr))->CanShow(); break; #endif - #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) + #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_TM1_4: return (static_cast(busPtr))->CanShow(); break; + case I_32_I1_TM2_3: return (static_cast(busPtr))->CanShow(); break; #endif #endif case I_HS_DOT_3: return (static_cast(busPtr))->CanShow(); break; @@ -440,22 +536,22 @@ class PolyBus { uint8_t w = c >> 24; RgbwColor col; - //TODO make color order override possible on a per-strip basis - #ifdef COLOR_ORDER_OVERRIDE - if (pix >= COO_MIN && pix < COO_MAX) co = COO_ORDER; - #endif - - //reorder channels to selected order - switch (co) - { - case 0: col.G = g; col.R = r; col.B = b; break; //0 = GRB, default + // reorder channels to selected order + switch (co & 0x0F) { + default: col.G = g; col.R = r; col.B = b; break; //0 = GRB, default case 1: col.G = r; col.R = g; col.B = b; break; //1 = RGB, common for WS2811 case 2: col.G = b; col.R = r; col.B = g; break; //2 = BRG case 3: col.G = r; col.R = b; col.B = g; break; //3 = RBG case 4: col.G = b; col.R = g; col.B = r; break; //4 = BGR - default: col.G = g; col.R = b; col.B = r; break; //5 = GBR + case 5: col.G = g; col.R = b; col.B = r; break; //5 = GBR + } + // upper nibble contains W swap information + switch (co >> 4) { + default: col.W = w; break; // no swapping + case 1: col.W = col.B; col.B = w; break; // swap W & B + case 2: col.W = col.G; col.G = w; break; // swap W & G + case 3: col.W = col.R; col.R = w; break; // swap W & R } - col.W = w; switch (busType) { case I_NONE: break; @@ -476,35 +572,45 @@ class PolyBus { case I_8266_U1_TM1_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; case I_8266_DM_TM1_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; case I_8266_BB_TM1_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; + case I_8266_U0_TM2_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; + case I_8266_U1_TM2_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; + case I_8266_DM_TM2_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; + case I_8266_BB_TM2_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; #endif #ifdef ARDUINO_ARCH_ESP32 case I_32_RN_NEO_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; - #ifndef CONFIG_IDF_TARGET_ESP32C3 + #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_NEO_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; #endif - #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) + #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_NEO_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; #endif +// case I_32_BB_NEO_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; case I_32_RN_NEO_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; - #ifndef CONFIG_IDF_TARGET_ESP32C3 + #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_NEO_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; #endif - #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) + #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_NEO_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; #endif +// case I_32_BB_NEO_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; case I_32_RN_400_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; - #ifndef CONFIG_IDF_TARGET_ESP32C3 + #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_400_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; #endif - #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) + #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_400_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; #endif +// case I_32_BB_400_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; case I_32_RN_TM1_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; - #ifndef CONFIG_IDF_TARGET_ESP32C3 + case I_32_RN_TM2_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; + #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_TM1_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; + case I_32_I0_TM2_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; #endif - #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) + #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_TM1_4: (static_cast(busPtr))->SetPixelColor(pix, col); break; + case I_32_I1_TM2_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; #endif #endif case I_HS_DOT_3: (static_cast(busPtr))->SetPixelColor(pix, RgbColor(col.R,col.G,col.B)); break; @@ -539,35 +645,45 @@ class PolyBus { case I_8266_U1_TM1_4: (static_cast(busPtr))->SetBrightness(b); break; case I_8266_DM_TM1_4: (static_cast(busPtr))->SetBrightness(b); break; case I_8266_BB_TM1_4: (static_cast(busPtr))->SetBrightness(b); break; + case I_8266_U0_TM2_3: (static_cast(busPtr))->SetBrightness(b); break; + case I_8266_U1_TM2_3: (static_cast(busPtr))->SetBrightness(b); break; + case I_8266_DM_TM2_3: (static_cast(busPtr))->SetBrightness(b); break; + case I_8266_BB_TM2_3: (static_cast(busPtr))->SetBrightness(b); break; #endif #ifdef ARDUINO_ARCH_ESP32 case I_32_RN_NEO_3: (static_cast(busPtr))->SetBrightness(b); break; - #ifndef CONFIG_IDF_TARGET_ESP32C3 + #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_NEO_3: (static_cast(busPtr))->SetBrightness(b); break; #endif - #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) + #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_NEO_3: (static_cast(busPtr))->SetBrightness(b); break; #endif +// case I_32_BB_NEO_3: (static_cast(busPtr))->SetBrightness(b); break; case I_32_RN_NEO_4: (static_cast(busPtr))->SetBrightness(b); break; - #ifndef CONFIG_IDF_TARGET_ESP32C3 + #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_NEO_4: (static_cast(busPtr))->SetBrightness(b); break; #endif - #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) + #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_NEO_4: (static_cast(busPtr))->SetBrightness(b); break; #endif +// case I_32_BB_NEO_4: (static_cast(busPtr))->SetBrightness(b); break; case I_32_RN_400_3: (static_cast(busPtr))->SetBrightness(b); break; - #ifndef CONFIG_IDF_TARGET_ESP32C3 + #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_400_3: (static_cast(busPtr))->SetBrightness(b); break; #endif - #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) + #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_400_3: (static_cast(busPtr))->SetBrightness(b); break; #endif +// case I_32_BB_400_3: (static_cast(busPtr))->SetBrightness(b); break; case I_32_RN_TM1_4: (static_cast(busPtr))->SetBrightness(b); break; - #ifndef CONFIG_IDF_TARGET_ESP32C3 + case I_32_RN_TM2_3: (static_cast(busPtr))->SetBrightness(b); break; + #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_TM1_4: (static_cast(busPtr))->SetBrightness(b); break; + case I_32_I0_TM2_3: (static_cast(busPtr))->SetBrightness(b); break; #endif - #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) + #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_TM1_4: (static_cast(busPtr))->SetBrightness(b); break; + case I_32_I1_TM2_3: (static_cast(busPtr))->SetBrightness(b); break; #endif #endif case I_HS_DOT_3: (static_cast(busPtr))->SetBrightness(b); break; @@ -603,35 +719,45 @@ class PolyBus { case I_8266_U1_TM1_4: col = (static_cast(busPtr))->GetPixelColor(pix); break; case I_8266_DM_TM1_4: col = (static_cast(busPtr))->GetPixelColor(pix); break; case I_8266_BB_TM1_4: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_8266_U0_TM2_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_8266_U1_TM2_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_8266_DM_TM2_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_8266_BB_TM2_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; #endif #ifdef ARDUINO_ARCH_ESP32 case I_32_RN_NEO_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; - #ifndef CONFIG_IDF_TARGET_ESP32C3 + #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_NEO_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; #endif - #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) + #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_NEO_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; #endif +// case I_32_BB_NEO_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; case I_32_RN_NEO_4: col = (static_cast(busPtr))->GetPixelColor(pix); break; - #ifndef CONFIG_IDF_TARGET_ESP32C3 + #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_NEO_4: col = (static_cast(busPtr))->GetPixelColor(pix); break; #endif - #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) + #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_NEO_4: col = (static_cast(busPtr))->GetPixelColor(pix); break; #endif +// case I_32_BB_NEO_4: col = (static_cast(busPtr))->GetPixelColor(pix); break; case I_32_RN_400_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; - #ifndef CONFIG_IDF_TARGET_ESP32C3 + #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_400_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; #endif - #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) + #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_400_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; #endif +// case I_32_BB_400_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; case I_32_RN_TM1_4: col = (static_cast(busPtr))->GetPixelColor(pix); break; - #ifndef CONFIG_IDF_TARGET_ESP32C3 + case I_32_RN_TM2_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; + #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_TM1_4: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_I0_TM2_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; #endif - #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) + #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_TM1_4: col = (static_cast(busPtr))->GetPixelColor(pix); break; + case I_32_I1_TM2_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; #endif #endif case I_HS_DOT_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; @@ -646,14 +772,16 @@ class PolyBus { case I_SS_P98_3: col = (static_cast(busPtr))->GetPixelColor(pix); break; } - #ifdef COLOR_ORDER_OVERRIDE - if (pix >= COO_MIN && pix < COO_MAX) co = COO_ORDER; - #endif - - switch (co) - { + // upper nibble contains W swap information + uint8_t w = col.W; + switch (co >> 4) { + case 1: col.W = col.B; col.B = w; break; // swap W & B + case 2: col.W = col.G; col.G = w; break; // swap W & G + case 3: col.W = col.R; col.R = w; break; // swap W & R + } + switch (co & 0x0F) { // W G R B - case 0: return ((col.W << 24) | (col.G << 8) | (col.R << 16) | (col.B)); //0 = GRB, default + default: return ((col.W << 24) | (col.G << 8) | (col.R << 16) | (col.B)); //0 = GRB, default case 1: return ((col.W << 24) | (col.R << 8) | (col.G << 16) | (col.B)); //1 = RGB, common for WS2811 case 2: return ((col.W << 24) | (col.B << 8) | (col.R << 16) | (col.G)); //2 = BRG case 3: return ((col.W << 24) | (col.B << 8) | (col.G << 16) | (col.R)); //3 = RBG @@ -684,35 +812,45 @@ class PolyBus { case I_8266_U1_TM1_4: delete (static_cast(busPtr)); break; case I_8266_DM_TM1_4: delete (static_cast(busPtr)); break; case I_8266_BB_TM1_4: delete (static_cast(busPtr)); break; + case I_8266_U0_TM2_3: delete (static_cast(busPtr)); break; + case I_8266_U1_TM2_3: delete (static_cast(busPtr)); break; + case I_8266_DM_TM2_3: delete (static_cast(busPtr)); break; + case I_8266_BB_TM2_3: delete (static_cast(busPtr)); break; #endif #ifdef ARDUINO_ARCH_ESP32 case I_32_RN_NEO_3: delete (static_cast(busPtr)); break; - #ifndef CONFIG_IDF_TARGET_ESP32C3 + #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_NEO_3: delete (static_cast(busPtr)); break; #endif - #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) + #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_NEO_3: delete (static_cast(busPtr)); break; #endif +// case I_32_BB_NEO_3: delete (static_cast(busPtr)); break; case I_32_RN_NEO_4: delete (static_cast(busPtr)); break; - #ifndef CONFIG_IDF_TARGET_ESP32C3 + #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_NEO_4: delete (static_cast(busPtr)); break; #endif - #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) + #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_NEO_4: delete (static_cast(busPtr)); break; #endif +// case I_32_BB_NEO_4: delete (static_cast(busPtr)); break; case I_32_RN_400_3: delete (static_cast(busPtr)); break; - #ifndef CONFIG_IDF_TARGET_ESP32C3 + #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_400_3: delete (static_cast(busPtr)); break; #endif - #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) + #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_400_3: delete (static_cast(busPtr)); break; #endif +// case I_32_BB_400_3: delete (static_cast(busPtr)); break; case I_32_RN_TM1_4: delete (static_cast(busPtr)); break; - #ifndef CONFIG_IDF_TARGET_ESP32C3 + case I_32_RN_TM2_3: delete (static_cast(busPtr)); break; + #ifndef WLED_NO_I2S0_PIXELBUS case I_32_I0_TM1_4: delete (static_cast(busPtr)); break; + case I_32_I0_TM2_3: delete (static_cast(busPtr)); break; #endif - #if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3) + #ifndef WLED_NO_I2S1_PIXELBUS case I_32_I1_TM1_4: delete (static_cast(busPtr)); break; + case I_32_I1_TM2_3: delete (static_cast(busPtr)); break; #endif #endif case I_HS_DOT_3: delete (static_cast(busPtr)); break; @@ -736,7 +874,9 @@ class PolyBus { #ifdef ESP8266 if (pins[0] == P_8266_HS_MOSI && pins[1] == P_8266_HS_CLK) isHSPI = true; #else - if(!num) isHSPI = true; // temporary hack to limit use of hardware SPI to a single SPI peripheral: only allow ESP32 hardware serial on segment 0 + // temporary hack to limit use of hardware SPI to a single SPI peripheral (HSPI): only allow ESP32 hardware serial on segment 0 + // SPI global variable is normally linked to VSPI on ESP32 (or FSPI C3, S3) + if (!num) isHSPI = true; #endif uint8_t t = I_NONE; switch (busType) { @@ -763,15 +903,27 @@ class PolyBus { return I_8266_U0_400_3 + offset; case TYPE_TM1814: return I_8266_U0_TM1_4 + offset; + case TYPE_TM1829: + return I_8266_U0_TM2_3 + offset; } #else //ESP32 uint8_t offset = 0; //0 = RMT (num 0-7) 8 = I2S0 9 = I2S1 - #ifndef CONFIG_IDF_TARGET_ESP32S2 + #if defined(CONFIG_IDF_TARGET_ESP32S2) + // ESP32-S2 only has 4 RMT channels + if (num > 4) return I_NONE; + if (num > 3) offset = 1; // only one I2S + #elif defined(CONFIG_IDF_TARGET_ESP32C3) + // On ESP32-C3 only the first 2 RMT channels are usable for transmitting + if (num > 1) return I_NONE; + //if (num > 1) offset = 1; // I2S not supported yet (only 1 I2S) + #elif defined(CONFIG_IDF_TARGET_ESP32S3) + // On ESP32-S3 only the first 4 RMT channels are usable for transmitting + if (num > 3) return I_NONE; + //if (num > 3) offset = num -4; // I2S not supported yet + #else + // standard ESP32 has 8 RMT and 2 I2S channels if (num > 9) return I_NONE; if (num > 7) offset = num -7; - #else //ESP32 S2 only has 4 RMT channels - if (num > 5) return I_NONE; - if (num > 4) offset = num -4; #endif switch (busType) { case TYPE_WS2812_RGB: @@ -783,6 +935,8 @@ class PolyBus { return I_32_RN_400_3 + offset; case TYPE_TM1814: return I_32_RN_TM1_4 + offset; + case TYPE_TM1829: + return I_32_RN_TM2_3 + offset; } #endif } diff --git a/wled00/button.cpp b/wled00/button.cpp index 2c433a344..b251a0c4a 100644 --- a/wled00/button.cpp +++ b/wled00/button.cpp @@ -18,7 +18,7 @@ void shortPressAction(uint8_t b) if (!macroButton[b]) { switch (b) { case 0: toggleOnOff(); stateUpdated(CALL_MODE_BUTTON); break; - case 1: ++effectCurrent %= strip.getModeCount(); colorUpdated(CALL_MODE_BUTTON); break; + case 1: ++effectCurrent %= strip.getModeCount(); stateChanged = true; colorUpdated(CALL_MODE_BUTTON); break; } } else { applyPreset(macroButton[b], CALL_MODE_BUTTON_PRESET); @@ -195,12 +195,12 @@ void handleAnalog(uint8_t b) colorHStoRGB(aRead*256,255,col); } else { // otherwise use "double press" for segment selection - WS2812FX::Segment& seg = strip.getSegment(macroDoublePress[b]); + Segment& seg = strip.getSegment(macroDoublePress[b]); if (aRead == 0) { - seg.setOption(SEG_OPTION_ON, 0); // off + seg.setOption(SEG_OPTION_ON, false); // off (use transition) } else { - seg.setOpacity(aRead, macroDoublePress[b]); - seg.setOption(SEG_OPTION_ON, 1); + seg.setOpacity(aRead); + seg.setOption(SEG_OPTION_ON, true); // on (use transition) } // this will notify clients of update (websockets,mqtt,etc) updateInterfaces(CALL_MODE_BUTTON); @@ -216,9 +216,13 @@ void handleAnalog(uint8_t b) void handleButton() { static unsigned long lastRead = 0UL; + static unsigned long lastRun = 0UL; bool analog = false; + unsigned long now = millis(); - if (strip.isUpdating()) return; // don't interfere with strip updates. Our button will still be there in 1ms (next cycle) + //if (strip.isUpdating()) return; // don't interfere with strip updates. Our button will still be there in 1ms (next cycle) + if (strip.isUpdating() && (millis() - lastRun < 400)) return; // be niced, but avoid button starvation + lastRun = millis(); for (uint8_t b=0; b ANALOG_BTN_READ_CYCLE) { // button is not a button but a potentiometer + if ((buttonType[b] == BTN_TYPE_ANALOG || buttonType[b] == BTN_TYPE_ANALOG_INVERTED) && now - lastRead > ANALOG_BTN_READ_CYCLE) { // button is not a button but a potentiometer analog = true; handleAnalog(b); continue; } @@ -242,21 +246,21 @@ void handleButton() //momentary button logic if (isButtonPressed(b)) { //pressed - if (!buttonPressedBefore[b]) buttonPressedTime[b] = millis(); + if (!buttonPressedBefore[b]) buttonPressedTime[b] = now; buttonPressedBefore[b] = true; - if (millis() - buttonPressedTime[b] > WLED_LONG_PRESS) { //long press + if (now - buttonPressedTime[b] > WLED_LONG_PRESS) { //long press if (!buttonLongPressed[b]) longPressAction(b); else if (b) { //repeatable action (~3 times per s) on button > 0 longPressAction(b); - buttonPressedTime[b] = millis() - WLED_LONG_REPEATED_ACTION; //300ms + buttonPressedTime[b] = now - WLED_LONG_REPEATED_ACTION; //333ms } buttonLongPressed[b] = true; } } else if (!isButtonPressed(b) && buttonPressedBefore[b]) { //released - long dur = millis() - buttonPressedTime[b]; + long dur = now - buttonPressedTime[b]; if (dur < WLED_DEBOUNCE_THRESHOLD) {buttonPressedBefore[b] = false; continue;} //too short "press", debounce bool doublePress = buttonWaitTime[b]; //did we have a short press before? buttonWaitTime[b] = 0; @@ -264,19 +268,22 @@ void handleButton() if (b == 0 && dur > WLED_LONG_AP) { // long press on button 0 (when released) if (dur > WLED_LONG_FACTORY_RESET) { // factory reset if pressed > 10 seconds WLED_FS.format(); + #ifdef WLED_ADD_EEPROM_SUPPORT clearEEPROM(); + #endif doReboot = true; } else { WLED::instance().initAP(true); } } else if (!buttonLongPressed[b]) { //short press + //NOTE: this interferes with double click handling in usermods so usermod needs to implement full button handling if (b != 1 && !macroDoublePress[b]) { //don't wait for double press on buttons without a default action if no double press macro set shortPressAction(b); } else { //double press if less than 350 ms between current press and previous short press release (buttonWaitTime!=0) if (doublePress) { doublePressAction(b); } else { - buttonWaitTime[b] = millis(); + buttonWaitTime[b] = now; } } } @@ -285,12 +292,12 @@ void handleButton() } //if 350ms elapsed since last short press release it is a short press - if (buttonWaitTime[b] && millis() - buttonWaitTime[b] > WLED_DOUBLE_PRESS && !buttonPressedBefore[b]) { + if (buttonWaitTime[b] && now - buttonWaitTime[b] > WLED_DOUBLE_PRESS && !buttonPressedBefore[b]) { buttonWaitTime[b] = 0; shortPressAction(b); } } - if (analog) lastRead = millis(); + if (analog) lastRead = now; } void handleIO() diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index f50181f6d..0f8575394 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -31,6 +31,9 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { getStringFromJson(cmDNS, id[F("mdns")], 33); getStringFromJson(serverDescription, id[F("name")], 33); getStringFromJson(alexaInvocationName, id[F("inv")], 33); +#ifdef WLED_ENABLE_SIMPLE_UI + CJSON(simplifiedUI, id[F("sui")]); +#endif JsonObject nw_ins_0 = doc["nw"]["ins"][0]; getStringFromJson(clientSSID, nw_ins_0[F("ssid")], 33); @@ -78,16 +81,53 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { // initialize LED pins and lengths prior to other HW (except for ethernet) JsonObject hw_led = hw["led"]; + uint8_t autoWhiteMode = RGBW_MODE_MANUAL_ONLY; CJSON(strip.ablMilliampsMax, hw_led[F("maxpwr")]); CJSON(strip.milliampsPerLed, hw_led[F("ledma")]); - CJSON(strip.autoWhiteMode, hw_led[F("rgbwm")]); - Bus::setAutoWhiteMode(strip.autoWhiteMode); - strip.fixInvalidSegments(); // refreshes segment light capabilities (in case auto white mode changed) + Bus::setAutoWhiteMode(hw_led[F("rgbwm")] | 255); CJSON(correctWB, hw_led["cct"]); CJSON(cctFromRgb, hw_led[F("cr")]); CJSON(strip.cctBlending, hw_led[F("cb")]); Bus::setCCTBlend(strip.cctBlending); strip.setTargetFps(hw_led["fps"]); //NOP if 0, default 42 FPS + CJSON(strip.useLedsArray, hw_led[F("ld")]); + + #ifndef WLED_DISABLE_2D + // 2D Matrix Settings + JsonObject matrix = hw_led[F("matrix")]; + if (!matrix.isNull()) { + strip.isMatrix = true; + CJSON(strip.panelH, matrix[F("ph")]); + CJSON(strip.panelW, matrix[F("pw")]); + CJSON(strip.hPanels, matrix[F("mph")]); + CJSON(strip.vPanels, matrix[F("mpv")]); + CJSON(strip.matrix.bottomStart, matrix[F("pb")]); + CJSON(strip.matrix.rightStart, matrix[F("pr")]); + CJSON(strip.matrix.vertical, matrix[F("pv")]); + CJSON(strip.matrix.serpentine, matrix[F("ps")]); + + JsonArray panels = matrix[F("panels")]; + uint8_t s = 0; + if (!panels.isNull()) { + for (JsonObject pnl : panels) { + CJSON(strip.panel[s].bottomStart, pnl["b"]); + CJSON(strip.panel[s].rightStart, pnl["r"]); + CJSON(strip.panel[s].vertical, pnl["v"]); + CJSON(strip.panel[s].serpentine, pnl["s"]); + if (++s >= WLED_MAX_PANELS) break; // max panels reached + } + } + // clear remaining panels + for (; s -1 && pinManager.allocatePin(pin, false, PinOwner::Button)) { btnPin[s] = pin; + #ifdef ESP32 + pinMode(btnPin[s], buttonType[s]==BTN_TYPE_PUSH_ACT_HIGH ? INPUT_PULLDOWN : INPUT_PULLUP); + #else pinMode(btnPin[s], INPUT_PULLUP); + #endif } else { btnPin[s] = -1; } @@ -226,6 +271,36 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { if (serialBaud < 96 || serialBaud > 15000) serialBaud = 1152; updateBaudRate(serialBaud *100); + JsonArray hw_if_i2c = hw[F("if")][F("i2c-pin")]; + CJSON(i2c_sda, hw_if_i2c[0]); + CJSON(i2c_scl, hw_if_i2c[1]); + PinManagerPinType i2c[2] = { { i2c_sda, true }, { i2c_scl, true } }; + if (i2c_scl >= 0 && i2c_sda >= 0 && pinManager.allocateMultiplePins(i2c, 2, PinOwner::HW_I2C)) { + #ifdef ESP32 + Wire.setPins(i2c_sda, i2c_scl); // this will fail if Wire is initilised (Wire.begin() called prior) + #endif + Wire.begin(); + } else { + i2c_sda = -1; + i2c_scl = -1; + } + JsonArray hw_if_spi = hw[F("if")][F("spi-pin")]; + CJSON(spi_mosi, hw_if_spi[0]); + CJSON(spi_sclk, hw_if_spi[1]); + CJSON(spi_miso, hw_if_spi[2]); + PinManagerPinType spi[3] = { { spi_mosi, true }, { spi_miso, true }, { spi_sclk, true } }; + if (spi_mosi >= 0 && spi_sclk >= 0 && pinManager.allocateMultiplePins(spi, 3, PinOwner::HW_SPI)) { + #ifdef ESP32 + SPI.begin(spi_sclk, spi_miso, spi_mosi); // SPI global uses VSPI on ESP32 and FSPI on C3, S3 + #else + SPI.begin(); + #endif + } else { + spi_mosi = -1; + spi_miso = -1; + spi_sclk = -1; + } + //int hw_status_pin = hw[F("status")]["pin"]; // -1 JsonObject light = doc[F("light")]; @@ -235,10 +310,10 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { float light_gc_bri = light["gc"]["bri"]; float light_gc_col = light["gc"]["col"]; // 2.8 - if (light_gc_bri > 1.5) strip.gammaCorrectBri = true; - else if (light_gc_bri > 0.5) strip.gammaCorrectBri = false; - if (light_gc_col > 1.5) strip.gammaCorrectCol = true; - else if (light_gc_col > 0.5) strip.gammaCorrectCol = false; + if (light_gc_bri > 1.5) gammaCorrectBri = true; + else if (light_gc_bri > 0.5) gammaCorrectBri = false; + if (light_gc_col > 1.5) gammaCorrectCol = true; + else if (light_gc_col > 0.5) gammaCorrectCol = false; JsonObject light_tr = light["tr"]; CJSON(fadeTransition, light_tr["mode"]); @@ -284,8 +359,9 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { CJSON(notifyAlexa, if_sync_send["va"]); CJSON(notifyHue, if_sync_send["hue"]); CJSON(notifyMacro, if_sync_send["macro"]); - CJSON(notifyTwice, if_sync_send[F("twice")]); CJSON(syncGroups, if_sync_send["grp"]); + if (if_sync_send[F("twice")]) udpNumRetries = 1; // import setting from 0.13 and earlier + CJSON(udpNumRetries, if_sync_send["ret"]); JsonObject if_nodes = interfaces["nodes"]; CJSON(nodeListEnabled, if_nodes[F("list")]); @@ -302,6 +378,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { CJSON(e131Universe, if_live_dmx[F("uni")]); CJSON(e131SkipOutOfSequence, if_live_dmx[F("seqskip")]); CJSON(DMXAddress, if_live_dmx[F("addr")]); + if (!DMXAddress || DMXAddress > 510) DMXAddress = 1; CJSON(DMXMode, if_live_dmx["mode"]); tdd = if_live[F("timeout")] | -1; @@ -467,21 +544,21 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { void deserializeConfigFromFS() { bool success = deserializeConfigSec(); if (!success) { //if file does not exist, try reading from EEPROM + #ifdef WLED_ADD_EEPROM_SUPPORT deEEPSettings(); return; + #endif } - #ifdef WLED_USE_DYNAMIC_JSON - DynamicJsonDocument doc(JSON_BUFFER_SIZE); - #else if (!requestJSONBufferLock(1)) return; - #endif DEBUG_PRINTLN(F("Reading settings from /cfg.json...")); success = readObjectFromFile("/cfg.json", nullptr, &doc); if (!success) { //if file does not exist, try reading from EEPROM + #ifdef WLED_ADD_EEPROM_SUPPORT deEEPSettings(); + #endif releaseJSONBufferLock(); return; } @@ -499,11 +576,7 @@ void serializeConfig() { DEBUG_PRINTLN(F("Writing settings to /cfg.json...")); - #ifdef WLED_USE_DYNAMIC_JSON - DynamicJsonDocument doc(JSON_BUFFER_SIZE); - #else if (!requestJSONBufferLock(2)) return; - #endif JsonArray rev = doc.createNestedArray("rev"); rev.add(1); //major settings revision @@ -515,6 +588,9 @@ void serializeConfig() { id[F("mdns")] = cmDNS; id[F("name")] = serverDescription; id[F("inv")] = alexaInvocationName; +#ifdef WLED_ENABLE_SIMPLE_UI + id[F("sui")] = simplifiedUI; +#endif JsonObject nw = doc.createNestedObject("nw"); @@ -585,7 +661,32 @@ void serializeConfig() { hw_led[F("cr")] = cctFromRgb; hw_led[F("cb")] = strip.cctBlending; hw_led["fps"] = strip.getTargetFps(); - hw_led[F("rgbwm")] = strip.autoWhiteMode; + hw_led[F("rgbwm")] = Bus::getAutoWhiteMode(); // global override + hw_led[F("ld")] = strip.useLedsArray; + + #ifndef WLED_DISABLE_2D + // 2D Matrix Settings + if (strip.isMatrix) { + JsonObject matrix = hw_led.createNestedObject(F("matrix")); + matrix[F("ph")] = strip.panelH; + matrix[F("pw")] = strip.panelW; + matrix[F("mph")] = strip.hPanels; + matrix[F("mpv")] = strip.vPanels; + matrix[F("pb")] = strip.matrix.bottomStart; + matrix[F("pr")] = strip.matrix.rightStart; + matrix[F("pv")] = strip.matrix.vertical; + matrix[F("ps")] = strip.matrix.serpentine; + + JsonArray panels = matrix.createNestedArray(F("panels")); + for (uint8_t i=0; iskippedLeds(); ins["type"] = bus->getType() & 0x7F; ins["ref"] = bus->isOffRefreshRequired(); - //ins[F("rgbw")] = bus->isRgbw(); + ins[F("rgbwm")] = bus->getAWMode(); } JsonArray hw_com = hw.createNestedArray(F("com")); @@ -650,6 +751,15 @@ void serializeConfig() { hw[F("baud")] = serialBaud; + JsonObject hw_if = hw.createNestedObject(F("if")); + JsonArray hw_if_i2c = hw_if.createNestedArray("i2c-pin"); + hw_if_i2c.add(i2c_sda); + hw_if_i2c.add(i2c_scl); + JsonArray hw_if_spi = hw_if.createNestedArray("spi-pin"); + hw_if_spi.add(spi_mosi); + hw_if_spi.add(spi_sclk); + hw_if_spi.add(spi_miso); + //JsonObject hw_status = hw.createNestedObject("status"); //hw_status["pin"] = -1; @@ -659,8 +769,8 @@ void serializeConfig() { light[F("aseg")] = autoSegments; JsonObject light_gc = light.createNestedObject("gc"); - light_gc["bri"] = (strip.gammaCorrectBri) ? 2.8 : 1.0; - light_gc["col"] = (strip.gammaCorrectCol) ? 2.8 : 1.0; + light_gc["bri"] = (gammaCorrectBri) ? 2.8 : 1.0; + light_gc["col"] = (gammaCorrectCol) ? 2.8 : 1.0; JsonObject light_tr = light.createNestedObject("tr"); light_tr["mode"] = fadeTransition; @@ -698,8 +808,8 @@ void serializeConfig() { if_sync_send["va"] = notifyAlexa; if_sync_send["hue"] = notifyHue; if_sync_send["macro"] = notifyMacro; - if_sync_send[F("twice")] = notifyTwice; if_sync_send["grp"] = syncGroups; + if_sync_send["ret"] = udpNumRetries; JsonObject if_nodes = interfaces.createNestedObject("nodes"); if_nodes[F("list")] = nodeListEnabled; @@ -842,17 +952,15 @@ void serializeConfig() { if (f) serializeJson(doc, f); f.close(); releaseJSONBufferLock(); + + doSerializeConfig = false; } //settings in /wsec.json, not accessible via webserver, for passwords and tokens bool deserializeConfigSec() { DEBUG_PRINTLN(F("Reading settings from /wsec.json...")); - #ifdef WLED_USE_DYNAMIC_JSON - DynamicJsonDocument doc(JSON_BUFFER_SIZE); - #else if (!requestJSONBufferLock(3)) return false; - #endif bool success = readObjectFromFile("/wsec.json", nullptr, &doc); if (!success) { @@ -884,6 +992,9 @@ bool deserializeConfigSec() { getStringFromJson(hueApiKey, interfaces["hue"][F("key")], 47); #endif + getStringFromJson(settingsPIN, doc["pin"], 5); + correctPIN = !strlen(settingsPIN); + JsonObject ota = doc["ota"]; getStringFromJson(otaPass, ota[F("pwd")], 33); CJSON(otaLock, ota[F("lock")]); @@ -897,11 +1008,7 @@ bool deserializeConfigSec() { void serializeConfigSec() { DEBUG_PRINTLN(F("Writing settings to /wsec.json...")); - #ifdef WLED_USE_DYNAMIC_JSON - DynamicJsonDocument doc(JSON_BUFFER_SIZE); - #else if (!requestJSONBufferLock(4)) return; - #endif JsonObject nw = doc.createNestedObject("nw"); @@ -927,6 +1034,8 @@ void serializeConfigSec() { if_hue[F("key")] = hueApiKey; #endif + doc["pin"] = settingsPIN; + JsonObject ota = doc.createNestedObject("ota"); ota[F("pwd")] = otaPass; ota[F("lock")] = otaLock; diff --git a/wled00/colors.cpp b/wled00/colors.cpp index 25cce032e..0b8c7811e 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -1,12 +1,57 @@ #include "wled.h" /* - * Color conversion methods + * Color conversion & utility methods */ +/* + * color blend function + */ +uint32_t color_blend(uint32_t color1, uint32_t color2, uint16_t blend, bool b16) { + if(blend == 0) return color1; + uint16_t blendmax = b16 ? 0xFFFF : 0xFF; + if(blend == blendmax) return color2; + uint8_t shift = b16 ? 16 : 8; + + uint32_t w1 = W(color1); + uint32_t r1 = R(color1); + uint32_t g1 = G(color1); + uint32_t b1 = B(color1); + + uint32_t w2 = W(color2); + uint32_t r2 = R(color2); + uint32_t g2 = G(color2); + uint32_t b2 = B(color2); + + uint32_t w3 = ((w2 * blend) + (w1 * (blendmax - blend))) >> shift; + uint32_t r3 = ((r2 * blend) + (r1 * (blendmax - blend))) >> shift; + uint32_t g3 = ((g2 * blend) + (g1 * (blendmax - blend))) >> shift; + uint32_t b3 = ((b2 * blend) + (b1 * (blendmax - blend))) >> shift; + + return RGBW32(r3, g3, b3, w3); +} + +/* + * color add function that preserves ratio + * idea: https://github.com/Aircoookie/WLED/pull/2465 by https://github.com/Proto-molecule + */ +uint32_t color_add(uint32_t c1, uint32_t c2) +{ + uint32_t r = R(c1) + R(c2); + uint32_t g = G(c1) + G(c2); + uint32_t b = B(c1) + B(c2); + uint32_t w = W(c1) + W(c2); + uint16_t max = r; + if (g > max) max = g; + if (b > max) max = b; + if (w > max) max = w; + if (max < 256) return RGBW32(r, g, b, w); + else return RGBW32(r * 255 / max, g * 255 / max, b * 255 / max, w * 255 / max); +} + void setRandomColor(byte* rgb) { - lastRandomIndex = strip.get_random_wheel_index(lastRandomIndex); + lastRandomIndex = strip.getMainSegment().get_random_wheel_index(lastRandomIndex); colorHStoRGB(lastRandomIndex*256,255,rgb); } @@ -274,3 +319,53 @@ uint16_t approximateKelvinFromRGB(uint32_t rgb) { return (k > 10091) ? 10091 : k; } } + +//gamma 2.8 lookup table used for color correction +static byte gammaT[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, + 2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5, + 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, + 10, 10, 11, 11, 11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16, + 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 24, 24, 25, + 25, 26, 27, 27, 28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 35, 36, + 37, 38, 39, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 50, + 51, 52, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 66, 67, 68, + 69, 70, 72, 73, 74, 75, 77, 78, 79, 81, 82, 83, 85, 86, 87, 89, + 90, 92, 93, 95, 96, 98, 99,101,102,104,105,107,109,110,112,114, + 115,117,119,120,122,124,126,127,129,131,133,135,137,138,140,142, + 144,146,148,150,152,154,156,158,160,162,164,167,169,171,173,175, + 177,180,182,184,186,189,191,193,196,198,200,203,205,208,210,213, + 215,218,220,223,225,228,231,233,236,239,241,244,247,249,252,255 }; + +uint8_t gamma8_cal(uint8_t b, float gamma) +{ + return (int)(powf((float)b / 255.0f, gamma) * 255.0f + 0.5f); +} + +void calcGammaTable(float gamma) +{ + for (uint16_t i = 0; i < 256; i++) { + gammaT[i] = gamma8_cal(i, gamma); + } +} + +uint8_t gamma8(uint8_t b) +{ + return gammaT[b]; +} + +uint32_t gamma32(uint32_t color) +{ + if (!gammaCorrectCol) return color; + uint8_t w = W(color); + uint8_t r = R(color); + uint8_t g = G(color); + uint8_t b = B(color); + w = gammaT[w]; + r = gammaT[r]; + g = gammaT[g]; + b = gammaT[b]; + return RGBW32(r, g, b, w); +} diff --git a/wled00/const.h b/wled00/const.h index 881920391..0c3905cfa 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -5,6 +5,8 @@ * Readability defines and their associated numerical values + compile-time constants */ +#define GRADIENT_PALETTE_COUNT 58 + //Defaults #define DEFAULT_CLIENT_SSID "Your_Network" #define DEFAULT_AP_PASS "wled1234" @@ -23,10 +25,22 @@ #ifdef ESP8266 #define WLED_MAX_BUSSES 3 #else - #ifdef CONFIG_IDF_TARGET_ESP32S2 - #define WLED_MAX_BUSSES 5 + #if defined(CONFIG_IDF_TARGET_ESP32C3) // 2 RMT, 6 LEDC, only has 1 I2S but NPB does not support it ATM + #define WLED_MAX_BUSSES 3 // will allow 2 digital & 1 analog (or the other way around) + #elif defined(CONFIG_IDF_TARGET_ESP32S2) // 4 RMT, 8 LEDC, only has 1 I2S bus, supported in NPB + #if defined(USERMOD_AUDIOREACTIVE) // requested by @softhack007 https://github.com/blazoncek/WLED/issues/33 + #define WLED_MAX_BUSSES 6 // will allow 4 digital & 2 analog + #else + #define WLED_MAX_BUSSES 7 // will allow 5 digital & 2 analog + #endif + #elif defined(CONFIG_IDF_TARGET_ESP32S3) // 4 RMT, 8 LEDC, has 2 I2S but NPB does not support them ATM + #define WLED_MAX_BUSSES 6 // will allow 4 digital & 2 analog #else - #define WLED_MAX_BUSSES 10 + #if defined(USERMOD_AUDIOREACTIVE) // requested by @softhack007 https://github.com/blazoncek/WLED/issues/33 + #define WLED_MAX_BUSSES 8 + #else + #define WLED_MAX_BUSSES 10 + #endif #endif #endif #endif @@ -77,6 +91,7 @@ #define USERMOD_ID_MY9291 28 //Usermod "usermod_MY9291.h" #define USERMOD_ID_SI7021_MQTT_HA 29 //Usermod "usermod_si7021_mqtt_ha.h" #define USERMOD_ID_BME280 30 //Usermod "usermod_bme280.h +#define USERMOD_ID_AUDIOREACTIVE 31 //Usermod "audioreactive.h" //Access point behavior #define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot @@ -151,6 +166,7 @@ #define TYPE_WS2812_RGB 22 #define TYPE_GS8608 23 //same driver as WS2812, but will require signal 2x per second (else displays test pattern) #define TYPE_WS2811_400KHZ 24 //half-speed WS2812 protocol, used by very old WS2811 units +#define TYPE_TM1829 25 #define TYPE_SK6812_RGBW 30 #define TYPE_TM1814 31 //"Analog" types (PWM) (32-47) @@ -168,8 +184,9 @@ #define TYPE_LPD6803 54 //Network types (master broadcast) (80-95) #define TYPE_NET_DDP_RGB 80 //network DDP RGB bus (master broadcast bus) -#define TYPE_NET_E131_RGB 81 //network E131 RGB bus (master broadcast bus) -#define TYPE_NET_ARTNET_RGB 82 //network ArtNet RGB bus (master broadcast bus) +#define TYPE_NET_E131_RGB 81 //network E131 RGB bus (master broadcast bus, unused) +#define TYPE_NET_ARTNET_RGB 82 //network ArtNet RGB bus (master broadcast bus, unused) +#define TYPE_NET_DDP_RGBW 88 //network DDP RGBW bus (master broadcast bus) #define IS_DIGITAL(t) ((t) & 0x10) //digital are 16-31 and 48-63 #define IS_PWM(t) ((t) > 40 && (t) < 46) @@ -222,9 +239,12 @@ #define SEG_OPTION_REVERSED 1 #define SEG_OPTION_ON 2 #define SEG_OPTION_MIRROR 3 //Indicates that the effect will be mirrored within the segment -#define SEG_OPTION_NONUNITY 4 //Indicates that the effect does not use FRAMETIME or needs getPixelColor -#define SEG_OPTION_FREEZE 5 //Segment contents will not be refreshed -#define SEG_OPTION_TRANSITIONAL 7 +#define SEG_OPTION_FREEZE 4 //Segment contents will not be refreshed +#define SEG_OPTION_RESET 5 //Segment runtime requires reset +#define SEG_OPTION_TRANSITIONAL 6 +#define SEG_OPTION_REVERSED_Y 7 +#define SEG_OPTION_MIRROR_Y 8 +#define SEG_OPTION_TRANSPOSED 9 //Segment differs return byte #define SEG_DIFFERS_BRI 0x01 @@ -241,6 +261,7 @@ // WLED Error modes #define ERR_NONE 0 // All good :) #define ERR_EEP_COMMIT 2 // Could not commit to EEPROM (wrong flash layout?) +#define ERR_NOBUF 3 // JSON buffer was not released in time, request cannot be handled at this time #define ERR_JSON 9 // JSON parsing failed (input too large?) #define ERR_FS_BEGIN 10 // Could not init filesystem (no partition?) #define ERR_FS_QUOTA 11 // The FS is full or the maximum file size is reached @@ -270,15 +291,19 @@ #endif #ifndef MAX_LED_MEMORY -#ifdef ESP8266 -#define MAX_LED_MEMORY 4000 -#else -#define MAX_LED_MEMORY 64000 -#endif + #ifdef ESP8266 + #define MAX_LED_MEMORY 4000 + #else + #if defined(ARDUINO_ARCH_ESP32S2) || defined(ARDUINO_ARCH_ESP32C3) + #define MAX_LED_MEMORY 32000 + #else + #define MAX_LED_MEMORY 64000 + #endif + #endif #endif #ifndef MAX_LEDS_PER_BUS -#define MAX_LEDS_PER_BUS 4096 +#define MAX_LEDS_PER_BUS 2048 // may not be enough for fast LEDs (i.e. APA102) #endif // string temp buffer (now stored in stack locally) @@ -299,9 +324,11 @@ #endif #ifndef ABL_MILLIAMPS_DEFAULT - #define ABL_MILLIAMPS_DEFAULT 850 // auto lower brightness to stay close to milliampere limit + #define ABL_MILLIAMPS_DEFAULT 850 // auto lower brightness to stay close to milliampere limit #else - #if ABL_MILLIAMPS_DEFAULT < 250 // make sure value is at least 250 + #if ABL_MILLIAMPS_DEFAULT == 0 // disable ABL + #elif ABL_MILLIAMPS_DEFAULT < 250 // make sure value is at least 250 + #warning "make sure value is at least 250" #define ABL_MILLIAMPS_DEFAULT 250 #endif #endif @@ -321,14 +348,11 @@ #ifdef ESP8266 #define JSON_BUFFER_SIZE 10240 #else - #define JSON_BUFFER_SIZE 20480 + #define JSON_BUFFER_SIZE 24576 #endif -#ifdef WLED_USE_DYNAMIC_JSON - #define MIN_HEAP_SIZE JSON_BUFFER_SIZE+512 -#else - #define MIN_HEAP_SIZE 4096 -#endif +//#define MIN_HEAP_SIZE (MAX_LED_MEMORY+2048) +#define MIN_HEAP_SIZE (8192) // Maximum size of node map (list of other WLED instances) #ifdef ESP8266 @@ -339,10 +363,10 @@ //this is merely a default now and can be changed at runtime #ifndef LEDPIN -#ifdef ESP8266 +#if defined(ESP8266) || (defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_PSRAM)) || defined(CONFIG_IDF_TARGET_ESP32C3) #define LEDPIN 2 // GPIO2 (D4) on Wemod D1 mini compatible boards #else - #define LEDPIN 2 // Changed from 16 to restore compatibility with ESP32-pico + #define LEDPIN 16 // aligns with GPIO2 (D4) on Wemos D1 mini32 compatible boards #endif #endif @@ -360,4 +384,43 @@ #define INTERFACE_UPDATE_COOLDOWN 2000 //time in ms to wait between websockets, alexa, and MQTT updates +#if defined(ESP8266) && defined(HW_PIN_SCL) + #undef HW_PIN_SCL +#endif +#if defined(ESP8266) && defined(HW_PIN_SDA) + #undef HW_PIN_SDA +#endif +#ifndef HW_PIN_SCL + #define HW_PIN_SCL SCL +#endif +#ifndef HW_PIN_SDA + #define HW_PIN_SDA SDA +#endif + +#if defined(ESP8266) && defined(HW_PIN_CLOCKSPI) + #undef HW_PIN_CLOCKSPI +#endif +#if defined(ESP8266) && defined(HW_PIN_DATASPI) + #undef HW_PIN_DATASPI +#endif +#if defined(ESP8266) && defined(HW_PIN_MISOSPI) + #undef HW_PIN_MISOSPI +#endif +#if defined(ESP8266) && defined(HW_PIN_CSSPI) + #undef HW_PIN_CSSPI +#endif +// defaults for VSPI +#ifndef HW_PIN_CLOCKSPI + #define HW_PIN_CLOCKSPI SCK +#endif +#ifndef HW_PIN_DATASPI + #define HW_PIN_DATASPI MOSI +#endif +#ifndef HW_PIN_MISOSPI + #define HW_PIN_MISOSPI MISO +#endif +#ifndef HW_PIN_CSSPI + #define HW_PIN_CSSPI SS +#endif + #endif diff --git a/wled00/data/index.css b/wled00/data/index.css index 863528cea..79a15993e 100644 --- a/wled00/data/index.css +++ b/wled00/data/index.css @@ -16,7 +16,10 @@ --c-c: #ccc; --c-e: #eee; --c-d: #ddd; - --c-r: #831; + --c-r: #c32; + --c-g: #2c1; + --c-l: #48a; + --c-y: #a90; --t-b: 0.5; --c-o: rgba(34, 34, 34, 0.9); --c-tb : rgba(34, 34, 34, var(--t-b)); @@ -29,6 +32,7 @@ --tbp: 14px 14px 10px 14px; --bbp: 9px 0 7px 0; --bhd: none; + --sgp: "block"; --bmt: 0px; } @@ -44,8 +48,10 @@ body { color: var(--c-f); text-align: center; -webkit-touch-callout: none; - -webkit-user-select: none; - user-select: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; -webkit-tap-highlight-color: transparent; scrollbar-width: 6px; scrollbar-color: var(--c-sb) transparent; @@ -73,8 +79,13 @@ body { p { margin: 10px 0 2px 0; +} +a, p, a:visited { color: var(--c-d); } +a, a:visited { + text-decoration: none; +} button { outline: none; @@ -89,10 +100,11 @@ button { #namelabel { position: fixed; bottom: calc(var(--bh) + 6px); - right: 4px; - color: var(--c-6); + right: 6px; + color: var(--c-8); /* should remain bright (--c-d) with dark shadow (see below) to be legible on gray background */ cursor: pointer; writing-mode: vertical-rl; + /* transform: rotate(180deg); */ } .bri { @@ -111,9 +123,20 @@ button { .icons { font-family: 'WIcons'; font-style: normal; - font-size: 24px; - line-height: 1; + font-size: 24px !important; + line-height: 1 !important; display: inline-block; +} + +.icons.on { + color: var(--c-g); +} + +.icons.off { + color: var(--c-6); +} + +.top .icons, .bot .icons { margin: -2px 0 4px 0; } @@ -121,75 +144,79 @@ button { font-size: 42px; } -.infot { +.segt, .plentry TABLE { table-layout: fixed; width: 100%; } -.segtd { +.segt TD { + padding: 2px !important; text-align: center; - text-transform: uppercase; - font-size: 14px; + /*text-transform: uppercase;*/ +} +.segt TD, .plentry TD { + font-size: 13px; + padding: 0; + vertical-align: middle; } .keytd { text-align: left; - padding-bottom: 8px; } .valtd { text-align: right; - padding-bottom: 8px; } -.slider-icon -{ - transform: translate(6px,3px); - color: var(--c-d); +.valtd i { + font-size: small; } -.e-icon -{ +.slider-icon { + transform: translate(3px,3px); +} + +.e-icon { transform: translateY(3px); - color: var(--c-d); } .sel-icon { transform: translateX(3px); - color: var(--c-d); } -.edit-icon { - position: absolute; - right: -26px; - top: 10px; - display: none; +.e-icon, .sel-icon, .slider-icon { + cursor: pointer; + color: var(--c-d); } .search-icon { position: absolute; - left: 8px; - top: 10px; - pointer-events: none; + top: 8px; + left: 12px; + /*pointer-events: none;*/ + width: 24px; + height: 24px; } -.search-cancel-icon { +.clear-icon { position: absolute; - right: 8px; - top: 9px; - cursor: pointer; display: none; + top: 8px; + right: 9px; + cursor: pointer; } .flr { - float: right; - cursor: pointer; - margin: 0; color: var(--c-f); transform: rotate(0deg); transition: transform 0.3s; + position: absolute; + top: 0; + right: 0; + padding: 8px; } +.expanded .flr, .exp { transform: rotate(180deg); } @@ -206,6 +233,17 @@ button { border: 0px; } +#liveview2D { + height: 90%; + display: none; + width: 90%; + border: 0px; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%,-50%); +} + .tab { background-color: transparent; color: var(--c-d); @@ -226,15 +264,18 @@ button { transition: color 0.3s, background-color 0.3s; font-size: 17px; color: var(--c-c); + min-width: 44px; } .top button { padding: var(--tbp); + margin: 0; } .bot button { padding: var(--bbp); width:25%; + margin: 0; } .tab button:hover { @@ -267,7 +308,6 @@ button { position: relative; width: 100%; width: calc(100%/var(--n)); - padding: 11px 0; box-sizing: border-box; border: 0px; overflow: auto; @@ -276,12 +316,30 @@ button { } #Effects { - padding-top: 0; - margin-top: 11px; - height: calc(100% - 11px); -webkit-overflow-scrolling: touch; } +#segutil, #segutil2, #segcont, #putil, #pcont, #pql { + width: 280px; +} + +#putil, #segutil, #segutil2 { + min-height: 42px; + margin: 13px auto 0; +} + +#segutil .segin { + padding-top: 12px; +} + +#pql, #segcont, #pcont { + margin: 0 auto; +} + +#putil { + padding: 5px 0 0; +} + .smooth { transition: transform calc(var(--f, 1)*.5s) ease-out } .tab-label { @@ -307,26 +365,113 @@ button { pointer-events: none; } -.staytop { +.staytop, .staybot { display: block; position: -webkit-sticky; + position: sticky !important; + top: 0; + z-index: 2; + margin: 0 auto auto; +} + +.staybot { + bottom: 5px; +} + +#sliders { + width: 300px; + margin: 0 auto; position: sticky; - background: var(--c-1); - top: -1px; + bottom: 0; +} + +#sliders .labels { + padding-top: 3px; + font-size: small; +} + +.slider { + background-color: var(--c-2); + max-width: 300px; + min-width: 280px; + margin: 0 auto; /* add 5px; if you want some vertical space but looks ugly */ + border-radius: 24px; + position: relative; +} + +.filter, .option { + background-color: var(--c-4); + border-radius: 26px; + height: 26px; + margin: 0 auto; /* add 4-8px if you want space at the bottom */ + padding: 4px 2px; + position: relative; z-index: 1; - margin-top: 1px; - width: 272px; - margin: auto; - border-radius: 25px; + opacity: 1; + transition: opacity 0.5s linear, height 0.5s, transform 0.5s; + transform: scaleY(1); } -#staytop1 { - top: 28px; +.option { + z-index: unset; } -#fxb0 { - margin-bottom: 2px; - filter: drop-shadow(0 0 1px #000); +/* Tooltip text */ +.slider .tooltiptext, .option .tooltiptext { + visibility: hidden; + background-color: var(--c-5); + /*border: 2px solid var(--c-2);*/ + box-shadow: 4px 4px 10px 4px var(--c-1); + color: var(--c-f); + text-align: center; + padding: 5px 10px; + border-radius: 6px; + + /* Position the tooltip text */ + width: 160px; + position: absolute; + z-index: 1; + bottom: 100%; + left: 50%; + margin-left: -92px; + + /* Fade in tooltip */ + opacity: 0; + transition: opacity 0.75s; +} +.option .tooltiptext { + bottom: 120%; +} +/* Tooltip arrow */ +.slider .tooltiptext::after, .option .tooltiptext::after { + content: ""; + position: absolute; + top: 100%; + left: 50%; + margin-left: -5px; + border-width: 5px; + border-style: solid; + border-color: var(--c-5) transparent transparent transparent; +} +/* Show the tooltip text when you mouse over the tooltip container */ +.slider:hover .tooltiptext, .option .check:hover .tooltiptext { + visibility: visible; + opacity: 1; +} + +#pql, .edit-icon { + display: none; +} + +.hide { + display: none !important; +} +.fade { + visibility: hidden; /* hide it */ + opacity: 0; /* make it transparent */ + transform: scaleY(0); /* shrink content */ + height: 0px; /* force other elements to move */ + padding: 0; /* remove empty space */ } .first { @@ -336,11 +481,12 @@ button { #toast { opacity: 0; background-color: var(--c-5); + border: 1px solid var(--c-2); max-width: 90%; color: var(--c-f); text-align: center; border-radius: 5px; - padding: 16px; + padding: 22px; position: fixed; z-index: 5; left: 50%; @@ -375,26 +521,24 @@ button { overflow: auto; } -.modal button:hover { - background-color: var(--c-4); +.close { + position: sticky; + top: 0; + float: right; } -#info { +#info, #nodes { + z-index: 4; +} + +#rover { z-index: 3; } -#rover, #nodes { - z-index: 2; -} - #ndlt { margin: 12px 0; } -.valtd i { - font-size: 14px; -} - #roverstar { position: fixed; top: calc(var(--th) + 5px); @@ -413,20 +557,45 @@ button { z-index: -2; } -#imgw { - display: inline-block; - margin: 8px; +#info .slider { + max-width: 200px; + min-width: 145px; + float: right; + margin: 0; +} +#info .sliderwrap { + width: 200px; } -#kv, #kn { +#info table, #nodes table { + table-layout: fixed; + width: 100%; +} + +#info td, #nodes td { + padding-bottom: 8px; +} + +#info .btn { + margin: 5px; +} +#info table .btn, #nodes table .btn { + margin: 0; +} +#info div, #nodes div { max-width: 490px; + margin: 0 auto; +} + +#imgw { + /*display: inline-block;*/ + margin: 8px auto; +} +/* +#kv, #kn { display: inline-block; } - -#kn td { - padding-bottom: 12px; -} - +*/ #lv { max-width: 600px; display: inline-block; @@ -457,44 +626,52 @@ img { .sliderdisplay { content:''; position: absolute; - top: 12.5px; bottom: 12.5px; - left: 13px; right: 13px; + top: 12px; left: 8px; right: 8px; + height: 5px; background: var(--c-4); - border-radius: 17px; + border-radius: 16px; pointer-events: none; z-index: -1; --bg: var(--c-f); } -#rwrap .sliderdisplay { --bg: #f00; } -#gwrap .sliderdisplay { --bg: #0f0; } -#bwrap .sliderdisplay { --bg: #00f; } -#wbal .sliderdisplay, #kwrap .sliderdisplay { - background: linear-gradient(90deg, #ff8f1f 0%, #fff 50%, #cbdbff); +#rwrap .sliderdisplay { --bg: none; background: linear-gradient(90deg, #000 -15%, #f00); } /* -15% since #000 is too dark */ +#gwrap .sliderdisplay { --bg: none; background: linear-gradient(90deg, #000 -15%, #0f0); } /* -15% since #000 is too dark */ +#bwrap .sliderdisplay { --bg: none; background: linear-gradient(90deg, #000 -15%, #00f); } /* -15% since #000 is too dark */ +#wwrap .sliderdisplay { --bg: none; background: linear-gradient(90deg, #000 -15%, #fff); } /* -15% since #000 is too dark */ +#kwrap .sliderdisplay, +#wbal .sliderdisplay { background: linear-gradient(90deg, #ff8f1f 0%, #fff 50%, #cbdbff); } + +/* wrapper divs hidden by default */ +#rgbwrap, #kwrap, #wwrap, #wbal, #qcs-w, #hexw { + display: none; } .sliderbubble { - width: 36px; - line-height: 24px; + width: 24px; + position: relative; + display: inline-block; + border-radius: 10px; background: var(--c-3); - position: absolute; - transform: translateX(-50%); - border-radius: 12px; - margin-left: 12px; - margin-top: 3px; - padding: 0px; - display: inline; + color: var(--c-f); + padding: 4px 4px 2px; + font-size: 14px; + right: 3px; + transition: visibility 0.25s ease, opacity 0.25s ease; + opacity: 0; + visibility: hidden; } -.hidden { - display: none; +output.sliderbubbleshow { + visibility: visible; + opacity: 1; } input[type=range] { -webkit-appearance: none; - width: 220px; - padding: 0px; - margin: 0px 10px 0px 10px; + width: 100%; + padding: 0; + margin: 0; background-color: transparent; cursor: pointer; } @@ -511,7 +688,7 @@ input[type=range]::-webkit-slider-runnable-track { input[type=range]::-webkit-slider-thumb { height: 16px; width: 16px; - border-radius: 17px; + border-radius: 50%; background: var(--c-f); cursor: pointer; -webkit-appearance: none; @@ -526,34 +703,31 @@ input[type=range]::-moz-range-thumb { border: 0px solid rgba(0, 0, 0, 0); height: 16px; width: 16px; - border-radius: 17px; + border-radius: 50%; background: var(--c-f); - transform: translateY(7px); + transform: translateY(5px); } -input[type=range]:active + .sliderbubble { - display: inline; - transform: translateX(-50%); +#Colors input[type=range]::-webkit-slider-thumb { + height: 18px; + width: 18px; + border: 2px solid var(--c-1); + margin-top: 5px; } -/* hide color controls until enabled in updateUI() */ -#pwrap, #wwrap, #wbal, #rgbwrap, #palwrap { - display: none; +#Colors input[type=range]::-moz-range-thumb { + border: 2px solid var(--c-1); } /* Slider wrapper div */ .sliderwrap { height: 30px; - width: 240px; + width: 230px; position: relative; + z-index: 0; } -/* Segment power button + brightness slider wrapper div */ -.sbs { - margin: 0px -20px 5px -6px; -} - -/* Segment brightness slider wrapper div */ -.sws { - margin-left: -7px; +#Colors .sliderwrap { + width: 260px; + margin: 10px 0 0; } /* Dynamically hide brightness slider label */ @@ -566,188 +740,195 @@ input[type=range]:active + .sliderbubble { margin-top: var(--bmt); } -#picker { - margin: 10px auto; +#picker, #rgbwrap, #kwrap, #wwrap, #wbal, #vwrap, #qcs-w, #hexw, #pall { + margin: 0 auto; width: 260px; } +#picker { + margin-top: 10px; +} + +/* buttons */ .btn { padding: 8px; - margin: 10px; + margin: 10px 4px; width: 230px; font-size: 19px; - background-color: var(--c-3); - color: var(--c-f); + color: var(--c-d); cursor: pointer; - border: 0px solid white; border-radius: 25px; - transition-duration: 0.5s; - /*-webkit-backface-visibility: hidden; - -webkit-transform:translate3d(0,0,0);*/ + transition-duration: 0.3s; + -webkit-backface-visibility: hidden; + -webkit-transform:translate3d(0,0,0); + backface-visibility: hidden; + transform:translate3d(0,0,0); + overflow: clip; + text-overflow: clip; + border: 1px solid var(--c-3); + background-color: var(--c-3); } - -/* Small round button (color selectors, icon-only round buttons) */ -.xxs { - width: 40px; - height: 40px; - margin: 6px; +#segutil .btn-s:hover, +#segutil2 .btn-s:hover, +#putil .btn-s:hover, +.btn:hover { + border: 1px solid var(--c-5) /*!important*/; + background-color: var(--c-5) /*!important*/; } - -/* Segments/presets auxiliary buttons (Add segment, Create preset, ...) */ .btn-s { - width: 276px; - background-color: var(--c-2); -} -.btn-i { - padding-bottom: 4px; + width: 100%; + margin: 0; } .btn-icon { - margin: 0px 8px 4px 0; + margin: -4px 4px 0 0; vertical-align: middle; + display: inline-block; +} +.btn-n { + width: 230px; + margin: 0 8px 0 0; } - -/* Wide button used in presets (Save, playlist test, delete) */ .btn-p { - width: 216px; + width: 120px; + margin: 5px 0; +} +.btn-xs, .btn-pl-del, .btn-pl-add { + width: 42px !important; + height: 42px !important; +} +.btn-xs { + margin: 2px 0 0 0; +} +#putil .btn-xs { + margin: 0; +} +#info .btn-xs { + border: 1px solid var(--c-4); } -/* Delete preset from playlist button */ -.btn-pl-del { - margin: 0 0 0 3px; +#putil .btn-s { + width: 135px; } -/* Add preset to playlist "+" button */ -.btn-pl-add { - margin: 3px 0 0 8px; +#nodes .infobtn { + margin: 0; } -/* Quick color select buttons wrapper div */ +#segutil .btn-s, #segutil2 .btn-s, #putil .btn-s { + background-color: var(--c-3); + border: 1px solid var(--c-3); +} + +.btn-pl-del, .btn-pl-add { + margin: 0; + white-space: nowrap; +} + +/* Quick color select wrapper div */ #qcs-w { margin-top: 10px; - display: none; } /* Quick color select buttons */ .qcs { - padding: 14px; margin: 2px; border-radius: 14px; display: inline-block; + width: 28px; + height: 28px; + line-height: 28px; } /* Quick color select Black button (has white border) */ .qcsb { - padding: 13px; - border: 1px solid var(--c-f); + width: 26px; + height: 26px; + line-height: 26px; + border: 1px solid #fff; } /* Hex color input wrapper div */ #hexw { margin-top: 5px; - display: none; -} - -/* Transition time input */ -#tt { - text-align: center; -} - -/* Color slot select buttons (1,2,3) */ -.cl { - width: 38.5px; - height: 38.5px; - margin: 7px; - background-color: #000; - box-shadow: 0 0 0 1.5px #fff; -} -.selected.cl { - box-shadow: 0 0 0 5px #fff; -} - -/* Playlist preset select */ -.sel-pl { - width: 192px; - background-position: 168px 16px; - margin: 8px 3px 0 0; -} - -/* Playlist end preset select */ -.sel-ple { - width: 216px; - background-position: 192px 16px; } select { - -webkit-appearance: none; - appearance: none; - background: url("data:image/svg+xml;utf8,") no-repeat; - background-size: 12px; - background-position: 206px 16px; - padding-left: 12px !important; - background-repeat: no-repeat; - outline: none; + padding: 4px 8px; + margin: 0; + font-size: 19px; + background-color: var(--c-3); + color: var(--c-d); + cursor: pointer; + border: 0 solid var(--c-2); + border-radius: 20px; + transition-duration: 0.5s; + -webkit-backface-visibility: hidden; + -webkit-transform:translate3d(0,0,0); + -webkit-appearance: none; + -moz-appearance: none; + backface-visibility: hidden; + transform:translate3d(0,0,0); + text-overflow: ellipsis; } -select:-moz-focusring { - transition-duration: 0s; - color: transparent; - text-shadow: 0 0 0 #fff; +#tt { + text-align: center; +} +.cl { + background-color: #000; +} +select.sel-p, select.sel-pl, select.sel-ple { + margin: 5px 0; + width: 100%; + height: 40px; +} +div.sel-p { + position: relative; +} +div.sel-p:after { + content: ""; + position: absolute; + right: 10px; + top: 22px; + width: 0; + height: 0; + border-left: 8px solid transparent; + border-right: 8px solid transparent; + border-top: 8px solid var(--c-f); +} +select.sel-ple { + text-align: center; } option { background-color: var(--c-3); color: var(--c-f); } - -input[type=number], input[type=text] { +input[type=number], +input[type=text] { background: var(--c-3); color: var(--c-f); - border: 0px solid white; - border-radius: 25px; + border: 0px solid var(--c-2); + border-radius: 5px; padding: 8px; - margin: 6px 6px 6px 0; + /*margin: 6px 6px 6px 0;*/ font-size: 19px; transition: background-color 0.2s; outline: none; - width: 50px; -webkit-appearance: textfield; - appearance: textfield; + -moz-appearance: textfield; + appearance: textfield; } -textarea { - background: var(--c-2); - color: var(--c-f); - width: 236px; - height: 90px; - border-radius: 5px; - border: 2px solid #555; - outline: none; - resize: none; - font-size: 19px; -} - -::selection { - background: var(--c-b); +input[type=number] { + text-align: right; + width: 50px; } input[type=text] { - width: 100px; text-align: center; } -input[type=text].ptxt { - width: 200px; - margin: 26px 0 6px 12px; -} - -input[type=text].stxt { - display: none; - margin-top: 6px; -} - -input[type=text].qltxt { - width: 50px; -} - -input[type=number]:focus, input[type=text]:focus { +input[type=number]:focus, +input[type=text]:focus { background: var(--c-6); } @@ -756,52 +937,68 @@ input[type=number]::-webkit-outer-spin-button { -webkit-appearance: none; } -.pln { - width: 67px !important; - margin: 0 2px 8px 0 !important; - text-align: center; -} -.plnl { - width: 86px; - margin: 0 2px 0 0; - display: inline-block; -} -.pli { - width: 38px; - margin: 0 0 0 29px; - display: inline-block; +#hexw input[type=text] { + width: 6em; } -.segn { - border-radius: 5px !important; - margin: 3px 0 6px 0 !important; +input[type=text].ptxt { + width: calc(100% - 24px); } -.pname, .plname, .segname { - position: absolute; - left: 50%; - transform: translateX(-50%); +textarea { + background: var(--c-2); + color: var(--c-f); + width: calc(100% - 14px); /* +padding=260px */ + height: 90px; + border-radius: 5px; + border: 2px solid var(--c-5); + outline: none; + resize: none; + font-size: 19px; + padding: 5px; +} + +.apitxt { + height: 7em; +} + +::selection { + background: var(--c-b); +} + +.ptxt { + margin: 0 4px 4px !important; + display: none; +} + +.stxt { + width: 50px !important; +} + +.segname, .pname { white-space: nowrap; cursor: pointer; -} - -.segntxt { - max-width: 160px; - overflow: hidden; - text-overflow: clip; -} - -.segname { - top: 0px; - padding: 9px 0; -} -.pname, .plname { - width: 208px; - padding: 8px 0; text-align: center; - overflow: hidden; - text-overflow: clip; + overflow: clip; + text-overflow: ellipsis; + line-height: 24px; + padding: 8px 24px; + max-width: 170px; + margin: 0 auto; + position: relative; } + +.segname .flr, .pname .flr { + transform: rotate(0deg); + right: -6px; +} + +/* segment power wrapper */ +.sbs { + padding: 4px 0 4px 8px; + display: var(--sgp); +} + .pname { top: 1px; } @@ -809,13 +1006,12 @@ input[type=number]::-webkit-outer-spin-button { top:0; } +/* preset id number */ .pid { position: absolute; - top: 0px; - left: 0px; - padding: 11px 0px 0px 11px; + top: 8px; + left: 12px; font-size: 16px; - width: 20px; text-align: center; color: var(--c-b); } @@ -828,29 +1024,39 @@ input[type=number]::-webkit-outer-spin-button { padding: 6px 0 0 0; } -/* Quick preset select buttons */ +.xxs { + width: 44px; + height: 44px; + margin: 5px; +} + +.xxs-w { + margin: 2px; + width: 50px; + height: 50px; +} + +#csl .xxs { + border: 2px solid var(--c-d) !important; + /*box-shadow: 0 0 0 2px var(--c-d);*/ +} +#csl .xxs-w { + border-width: 5px !important; + /*box-shadow: 0 0 0 5px var(--c-d);*/ +} + +.qcs/*, #namelabel*/ { /* text shadow for name to be legible on grey backround */ + text-shadow: -1px -1px 0 var(--c-4), 1px -1px 0 var(--c-4), -1px 1px 0 var(--c-4), 1px 1px 0 var(--c-4); +} + .psts { - background-color: var(--c-3); color: var(--c-f); - cursor: pointer; - padding: 2px 0 0 0; - height: 40px; + margin: 4px; } -/* Segment apply button (checkmark) */ -.cnf { - color: var(--c-f); - cursor: pointer; - background: var(--c-3); - border-radius: 5px; - padding: 8.5px 21px 5px; - display: inline; -} - -/* Segment power button icon */ .pwr { color: var(--c-6); - transform: translate(2px, 3px); + transform: translate(1px, 1px); cursor: pointer; } @@ -865,42 +1071,34 @@ input[type=number]::-webkit-outer-spin-button { } .frz { - left: 36px; + left: 32px; position: absolute; - top: 0px; + top: -3px; cursor: pointer; padding: 8px; + z-index: 1; } -/* TODO expanded does not seem to apply to .frz, what is this for? */ .expanded .frz { display: none; } +/* radiobuttons and checkmarks */ .check, .radio { - display: inline-block; + display: block; position: relative; - padding-bottom: 32px; - margin-bottom: 14px; cursor: pointer; - text-align: center; -} - -.schkl { - padding: 2px 5px 0px 35px; - margin: 0 0 0 2px; } .revchkl { - padding: 2px 0px 0px 35px; + padding: 4px 0px 0px 35px; margin-bottom: 0px; margin-top: 8px; } -.fxchkl { - position: absolute; - top: 0px; - left: 8px; +TD .revchkl { + padding: 0 0 0 32px; + margin-top: 0; } .check input, .radio input { @@ -913,82 +1111,69 @@ input[type=number]::-webkit-outer-spin-button { .checkmark, .radiomark { position: absolute; - left: 0; height: 24px; width: 24px; - background-color: var(--c-4); - border-radius: 10px; - /*border: 1px solid var(--c-2);*/ -} - -.checkmark { - top: 6px; + top: 0; + bottom: 0; + left: 0; + background-color: var(--c-3); + border: 1px solid var(--c-2); } .radiomark { + top: 8px; + left: 8px; + height: 22px; + width: 22px; border-radius: 50%; background-color: transparent; - top: 7px; } -.schk { - top: 0; +.checkmark { + border-radius: 10px; } -.psv { - left: initial; - bottom: initial; - top: 0; - right: 0; -} - -.psvl { - padding: 2px 35px 10px 0px; - margin-top: 10px; - margin-bottom: 0px; -} - - +.radio:hover input ~ .radiomark, .check:hover input ~ .checkmark { background-color: var(--c-5); } -.check input:checked ~ .checkmark { - background-color: var(--c-6); -} - .checkmark:after, .radiomark:after { content: ""; position: absolute; display: none; } -.check input:checked ~ .checkmark:after, .radio input:checked ~ .radiomark:after { - display: block; -} - .check .checkmark:after { - left: 8px; + left: 9px; top: 4px; width: 5px; height: 10px; border: solid var(--c-f); border-width: 0 3px 3px 0; +} + +.rot45, +.check .checkmark:after { -webkit-transform: rotate(45deg); -ms-transform: rotate(45deg); transform: rotate(45deg); } .radio .radiomark:after { - width: 12px; - height: 12px; + width: 14px; + height: 14px; top: 50%; left: 50%; - margin: -6px; + margin: -7px; border-radius: 50%; background: var(--c-f); } +TD .checkmark, TD .radiomark { + top: -6px; +} + .h { font-size: 13px; text-align: center; @@ -999,128 +1184,218 @@ input[type=number]::-webkit-outer-spin-button { margin-bottom: 5px; } +/* segment & preset wrapper */ .seg, .pres { - position: relative; - display: inline-block; - padding: 8px; - margin: 10px; - width: 260px; - font-size: 19px; background-color: var(--c-2); color: var(--c-f); - border: 0px solid white; - border-radius: 20px; + border: 0px solid var(--c-f); text-align: left; transition: background-color 0.5s; - filter: brightness(1); /* required for slider background to render? */ + /*filter: brightness(1);*/ + font-size: 19px; + border-radius: 21px; + /*min-width: 264px;*/ } -.selected { - background-color: var(--c-4); +.seg { + top: auto !important; /* prevent sticky */ + bottom: auto !important; } -/* "selected" CSS class is applied to the segment when it is the main segment. - By default, do not highlight. Can be overridden by skin.css */ -.selected.seg { - background-color: var(--c-2); /* var(--c-4); */ +/* checkmark labels */ +.seg .schkl { + position: absolute; + top: 7px; + left: 9px; } -.selected .checkmark, .selected .radiokmark { - background-color: var(--c-4); /* var(--c-6); */ +/* checkmark labels */ +.filter .fchkl, .option .ochkl { + display: inline-block; + min-width: 0.7em; + padding: 1px 4px 4px 32px; + text-align: left; + line-height: 24px; + vertical-align: middle; + -webkit-filter: grayscale(100%); /* Safari 6.0 - 9.0 */ + filter: grayscale(100%); } +.lbl-l { + font-size: 13px; + text-align: center; + padding: 4px 0; +} + +.lbl-s { + display: inline-block; + /* margin: 10px 4px 0 0; */ + margin-top: 6px; + font-size: 13px; + width: 48%; + text-align: center; +} + +/* list wrapper */ .list { position: relative; + width: 280px; transition: background-color 0.5s; - margin: auto auto 10px; - padding-bottom: 10px; - width: 230px; + margin: auto auto 20px; + font-size: 19px; + line-height: 24px; } +/* list item */ .lstI { - position: sticky; + align-items: center; + cursor: pointer; + background-color: var(--c-2); overflow: hidden; + position: sticky; + border-radius: 21px; + margin: 13px auto 0; + min-height: 40px; + border: 1px solid var(--c-2); } -.fxbtn { - margin: 20px auto; - padding: 8px 0; +#segutil .lstI { + margin-top: 0; } -.lstI:hover { +/* selected item/element */ +.selected { /* has to be after .lstI since !important is not ok */ background: var(--c-4); } -.lstI:last-child { - border: none; +#segcont .seg:hover:not([class*="expanded"]), +.lstI:hover:not([class*="expanded"]) { + background: var(--c-5); } -.lstI.sticky, .lstI.selected { - z-index: 1; -} - -#pallist .lstI.selected { - top: 27px; - bottom: -11px; -} - -#pallist .lstI.sticky { - top: -11px; -} - -.lstI.selected { - background: var(--c-5); - top: 95px; - bottom: -11px; -} - -.lstI.sticky { - top: 57px; -} - -.lstIname { - margin: 3px 0; - white-space: nowrap; - cursor: pointer; - font-size: 19px; -} - -.lstIprev { - width: 220px; - height: 5px; - margin: auto; - position: absolute; - bottom: 0px; - left: 5px; -} - -input[type="text"].search { - display: block; - width: 230px; - box-sizing: border-box; - padding: 8px 8px 9px 38px; - margin: 6px auto 0 auto; - text-align: left; +.selected .checkmark, +.selected .radiomark, +.selected input[type=number], +.selected input[type=text] { background-color: var(--c-3); } -input[type="text"].search:focus { +/* selected list item */ +.lstI.selected { + top: 0; + bottom: 0; +} + +.lstI.sticky, +.lstI.selected { + z-index: 1; + box-shadow: 0px 0px 10px 4px var(--c-1); +} + +#pcont .selected:not([class*="expanded"]) { + bottom: 52px; + top: 42px; +} + +#fxlist .lstI.selected { + top: 84px; +} + +#fxlist .lstI.sticky { + top: 42px; +} + +#pallist .lstI.selected { + top: 84px; +} + +#pallist .lstI.sticky { + top: 42px; +} + +/* list item content */ +.lstIcontent { + padding: 9px 0 7px; + position: relative; +} + +/* list item name (for sorting) */ +.lstIname { + white-space: nowrap; + text-overflow: ellipsis; + -webkit-filter: grayscale(100%); /* Safari 6.0 - 9.0 */ + filter: grayscale(100%); +} + +/* list item palette preview */ +.lstIprev { + width: 100%; + height: 7px; + position: absolute; + bottom: 0; + left: 0; + z-index: -1; +} + +/* find/search element */ +.fnd { + width: 280px; + margin: 0 auto; + position: relative; +} + +.fnd input[type="text"] { + display: block; + width: 100%; + box-sizing: border-box; + padding: 8px 40px 8px 44px; + margin: 5px auto 0; + text-align: left; + border-radius: 21px; + background: var(--c-2); + border: 1px solid var(--c-3); + -webkit-filter: grayscale(100%); /* Safari 6.0 - 9.0 */ + filter: grayscale(100%); +} + +.fnd input[type="text"]:focus { background-color: var(--c-4); } -input[type="text"].search:not(:placeholder-shown) { - background-color: var(--c-5); +.fnd input[type="text"]:not(:placeholder-shown), +.fnd input[type="text"]:hover { + background-color: var(--c-3); } -.pres { - margin-bottom: 6px; +/* segment & preset inner/expanded content */ +.segin, +.presin { + padding: 8px; + position: relative; + width: 264px; } -.segin { - padding: 8px 8px 4px 8px; +.btn-s, +.btn-n { + border: 1px solid var(--c-2); + background-color: var(--c-2); +} +.modal .btn:hover, +.segin .btn:hover { + border: 1px solid var(--c-5) /*!important*/; + background-color: var(--c-5) /*!important*/; +} + +/* hidden list items, must be after .expanded */ +.pres .lstIcontent, .segin { display: none; } +.check input:checked ~ .checkmark:after, +.radio input:checked ~ .radiomark:after, +.show, +.expanded .edit-icon, +.expanded .segin, .expanded .presin, .expanded .sbs, .expanded { - display: block; + display: inline-block !important; } .c { @@ -1140,10 +1415,7 @@ input[type="text"].search:not(:placeholder-shown) { width: auto; height: 2px; background-color: var(--c-b); -} - -.no-margin { - margin: 0; + margin: 3px 0; } ::-webkit-scrollbar { @@ -1161,23 +1433,46 @@ input[type="text"].search:not(:placeholder-shown) { background: var(--c-sbh); } +@media not all and (hover: none) { + .sliderwrap:hover + output.sliderbubble { + visibility: visible; + opacity: 1; + } +} + @media all and (max-width: 335px) { .sliderbubble { - display: none; - } + display: none; + } } @media all and (max-width: 550px) and (min-width: 374px) { - .infobtn { - width: 155px; + #info table .btn, #nodes table .btn { + width: 200px; + } + #info .infobtn, #nodes .infobtn { + width: 145px; + } + #info div, #nodes div { + max-width: 320px; } } -@media all and (max-width: 685px) { +@media all and (max-width: 540px) { .top button { width: 16.6%; padding: 8px 0 4px 0; } +} + +@media all and (min-width: 541px) and (max-width: 719px) { + .top button { + width: 14.2%; + padding: 8px 0 4px 0; + } +} + +@media all and (max-width: 719px) { .hd { display: none !important; } @@ -1187,6 +1482,12 @@ input[type="text"].search:not(:placeholder-shown) { } } +@media all and (max-width: 798px) { + #buttonNodes { + display: none; + } +} + @media all and (max-width: 1249px) { #buttonPcm { display: none; diff --git a/wled00/data/index.htm b/wled00/data/index.htm index d0cc5f0eb..f4b50c44f 100644 --- a/wled00/data/index.htm +++ b/wled00/data/index.htm @@ -7,10 +7,52 @@ WLED - + - +
Loading WLED UI...
@@ -23,10 +65,10 @@ - - - - + + + +

Brightness

@@ -36,6 +78,7 @@
+ @@ -44,45 +87,65 @@
-
-
-
-
- -
-

-
+
+
+ +
+ +
+

-
+
+ +
+ +
+

+
+
+ +
+ +
+

+
+
+
-

RGB color

-
- -
-

-
- -
-

-
- -
-

+

RGB color

+
+
+ +
+
+
+
+
+ +
+
+
+
+
+ +
+
+
-

White channel

-
- +

White channel

+
+
-
-

White balance

+
+

White balance

@@ -99,63 +162,142 @@
-
R
+
R
- - - + + +
+

+ - +
-
-

- - Color palette -

-
-
-
- +

Color palette

+
+
+ + + +
+
+
+
+
-

Effect speed

-
- -
- - -
+
+

Effect mode

+
+ + + +
+
+
+ +
-

Effect intensity

-
- -
- - -
+
+
+ + + + + +
+
+ +
+ +
+
+ + Effect speed +
+
+ +
+ +
+
+ + Effect intensity +
+
+ +
+ +
+
+ + Custom 1 +
+
+ +
+ +
+
+ + Custom 2 +
+
+ +
+ +
+
+ + Custom 3 +
+
+ + +
-
-

Effect mode

-
- Loading...
@@ -163,24 +305,27 @@
Loading...
-
- +
-

Transition: s

+

Transition:  s

-
- -
-
-
- Loading... +

Presets

+
+ + + +
+
+ Loading... +
+
@@ -193,25 +338,35 @@
-
+
+
Loading...

+
+ + + + +
+
+ Made with ❤︎ by Aircoookie and the WLED community
+ +
- - diff --git a/wled00/data/index.js b/wled00/data/index.js index 9de58ba94..66f502837 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -3,93 +3,72 @@ var loc = false, locip; var noNewSegs = false; var isOn = false, nlA = false, isLv = false, isInfo = false, isNodes = false, syncSend = false, syncTglRecv = true; var hasWhite = false, hasRGB = false, hasCCT = false; -var whites = [0,0,0]; -var colors = [[0,0,0],[0,0,0],[0,0,0]]; -var expanded = [false]; -var powered = [true]; var nlDur = 60, nlTar = 0; var nlMode = false; var selectedFx = 0; +var selectedPal = 0; var csel = 0; // selected color slot (0-2) var currentPreset = -1; var lastUpdate = 0; var segCount = 0, ledCount = 0, lowestUnused = 0, maxSeg = 0, lSeg = 0; -var pcMode = false, pcModeA = false, lastw = 0; +var pcMode = false, pcModeA = false, lastw = 0, wW; var tr = 7; var d = document; -const ranges = RangeTouch.setup('input[type="range"]', {}); var palettesData; -var pJson = {}; +var fxdata = []; +var pJson = {}, eJson = {}, lJson = {}; var pN = "", pI = 0, pNum = 0; var pmt = 1, pmtLS = 0, pmtLast = 0; var lastinfo = {}; -var ws; -var fxlist = d.getElementById('fxlist'), pallist = d.getElementById('pallist'); +var isM = false, mw = 0, mh=0; +var ws, cpick, ranges; var cfg = { theme:{base:"dark", bg:{url:""}, alpha:{bg:0.6,tab:0.8}, color:{bg:""}}, comp :{colors:{picker: true, rgb: false, quick: true, hex: false}, - labels:true, pcmbot:false, pid:true, seglen:false, css:true, hdays:false} + labels:true, pcmbot:false, pid:true, seglen:false, segpwr:false, segexp:false, css:true, hdays:false} }; var hol = [ [0,11,24,4,"https://aircoookie.github.io/xmas.png"], // christmas [0,2,17,1,"https://images.alphacoders.com/491/491123.jpg"], // st. Patrick's day - [2022,3,17,2,"https://aircoookie.github.io/easter.png"], + [2025,3,20,2,"https://aircoookie.github.io/easter.png"], [2023,3,9,2,"https://aircoookie.github.io/easter.png"], - [2024,2,31,2,"https://aircoookie.github.io/easter.png"] + [2024,2,31,2,"https://aircoookie.github.io/easter.png"], + [0,6,4,1,"https://initiate.alphacoders.com/download/wallpaper/516792/images/jpg/510921363292536"], // 4th of July + [0,0,1,1,"https://initiate.alphacoders.com/download/wallpaper/1198800/images/jpg/2522807481585600"] // new year ]; -var cpick = new iro.ColorPicker("#picker", { - width: 260, - wheelLightness: false, - wheelAngle: 270, - wheelDirection: "clockwise", - layout: [ - { - component: iro.ui.Wheel, - options: {} - } - ] -}); +function handleVisibilityChange() {if (!d.hidden && new Date () - lastUpdate > 3000) requestJson();} +function sCol(na, col) {d.documentElement.style.setProperty(na, col);} +function gId(c) {return d.getElementById(c);} +function gEBCN(c) {return d.getElementsByClassName(c);} +function isEmpty(o) {return Object.keys(o).length === 0;} +function isObj(i) {return (i && typeof i === 'object' && !Array.isArray(i));} +function isNumeric(n) {return !isNaN(parseFloat(n)) && isFinite(n);} -function handleVisibilityChange() { - if (!d.hidden && new Date () - lastUpdate > 3000) { - requestJson(null); - } -} +// returns true if dataset R, G & B values are 0 +function isRgbBlack(a) {return (parseInt(a.r) == 0 && parseInt(a.g) == 0 && parseInt(a.b) == 0);} -function sCol(na, col) { - d.documentElement.style.setProperty(na, col); -} - -function isRgbBlack(a, s) { - return (a[s][0] == 0 && a[s][1] == 0 && a[s][2] == 0); -} - -// returns RGB color from a given slot s 0-2 from color array a -function rgbStr(a, s) { - return "rgb(" + a[s][0] + "," + a[s][1] + "," + a[s][2] + ")"; -} +// returns RGB color from a given dataset +function rgbStr(a) {return "rgb(" + a.r + "," + a.g + "," + a.b + ")";} // brightness approximation for selecting white as text color if background bri < 127, and black if higher -function rgbBri(a, s) { - var R = a[s][0], G = a[s][1], B = a[s][2]; - return 0.2126*R + 0.7152*G + 0.0722*B; -} +function rgbBri(a) {return 0.2126*parseInt(a.r) + 0.7152*parseInt(a.g) + 0.0722*parseInt(a.b);} // sets background of color slot selectors -function setCSL(s) { - var cd = d.getElementsByClassName('cl')[s]; - var w = whites[s]; - if (hasRGB && !isRgbBlack(colors, s)) { - cd.style.background = rgbStr(colors, s); - cd.style.color = rgbBri(colors, s) > 127 ? "#000":"#fff"; +function setCSL(cs) +{ + let w = cs.dataset.w ? parseInt(cs.dataset.w) : 0; + let hasShadow = getComputedStyle(cs).textShadow !== "none"; + if (hasRGB && !isRgbBlack(cs.dataset)) { + cs.style.backgroundColor = rgbStr(cs.dataset); + if (!hasShadow) cs.style.color = rgbBri(cs.dataset) > 127 ? "#000":"#fff"; // if text has no CSS "shadow" if (hasWhite && w > 0) { - cd.style.background = `linear-gradient(180deg, ${rgbStr(colors, s)} 30%, ${rgbStr([[w,w,w]], 0)})`; + cs.style.background = `linear-gradient(180deg, ${rgbStr(cs.dataset)} 30%, rgb(${w},${w},${w}))`; } } else { if (!hasWhite) w = 0; - cd.style.background = rgbStr([[w,w,w]], 0); - cd.style.color = w > 127 ? "#000":"#fff"; + cs.style.background = `rgb(${w},${w},${w})`; + if (!hasShadow) cs.style.color = w > 127 ? "#000":"#fff"; } } @@ -98,15 +77,16 @@ function applyCfg() cTheme(cfg.theme.base === "light"); var bg = cfg.theme.color.bg; if (bg) sCol('--c-1', bg); - if (lastinfo.leds) updateUI(); // update component visibility var l = cfg.comp.labels; - sCol('--tbp',l ? "14px 14px 10px 14px":"10px 22px 4px 22px"); - sCol('--bbp',l ? "9px 0 7px 0":"10px 0 4px 0"); - sCol('--bhd',l ? "block":"none"); // hides/shows button labels - sCol('--bmt',l ? "0px":"5px"); + sCol('--tbp', l ? "14px 14px 10px 14px":"10px 22px 4px 22px"); + sCol('--bbp', l ? "9px 0 7px 0":"10px 0 4px 0"); + sCol('--bhd', l ? "block":"none"); // show/hide labels + sCol('--bmt', l ? "0px":"5px"); sCol('--t-b', cfg.theme.alpha.tab); + sCol('--sgp', !cfg.comp.segpwr ? "block":"none"); // show/hide segment power size(); localStorage.setItem('wledUiCfg', JSON.stringify(cfg)); + if (lastinfo.leds) updateUI(); // update component visibility } function tglHex() @@ -127,65 +107,76 @@ function tglLabels() applyCfg(); } +function tglRgb() +{ + cfg.comp.colors.rgb = !cfg.comp.colors.rgb; + applyCfg(); +} + function cTheme(light) { if (light) { - sCol('--c-1','#eee'); - sCol('--c-f','#000'); - sCol('--c-2','#ddd'); - sCol('--c-3','#bbb'); - sCol('--c-4','#aaa'); - sCol('--c-5','#999'); - sCol('--c-6','#999'); - sCol('--c-8','#888'); - sCol('--c-b','#444'); - sCol('--c-c','#333'); - sCol('--c-e','#111'); - sCol('--c-d','#222'); - sCol('--c-r','#c42'); - sCol('--c-o','rgba(204, 204, 204, 0.9)'); - sCol('--c-sb','#0003'); sCol('--c-sbh','#0006'); - sCol('--c-tb','rgba(204, 204, 204, var(--t-b))'); - sCol('--c-tba','rgba(170, 170, 170, var(--t-b))'); - sCol('--c-tbh','rgba(204, 204, 204, var(--t-b))'); - d.getElementById('imgw').style.filter = "invert(0.8)"; - } else { // default dark theme - sCol('--c-1','#111'); - sCol('--c-f','#fff'); - sCol('--c-2','#222'); - sCol('--c-3','#333'); - sCol('--c-4','#444'); - sCol('--c-5','#555'); - sCol('--c-6','#666'); - sCol('--c-8','#888'); - sCol('--c-b','#bbb'); - sCol('--c-c','#ccc'); - sCol('--c-e','#eee'); - sCol('--c-d','#ddd'); - sCol('--c-r','#831'); - sCol('--c-o','rgba(34, 34, 34, 0.9)'); - sCol('--c-sb','#fff3'); sCol('--c-sbh','#fff5'); - sCol('--c-tb','rgba(34, 34, 34, var(--t-b))'); - sCol('--c-tba','rgba(102, 102, 102, var(--t-b))'); - sCol('--c-tbh','rgba(51, 51, 51, var(--t-b))'); - d.getElementById('imgw').style.filter = "unset"; + sCol('--c-1','#eee'); + sCol('--c-f','#000'); + sCol('--c-2','#ddd'); + sCol('--c-3','#bbb'); + sCol('--c-4','#aaa'); + sCol('--c-5','#999'); + sCol('--c-6','#999'); + sCol('--c-8','#888'); + sCol('--c-b','#444'); + sCol('--c-c','#333'); + sCol('--c-e','#111'); + sCol('--c-d','#222'); + sCol('--c-r','#a21'); + sCol('--c-g','#2a1'); + sCol('--c-l','#26c'); + sCol('--c-o','rgba(204, 204, 204, 0.9)'); + sCol('--c-sb','#0003'); sCol('--c-sbh','#0006'); + sCol('--c-tb','rgba(204, 204, 204, var(--t-b))'); + sCol('--c-tba','rgba(170, 170, 170, var(--t-b))'); + sCol('--c-tbh','rgba(204, 204, 204, var(--t-b))'); + gId('imgw').style.filter = "invert(0.8)"; + } else { + sCol('--c-1','#111'); + sCol('--c-f','#fff'); + sCol('--c-2','#222'); + sCol('--c-3','#333'); + sCol('--c-4','#444'); + sCol('--c-5','#555'); + sCol('--c-6','#666'); + sCol('--c-8','#888'); + sCol('--c-b','#bbb'); + sCol('--c-c','#ccc'); + sCol('--c-e','#eee'); + sCol('--c-d','#ddd'); + sCol('--c-r','#e42'); + sCol('--c-g','#4e2'); + sCol('--c-l','#48a'); + sCol('--c-o','rgba(34, 34, 34, 0.9)'); + sCol('--c-sb','#fff3'); sCol('--c-sbh','#fff5'); + sCol('--c-tb','rgba(34, 34, 34, var(--t-b))'); + sCol('--c-tba','rgba(102, 102, 102, var(--t-b))'); + sCol('--c-tbh','rgba(51, 51, 51, var(--t-b))'); + gId('imgw').style.filter = "unset"; } } -function loadBg(iUrl) { - let bg = d.getElementById('bg'); +function loadBg(iUrl) +{ + let bg = gId('bg'); let img = d.createElement("img"); img.src = iUrl; - if (iUrl == "") { + if (iUrl == "" || iUrl==="https://picsum.photos/1920/1080") { var today = new Date(); - for (var i=0; i=hs && today<=he) img.src = hol[i][4]; + he.setDate(he.getDate() + h[3]); + if (today>=hs && today<=he) img.src = h[4]; } } - img.addEventListener('load', (event) => { + img.addEventListener('load', (e) => { var a = parseFloat(cfg.theme.alpha.bg); if (isNaN(a)) a = 0.6; bg.style.opacity = a; @@ -196,10 +187,10 @@ function loadBg(iUrl) { function loadSkinCSS(cId) { - if (!d.getElementById(cId)) // check if element exists + if (!gId(cId)) // check if element exists { - var h = document.getElementsByTagName('head')[0]; - var l = document.createElement('link'); + var h = d.getElementsByTagName('head')[0]; + var l = d.createElement('link'); l.id = cId; l.rel = 'stylesheet'; l.type = 'text/css'; @@ -209,10 +200,11 @@ function loadSkinCSS(cId) } } -function onLoad() { +function onLoad() +{ if (window.location.protocol == "file:") { - loc = true; - locip = localStorage.getItem('locIp'); + loc = true; + locip = localStorage.getItem('locIp'); if (!locip) { locip = prompt("File Mode. Please enter WLED IP!"); localStorage.setItem('locIp', locip); @@ -228,18 +220,18 @@ function onLoad() { fetch((loc?`http://${locip}`:'.') + "/holidays.json", { // may be loaded from external source method: 'get' }) - .then(res => { + .then((res)=>{ //if (!res.ok) showErrorToast(); return res.json(); }) - .then(json => { + .then((json)=>{ if (Array.isArray(json)) hol = json; //TODO: do some parsing first }) - .catch(function (error) { + .catch((e)=>{ console.log("No array of holidays in holidays.json. Defaults loaded."); }) - .finally(function(){ + .finally(()=>{ loadBg(cfg.theme.bg.url); }); } else @@ -248,20 +240,30 @@ function onLoad() { selectSlot(0); updateTablinks(0); - resetUtil(); - cpick.on("input:end", function() { - setColor(1); - }); - cpick.on("color:change", updatePSliders); pmtLS = localStorage.getItem('wledPmt'); - setTimeout(function(){requestJson(null, false);}, 50); + + // Load initial data + loadPalettes(()=>{ + // fill effect extra data array + loadFXData(()=>{ + // load and populate effects + loadFX(()=>{ + setTimeout(()=>{ // ESP8266 can't handle quick requests + loadPalettesData(()=>{ + requestJson();// will load presets and create WS + }); + },100); + }); + }); + }); + resetUtil(); + d.addEventListener("visibilitychange", handleVisibilityChange, false); size(); - d.getElementById("cv").style.opacity=0; + gId("cv").style.opacity=0; if (localStorage.getItem('pcm') == "true") togglePcMode(true); var sls = d.querySelectorAll('input[type="range"]'); for (var sl of sls) { - sl.addEventListener('input', updateBubble, true); sl.addEventListener('touchstart', toggleBubble); sl.addEventListener('touchend', toggleBubble); } @@ -269,15 +271,14 @@ function onLoad() { function updateTablinks(tabI) { - var tablinks = d.getElementsByClassName("tablinks"); - for (var i of tablinks) { - i.className = i.className.replace(" active", ""); - } + var tablinks = gEBCN("tablinks"); + for (var i of tablinks) i.classList.remove("active"); if (pcMode) return; - tablinks[tabI].className += " active"; + tablinks[tabI].classList.add("active"); } -function openTab(tabI, force = false) { +function openTab(tabI, force = false) +{ if (pcMode && !force) return; iSlide = tabI; _C.classList.toggle('smooth', false); @@ -286,24 +287,34 @@ function openTab(tabI, force = false) { } var timeout; -function showToast(text, error = false) { - if (error) d.getElementById('connind').style.backgroundColor = "#831"; - var x = d.getElementById("toast"); +function showToast(text, error = false) +{ + if (error) gId('connind').style.backgroundColor = "var(--c-r)"; + var x = gId("toast"); + //if (error) text += ''; x.innerHTML = text; - x.className = error ? "error":"show"; + x.classList.add(error ? "error":"show"); clearTimeout(timeout); x.style.animation = 'none'; - x.style.animation = null; - timeout = setTimeout(function(){ x.className = x.className.replace("show", ""); }, 2900); + timeout = setTimeout(()=>{ x.classList.remove("show"); }, 2900); + if (error) console.log(text); } -function showErrorToast() { - // if we received a timeout force WS reconnect - reconnectWS(); +function showErrorToast() +{ showToast('Connection to light failed!', true); } -function clearErrorToast() { - d.getElementById("toast").className = d.getElementById("toast").className.replace("error", ""); + +function clearErrorToast(n=5000) +{ + var x = gId("toast"); + if (x.classList.contains("error")) { + clearTimeout(timeout); + timeout = setTimeout(()=>{ + x.classList.remove("show"); + x.classList.remove("error"); + }, n); + } } function getRuntimeStr(rt) @@ -329,53 +340,54 @@ function inforow(key, val, unit = "") function getLowestUnusedP() { var l = 1; - for (var key in pJson) { - if (key == l) l++; - } + for (var key in pJson) if (key == l) l++; if (l > 250) l = 250; return l; } -function checkUsed(i) { - var id = d.getElementById(`p${i}id`).value; - if (pJson[id] && (i == 0 || id != i)) { - d.getElementById(`p${i}warn`).innerHTML = `⚠ Overwriting ${pName(id)}!`; - } else { - d.getElementById(`p${i}warn`).innerHTML = ""; - } +function checkUsed(i) +{ + var id = gId(`p${i}id`).value; + if (pJson[id] && (i == 0 || id != i)) + gId(`p${i}warn`).innerHTML = `⚠ Overwriting ${pName(id)}!`; + else + gId(`p${i}warn`).innerHTML = id>250?"⚠ ID must be 250 or less.":""; } -function pName(i) { +function pName(i) +{ var n = "Preset " + i; - if (pJson[i].n) n = pJson[i].n; + if (pJson && pJson[i] && pJson[i].n) n = pJson[i].n; return n; } -function isPlaylist(i) { +function isPlaylist(i) +{ return pJson[i].playlist && pJson[i].playlist.ps; } -function papiVal(i) { - if (!pJson[i]) return ""; +function papiVal(i) +{ + if (!pJson || !pJson[i]) return ""; var o = Object.assign({},pJson[i]); if (o.win) return o.win; delete o.n; delete o.p; delete o.ql; return JSON.stringify(o); } -function qlName(i) { - if (!pJson[i]) return ""; - if (!pJson[i].ql) return ""; +function qlName(i) +{ + if (!pJson || !pJson[i] || !pJson[i].ql) return ""; return pJson[i].ql; } -function cpBck() { - var copyText = d.getElementById("bck"); +function cpBck() +{ + var copyText = gId("bck"); copyText.select(); copyText.setSelectionRange(0, 999999); d.execCommand("copy"); - showToast("Copied to clipboard!"); } @@ -385,10 +397,9 @@ function presetError(empty) try { bckstr = localStorage.getItem("wledP"); if (bckstr.length > 10) hasBackup = true; - } catch (e) { + } catch (e) {} - } - var cn = `
`; + var cn = `
`; if (empty) cn += `You have no presets yet!`; else @@ -405,82 +416,142 @@ function presetError(empty) `; } cn += `
`; - d.getElementById('pcont').innerHTML = cn; - if (hasBackup) d.getElementById('bck').value = bckstr; + gId('pcont').innerHTML = cn; + if (hasBackup) gId('bck').value = bckstr; } function loadPresets(callback = null) { - //1st boot (because there is a callback) + // 1st boot (because there is a callback) if (callback && pmt == pmtLS && pmt > 0) { - //we have a copy of the presets in local storage and don't need to fetch another one + // we have a copy of the presets in local storage and don't need to fetch another one populatePresets(true); pmtLast = pmt; callback(); return; } - //afterwards + // afterwards if (!callback && pmt == pmtLast) return; - pmtLast = pmt; + var url = (loc?`http://${locip}`:'') + '/presets.json'; - var url = '/presets.json'; - if (loc) { - url = `http://${locip}/presets.json`; - } - - fetch - (url, { + fetch(url, { method: 'get' }) .then(res => { - if (!res.ok) { - showErrorToast(); - } + if (res.status=="404") return {"0":{}}; + //if (!res.ok) showErrorToast(); return res.json(); }) .then(json => { pJson = json; + pmtLast = pmt; populatePresets(); }) - .catch(function (error) { - showToast(error, true); - console.log(error); + .catch((e)=>{ + //showToast(e, true); presetError(false); }) - .finally(() => { + .finally(()=>{ if (callback) setTimeout(callback,99); }); } -var pQL = []; +function loadPalettes(callback = null) +{ + var url = (loc?`http://${locip}`:'') + '/json/palettes'; + fetch(url, { + method: 'get' + }) + .then((res)=>{ + if (!res.ok) showErrorToast(); + return res.json(); + }) + .then((json)=>{ + lJson = Object.entries(json); + populatePalettes(); + }) + .catch((e)=>{ + showToast(e, true); + }) + .finally(()=>{ + if (callback) callback(); + updateUI(); + }); +} + +function loadFX(callback = null) +{ + var url = (loc?`http://${locip}`:'') + '/json/effects'; + + fetch(url, { + method: 'get' + }) + .then((res)=>{ + if (!res.ok) showErrorToast(); + return res.json(); + }) + .then((json)=>{ + eJson = Object.entries(json); + populateEffects(); + }) + .catch((e)=>{ + showToast(e, true); + }) + .finally(()=>{ + if (callback) callback(); + updateUI(); + }); +} + +function loadFXData(callback = null) +{ + var url = (loc?`http://${locip}`:'') + '/json/fxdata'; + + fetch(url, { + method: 'get' + }) + .then((res)=>{ + if (!res.ok) showErrorToast(); + return res.json(); + }) + .then((json)=>{ + fxdata = json||[]; + // add default value for Solid + fxdata.shift() + fxdata.unshift("@;!;0"); + }) + .catch((e)=>{ + fxdata = []; + showToast(e, true); + }) + .finally(()=>{ + if (callback) callback(); + updateUI(); + }); +} + +var pQL = []; function populateQL() { var cn = ""; if (pQL.length > 0) { - cn += `

Quick load

`; - - var it = 0; + pQL.sort((a,b) => (a[0]>b[0])); + cn += `

Quick load

`; for (var key of (pQL||[])) { - cn += ``; - it++; - if (it > 4) { - it = 0; - cn += '
'; - } + cn += ``; } - if (it != 0) cn+= '
'; - - cn += `

All presets

`; - } - d.getElementById('pql').innerHTML = cn; + gId('pql').classList.add("expanded"); + } else gId('pql').classList.remove("expanded"); + gId('pql').innerHTML = cn; } function populatePresets(fromls) { if (fromls) pJson = JSON.parse(localStorage.getItem("wledP")); + if (!pJson) {setTimeout(loadPresets,250); return;} delete pJson["0"]; var cn = ""; var arr = Object.entries(pJson); @@ -488,25 +559,25 @@ function populatePresets(fromls) pQL = []; var is = []; pNum = 0; - for (var key of (arr||[])) { - if (!isObject(key[1])) continue; + if (!isObj(key[1])) continue; let i = parseInt(key[0]); var qll = key[1].ql; - if (qll) pQL.push([i, qll]); + if (qll) pQL.push([i, qll, pName(i)]); is.push(i); - cn += `
`; + cn += `
`; if (cfg.comp.pid) cn += `
${i}
`; - cn += `
${isPlaylist(i)?"":""}${pName(i)}
- -
-

`; + cn += `
${isPlaylist(i)?"":""}${pName(i)} +
+ +
+
`; pNum++; } - d.getElementById('pcont').innerHTML = cn; + gId('pcont').innerHTML = cn; if (pNum > 0) { if (pmtLS != pmt && pmt != 0) { localStorage.setItem("wledPmt", pmt); @@ -514,16 +585,57 @@ function populatePresets(fromls) localStorage.setItem("wledP", JSON.stringify(pJson)); } pmtLS = pmt; - for (var a = 0; a < is.length; a++) { - let i = is[a]; - if (expanded[i+100]) expand(i+100, true); - } - //makePlSel(arr); } else { presetError(true); } updatePA(); populateQL(); } +function parseInfo(i) { + lastinfo = i; + var name = i.name; + gId('namelabel').innerHTML = name; + if (!name.match(/[\u3040-\u30ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff\uff66-\uff9f\u3131-\uD79D]/)) + gId('namelabel').style.transform = "rotate(180deg)"; // rotate if no CJK characters + if (name === "Dinnerbone") d.documentElement.style.transform = "rotate(180deg)"; // Minecraft easter egg + if (i.live) name = "(Live) " + name; + if (loc) name = "(L) " + name; + d.title = name; + ledCount = i.leds.count; + syncTglRecv = i.str; + maxSeg = i.leds.maxseg; + pmt = i.fs.pmt; + // do we have a matrix set-up + mw = i.leds.matrix ? i.leds.matrix.w : 0; + mh = i.leds.matrix ? i.leds.matrix.h : 0; + isM = mw>0 && mh>0; + if (!isM) { + gId("filter1D").classList.add("hide"); + //gId("filter2D").classList.add("hide"); + hideModes("2D"); + } +// if (i.noaudio) { +// gId("filterVol").classList.add("hide"); +// gId("filterFreq").classList.add("hide"); +// } +// if (!i.u || !i.u.AudioReactive) { +// gId("filterVol").classList.add("hide"); hideModes(" ♪"); // hide volume reactive effects +// gId("filterFreq").classList.add("hide"); hideModes(" ♫"); // hide frequency reactive effects +// } +} + +//https://stackoverflow.com/questions/2592092/executing-script-elements-inserted-with-innerhtml +//var setInnerHTML = function(elm, html) { +// elm.innerHTML = html; +// Array.from(elm.querySelectorAll("script")).forEach( oldScript => { +// const newScript = document.createElement("script"); +// Array.from(oldScript.attributes) +// .forEach( attr => newScript.setAttribute(attr.name, attr.value) ); +// newScript.appendChild(document.createTextNode(oldScript.innerHTML)); +// oldScript.parentNode.replaceChild(newScript, oldScript); +// }); +//} +//setInnerHTML(obj, html); + function populateInfo(i) { var cn=""; @@ -536,31 +648,37 @@ function populateInfo(i) var urows=""; if (i.u) { for (const [k, val] of Object.entries(i.u)) { - if (val[1]) { + if (val[1]) urows += inforow(k,val[0],val[1]); - } else { + else urows += inforow(k,val); - } } } - var vcn = "Kuuhaku"; - if (i.ver.startsWith("0.13.")) vcn = "Toki"; + if (i.ver.startsWith("0.14.")) vcn = "Hoshi"; + if (i.ver.includes("-bl")) vcn = "Supāku"; if (i.cn) vcn = i.cn; - cn += `v${i.ver} "${vcn}"

- ${urows} - ${inforow("Build",i.vid)} - ${inforow("Signal strength",i.wifi.signal +"% ("+ i.wifi.rssi, " dBm)")} - ${inforow("Uptime",getRuntimeStr(i.uptime))} - ${inforow("Free heap",heap," kB")} - ${inforow("Estimated current",pwru)} - ${inforow("Frames / second",i.leds.fps)} - ${inforow("MAC address",i.mac)} - ${inforow("Filesystem",i.fs.u + "/" + i.fs.t + " kB (" +Math.round(i.fs.u*100/i.fs.t) + "%)")} - ${inforow("Environment",i.arch + " " + i.core + " (" + i.lwip + ")")} -
`; - d.getElementById('kv').innerHTML = cn; + cn += `v${i.ver} "${vcn}"

+${urows} +${urows===""?'':''} +${inforow("Build",i.vid)} +${inforow("Signal strength",i.wifi.signal +"% ("+ i.wifi.rssi, " dBm)")} +${inforow("Uptime",getRuntimeStr(i.uptime))} +${inforow("Free heap",heap," kB")} +${i.psram?inforow("Free PSRAM",(i.psram/1024).toFixed(1)," kB"):""} +${inforow("Estimated current",pwru)} +${inforow("Average FPS",i.leds.fps)} +${inforow("MAC address",i.mac)} +${inforow("Filesystem",i.fs.u + "/" + i.fs.t + " kB (" +Math.round(i.fs.u*100/i.fs.t) + "%)")} +${inforow("Environment",i.arch + " " + i.core + " (" + i.lwip + ")")} +

`; + gId('kv').innerHTML = cn; + // update all sliders in Info + for (let sd of (gId('kv').getElementsByClassName('sliderdisplay')||[])) { + let s = sd.previousElementSibling; + if (s) updateTrail(s); + } } function populateSegments(s) @@ -569,83 +687,113 @@ function populateSegments(s) let li = lastinfo; segCount = 0; lowestUnused = 0; lSeg = 0; - for (var y = 0; y < (s.seg||[]).length; y++) - { + for (var inst of (s.seg||[])) { segCount++; - var inst=s.seg[y]; let i = parseInt(inst.id); - powered[i] = inst.on; if (i == lowestUnused) lowestUnused = i+1; if (i > lSeg) lSeg = i; - cn += `
- - &#x${li.live && li.liveseg==i?'e410':'e325'}; -
-
${inst.n ? inst.n : "Segment "+i}
- -
- -
- -
- -
- -
-
-
- - - - - - - - - - - -
Start LED${cfg.comp.seglen?"Length":"Stop LED"}Offset
- - - - - - - - - - - -
GroupingSpacingApply
-
- - -
- - -
-
-

`; + let sg = gId(`seg${i}`); + let exp = sg ? (sg.classList.contains("expanded") || (i===0 && cfg.comp.segexp)) : false; + + let segp = `
+ +
+ +
+
+
`; + let staX = inst.start; + let stoX = inst.stop; + let staY = inst.startY; + let stoY = inst.stopY; + let rvXck = ``; + let miXck = ``; + let rvYck = "", miYck =""; + if (isM) { + rvYck = ``; + miYck = ``; + } + let map2D = `
Expand 1D FX
+
+
`; + let sndSim = `
Sound sim
+
+
`; + cn += `
+ + &#x${inst.frz ? (li.live && li.liveseg==i?'e410':'e0e8') : 'e325'}; +
+ ${inst.n ? inst.n : "Segment "+i} + +
+ + ${cfg.comp.segpwr?segp:''} +
+ + + + + + + + + + + + + ${isM ? ''+ + ''+ + ''+ + ''+ + ''+ + '':''} + + + + + + + + + + +
${isM?'Start X':'Start LED'}${isM?(cfg.comp.seglen?"Width":"Stop X"):(cfg.comp.seglen?"LED count":"Stop LED")}${isM?'':'Offset'}
${isM?miXck+'
'+rvXck:''}
Start Y'+(cfg.comp.seglen?'Height':'Stop Y')+'
'+miYck+'
'+rvYck+'
GroupingSpacing
+
+ ${!isM?rvXck:''} + ${isM&&stoY-staY>1&&stoX-staX>1?map2D:''} + ${s.AudioReactive && s.AudioReactive.on ? "" : sndSim} + +
+ + +
+
+ ${cfg.comp.segpwr?'':segp} +
`; } - d.getElementById('segcont').innerHTML = cn; + gId('segcont').innerHTML = cn; if (lowestUnused >= maxSeg) { - d.getElementById('segutil').innerHTML = 'Maximum number of segments reached.'; + gId('segutil').innerHTML = 'Maximum number of segments reached.'; noNewSegs = true; } else if (noNewSegs) { resetUtil(); @@ -653,100 +801,98 @@ function populateSegments(s) } for (var i = 0; i <= lSeg; i++) { updateLen(i); - updateTrail(d.getElementById(`seg${i}bri`)); - let segr = d.getElementById(`segr${i}`); - if (segr) segr.style.display = "none"; + updateTrail(gId(`seg${i}bri`)); + gId(`segr${i}`).style.display = "none"; + if (!gId(`seg${i}sel`).checked) gId(`selall`).checked = false; } - if (segCount < 2) d.getElementById(`segd${lSeg}`).style.display = "none"; - if (!noNewSegs && (cfg.comp.seglen?parseInt(d.getElementById(`seg${lSeg}s`).value):0)+parseInt(d.getElementById(`seg${lSeg}e`).value) 1) ? "inline":"none"; + if (segCount < 2) gId(`segd${lSeg}`).style.display = "none"; + if (!isM && !noNewSegs && (cfg.comp.seglen?parseInt(gId(`seg${lSeg}s`).value):0)+parseInt(gId(`seg${lSeg}e`).value) 1) ? "block":"none"; // rsbtn parent } -function populateEffects(effects) +function populateEffects() { - var html = ``; + var effects = eJson; + var html = ""; - effects.shift(); //remove solid + effects.shift(); // temporary remove solid for (let i = 0; i < effects.length; i++) { - effects[i] = {id: parseInt(i)+1, name:effects[i]}; - } - effects.sort(compare); - - effects.unshift({ - "id": 0, - "name": "Solid", - "class": "sticky" - }); - - for (let i = 0; i < effects.length; i++) { - html += generateListItemHtml( - 'fx', - effects[i].id, - effects[i].name, - 'setX', - '', - effects[i].class, - ); - } - - fxlist.innerHTML=html; -} - -function populatePalettes(palettes) -{ - palettes.shift(); //remove default - for (let i = 0; i < palettes.length; i++) { - palettes[i] = { - "id": parseInt(i)+1, - "name": palettes[i] + effects[i] = { + id: effects[i][0], + name:effects[i][1] }; } - palettes.sort(compare); - - palettes.unshift({ + effects.sort((a,b) => (a.name).localeCompare(b.name)); + effects.unshift({ "id": 0, - "name": "Default", - "class": "sticky" + "name": "Solid" }); - - var html = ``; - for (let i = 0; i < palettes.length; i++) { + + for (let ef of effects) { + // WLEDSR: add slider and color control to setFX (used by requestjson) + let id = ef.id; + let nm = ef.name+" "; + let fd = ""; + if (ef.name.indexOf("RSVD") < 0) { + if (Array.isArray(fxdata) && fxdata.length>id) { + if (fxdata[id].length==0) fd = ";;!;1d" + else fd = fxdata[id].substr(1); + let eP = (fd == '')?[]:fd.split(";"); // effect parameters + let p = (eP.length<3 || eP[2]==='')?[]:eP[2].split(","); // palette data + if (p.length>0 && (p[0] !== "" && !isNumeric(p[0]))) nm += "🎨"; // effects using palette + let m = (eP.length<4 || eP[3]==='')?[]:eP[3].split(","); // metadata + if (m.length>0) for (let r of m) { + if (r.substring(0,2)=="1d") nm += "⋮"; // 1D effects + if (r.substring(0,2)=="2d") nm += "▦"; // 2D effects + if (r.substring(0,2)=="vo") nm += "♪"; // volume effects + if (r.substring(0,2)=="fr") nm += "♫"; // frequency effects + } + } + html += generateListItemHtml('fx',id,nm,'setFX','',fd); + } + } + + gId('fxlist').innerHTML=html; +} + +function populatePalettes() +{ + lJson.shift(); // temporary remove default + lJson.sort((a,b) => (a[1]).localeCompare(b[1])); + lJson.unshift([0,"Default"]); + + var html = ""; + for (let pa of lJson) { html += generateListItemHtml( 'palette', - palettes[i].id, - palettes[i].name, + pa[0], + pa[1], 'setPalette', - `
`, - palettes[i].class, + `
` ); } - pallist.innerHTML=html; + + gId('pallist').innerHTML=html; } function redrawPalPrev() { let palettes = d.querySelectorAll('#pallist .lstI'); - for (let i = 0; i < palettes.length; i++) { - let id = palettes[i].dataset.id; - let lstPrev = palettes[i].querySelector('.lstIprev'); - if (lstPrev) { - lstPrev.style = genPalPrevCss(id); + for (var pal of (palettes||[])) { + let lP = pal.querySelector('.lstIprev'); + if (lP) { + lP.style = genPalPrevCss(pal.dataset.id); } } } function genPalPrevCss(id) { - if (!palettesData) { - return; - } + if (!palettesData) return; + var paletteData = palettesData[id]; - if (!paletteData) { - return 'display: none'; - } + if (!paletteData) return 'display: none'; // We need at least two colors for a gradient if (paletteData.length == 1) { @@ -758,60 +904,62 @@ function genPalPrevCss(id) var gradient = []; for (let j = 0; j < paletteData.length; j++) { - const element = paletteData[j]; - let r; - let g; - let b; + const e = paletteData[j]; + let r, g, b; let index = false; - if (Array.isArray(element)) { - index = element[0]/255*100; - r = element[1]; - g = element[2]; - b = element[3]; - } else if (element == 'r') { + if (Array.isArray(e)) { + index = Math.round(e[0]/255*100); + r = e[1]; + g = e[2]; + b = e[3]; + } else if (e == 'r') { r = Math.random() * 255; g = Math.random() * 255; b = Math.random() * 255; } else { - if (colors) { - let pos = element[1] - 1; - r = colors[pos][0]; - g = colors[pos][1]; - b = colors[pos][2]; - } + let i = e[1] - 1; + var cd = gId('csl').children; + r = parseInt(cd[i].dataset.r); + g = parseInt(cd[i].dataset.g); + b = parseInt(cd[i].dataset.b); } if (index === false) { - index = j / paletteData.length * 100; + index = Math.round(j / paletteData.length * 100); } - + gradient.push(`rgb(${r},${g},${b}) ${index}%`); } return `background: linear-gradient(to right,${gradient.join()});`; } -function generateListItemHtml(listName, id, name, clickAction, extraHtml = '', extraClass = '') +function generateListItemHtml(listName, id, name, clickAction, extraHtml = '', effectPar = '') { - return `
-
` + ); + } + } + parseInfo(i); + if (isInfo) populateInfo(i); } - if (json.success) { - return; - } - var s = json; - if (reqsLegal && !ws) reconnectWS(); - - if (!command || rinfo) { //we have info object - if (!rinfo) { //entire JSON (on load) - populateEffects(json.effects); - populatePalettes(json.palettes); + var s = json.state ? json.state : json; + readState(s); - //load palette previews, presets, and open websocket sequentially - setTimeout(function(){ - loadPresets(function(){ - loadPalettesData(function(){ - if (!ws && json.info.ws > -1) makeWS(); - }); - }); - },25); - - reqsLegal = true; - } - - var info = json.info; - var name = info.name; - d.getElementById('namelabel').innerHTML = name; - if (name === "Dinnerbone") { - d.documentElement.style.transform = "rotate(180deg)"; - } - if (info.live) { - name = "(Live) " + name; - } - if (loc) { - name = "(L) " + name; - } - d.title = name; - ledCount = info.leds.count; - syncTglRecv = info.str; - maxSeg = info.leds.maxseg; - pmt = info.fs.pmt; - - if (!command && rinfo) setTimeout(loadPresets, 99); - - d.getElementById('buttonNodes').style.display = (info.ndc > 0 && window.innerWidth > 770) ? "block":"none"; - lastinfo = info; - if (isInfo) { - populateInfo(info); - } - s = json.state; - displayRover(info, s); - } - - readState(s,command); + //load presets and open websocket sequentially + if (!pJson || isEmpty(pJson)) setTimeout(()=>{ + loadPresets(()=>{ + if (!(ws && ws.readyState === WebSocket.OPEN)) makeWS(); + }); + },25); + reqsLegal = true; }) - .catch(function (error) { - showToast(error, true); - console.log(error); + .catch((e)=>{ + showToast(e, true); }); } -function togglePower() { +function togglePower() +{ isOn = !isOn; var obj = {"on": isOn}; + if (isOn && lastinfo && lastinfo.live && lastinfo.liveseg>=0) { + obj.live = false; + obj.seg = []; + obj.seg[0] = {"id": lastinfo.liveseg, "frz": false}; + } requestJson(obj); } -function toggleNl() { +function toggleNl() +{ nlA = !nlA; if (nlA) { @@ -1269,115 +1581,145 @@ function toggleNl() { requestJson(obj); } -function toggleSync() { +function toggleSync() +{ syncSend = !syncSend; - if (syncSend) - { - showToast('Other lights in the network will now sync to this one.'); - } else { - showToast('This light and other lights in the network will no longer sync.'); - } + if (syncSend) showToast('Other lights in the network will now sync to this one.'); + else showToast('This light and other lights in the network will no longer sync.'); var obj = {"udpn": {"send": syncSend}}; if (syncTglRecv) obj.udpn.recv = syncSend; requestJson(obj); } -function toggleLiveview() { +function toggleLiveview() +{ + //WLEDSR adding liveview2D support + if (isInfo && isM) toggleInfo(); + if (isNodes && isM) toggleNodes(); isLv = !isLv; - d.getElementById('liveview').style.display = (isLv) ? "block":"none"; - var url = loc ? `http://${locip}/liveview`:"/liveview"; - d.getElementById('liveview').src = (isLv) ? url:"about:blank"; - d.getElementById('buttonSr').className = (isLv) ? "active":""; + + var lvID = "liveview"; + if (isM) { + lvID = "liveview2D" + if (isLv) { + var cn = ''; + d.getElementById('kliveview2D').innerHTML = cn; + } + + gId('mliveview2D').style.transform = (isLv) ? "translateY(0px)":"translateY(100%)"; + } + + gId(lvID).style.display = (isLv) ? "block":"none"; + var url = (loc?`http://${locip}`:'') + "/" + lvID; + gId(lvID).src = (isLv) ? url:"about:blank"; + gId('buttonSr').className = (isLv) ? "active":""; if (!isLv && ws && ws.readyState === WebSocket.OPEN) ws.send('{"lv":false}'); size(); } -function toggleInfo() { +function toggleInfo() +{ if (isNodes) toggleNodes(); + if (isLv && isM) toggleLiveview(); isInfo = !isInfo; - if (isInfo) populateInfo(lastinfo); - d.getElementById('info').style.transform = (isInfo) ? "translateY(0px)":"translateY(100%)"; - d.getElementById('buttonI').className = (isInfo) ? "active":""; + if (isInfo) requestJson(); + gId('info').style.transform = (isInfo) ? "translateY(0px)":"translateY(100%)"; + gId('buttonI').className = (isInfo) ? "active":""; } -function toggleNodes() { +function toggleNodes() +{ if (isInfo) toggleInfo(); + if (isLv && isM) toggleLiveview(); isNodes = !isNodes; - d.getElementById('nodes').style.transform = (isNodes) ? "translateY(0px)":"translateY(100%)"; - d.getElementById('buttonNodes').className = (isNodes) ? "active":""; if (isNodes) loadNodes(); + gId('nodes').style.transform = (isNodes) ? "translateY(0px)":"translateY(100%)"; + gId('buttonNodes').className = (isNodes) ? "active":""; } -function makeSeg() { +function makeSeg() +{ var ns = 0; - if (lowestUnused > 0) { - var pend = parseInt(d.getElementById(`seg${lowestUnused -1}e`).value,10) + (cfg.comp.seglen?parseInt(d.getElementById(`seg${lowestUnused -1}s`).value,10):0); + var lu = lowestUnused; + let li = lastinfo; + if (lu > 0) { + var pend = parseInt(gId(`seg${lu -1}e`).value,10) + (cfg.comp.seglen?parseInt(gId(`seg${lu -1}s`).value,10):0); if (pend < ledCount) ns = pend; } - var cn = `
-
- New segment ${lowestUnused} - -
-
-
- - + gId('segutil').scrollIntoView({ + behavior: 'smooth', + block: 'start', + }); + var ct = (isM?mw:ledCount)-(cfg.comp.seglen?ns:0); + //TODO: add calculation for Y in case of 2D matrix + var cn = `
+
+ +
- - - + + - - - + + + + ${isM ? ''+ + ''+ + ''+ + ''+ + '':''}
Start LED${cfg.comp.seglen?"Length":"Stop LED"}Apply${isM?'Start X':'Start LED'}${isM?(cfg.comp.seglen?"Width":"Stop X"):(cfg.comp.seglen?"LED count":"Stop LED")}
Start Y'+(cfg.comp.seglen?'Height':'Stop Y')+'
-
${ledCount - ns} LED${ledCount - ns >1 ? "s":""}
+
${ledCount - ns} LEDs
+
`; - d.getElementById('segutil').innerHTML = cn; + gId('segutil').innerHTML = cn; } -function resetUtil() { - var cn = `
`; - d.getElementById('segutil').innerHTML = cn; +function resetUtil() +{ +// gId('segutil').innerHTML = ''; + gId('segutil').innerHTML = '
' + + '' + + '
segment
'; } var plJson = {"0":{ - "ps": [0], - "dur": [100], - "transition": [-1], //to be inited to default transition dur + "ps": [0], + "dur": [100], + "transition": [-1], // to be inited to default transition dur "repeat": 0, "r": false, - "end": 0 + "end": 0 }}; -function makePlSel(incPl=false) { +function makePlSel(el, incPl=false) { var plSelContent = ""; delete pJson["0"]; // remove filler preset var arr = Object.entries(pJson); - for (var i = 0; i < arr.length; i++) { - var n = arr[i][1].n ? arr[i][1].n : "Preset " + arr[i][0]; - if (!incPl && arr[i][1].playlist && arr[i][1].playlist.ps) continue; //remove playlists, sub-playlists not yet supported - plSelContent += `` + for (var a of arr) { + var n = a[1].n ? a[1].n : "Preset " + a[0]; + if (!incPl && a[1].playlist && a[1].playlist.ps) continue; // remove playlists, sub-playlists not yet supported + plSelContent += `` } return plSelContent; } function refreshPlE(p) { - var plEDiv = d.getElementById(`ple${p}`); + var plEDiv = gId(`ple${p}`); if (!plEDiv) return; - var content = ""; + var content = "
Playlist entries
"; for (var i = 0; i < plJson[p].ps.length; i++) { content += makePlEntry(p,i); } + content += `
`; plEDiv.innerHTML = content; var dels = plEDiv.getElementsByClassName("btn-pl-del"); if (dels.length < 2) dels[0].style.display = "none"; - var sels = d.getElementById(`seg${p+100}`).getElementsByClassName("sel"); + var sels = gId(`seg${p+100}`).getElementsByClassName("sel"); for (var i of sels) { if (i.dataset.val) { if (parseInt(i.dataset.val) > 0) i.value = i.dataset.val; @@ -1386,7 +1728,7 @@ function refreshPlE(p) { } } -//p: preset ID, i: ps index +// p: preset ID, i: ps index function addPl(p,i) { plJson[p].ps.splice(i+1,0,0); plJson[p].dur.splice(i+1,0,plJson[p].dur[i]); @@ -1418,15 +1760,15 @@ function pleTr(p,i,field) { function plR(p) { var pl = plJson[p]; - pl.r = d.getElementById(`pl${p}rtgl`).checked; - if (d.getElementById(`pl${p}rptgl`).checked) { //infinite + pl.r = gId(`pl${p}rtgl`).checked; + if (gId(`pl${p}rptgl`).checked) { // infinite pl.repeat = 0; delete pl.end; - d.getElementById(`pl${p}o1`).style.display = "none"; + gId(`pl${p}o1`).style.display = "none"; } else { - pl.repeat = parseInt(d.getElementById(`pl${p}rp`).value); - pl.end = parseInt(d.getElementById(`pl${p}selEnd`).value); - d.getElementById(`pl${p}o1`).style.display = "block"; + pl.repeat = parseInt(gId(`pl${p}rp`).value); + pl.end = parseInt(gId(`pl${p}selEnd`).value); + gId(`pl${p}o1`).style.display = "block"; } } @@ -1434,154 +1776,209 @@ function makeP(i,pl) { var content = ""; if (pl) { var rep = plJson[i].repeat ? plJson[i].repeat : 0; - content = `
Playlist Entries
-
-
+ +
+
+
+

Quick Load

+
+
+ +
+

Solid color

+
+
+
+
+
+
+

+
+
+
+
+
R
+
+
+ +
+ +
+
+ +
+ +
+

+
+
+ +
+ +
+
+
+
+

RGB channels

+
+
+ +
+
+

+
+
+ +
+
+

+
+
+ +
+
+

+
+
+

White channel

+
+ +
+
+
+
+

White balance

+
+ +
+
+
+
+ +
+

Color slots

+ +
+ +
+
+
+ +
+

Presets

+
+ + + +
+
+
+ +
+

Effect

+
+ +
+ +
+
+ +
+
+ +
+ +
+
+ +
+
+
Solid
+
Default
+
+
+ + + +
+
+ +
+
+
+
+ + + +
+
+ +
+
+
+
+
+
+
+ +
+
+
+ + + + + +
+ + + diff --git a/wled00/data/simple.js b/wled00/data/simple.js new file mode 100644 index 000000000..79db2714d --- /dev/null +++ b/wled00/data/simple.js @@ -0,0 +1,1452 @@ +//page js +var loc = false, locip; +var noNewSegs = false; +var isOn = false, isInfo = false, isNodes = false, isRgbw = false, cct = false; +var whites = [0,0,0]; +var selColors; +var powered = [true]; +var selectedFx = 0; +var selectedPal = 0; +var csel = 0; +var currentPreset = -1; +var lastUpdate = 0; +var segCount = 0, ledCount = 0, lowestUnused = 0, maxSeg = 0, lSeg = 0; +var tr = 7; +var d = document; +var palettesData; +var fxdata = []; +var pJson = {}, eJson = {}, lJson = {}; +var pN = "", pI = 0, pNum = 0; +var pmt = 1, pmtLS = 0, pmtLast = 0; +var lastinfo = {}; +var ws, cpick, ranges; +var cfg = { + theme:{base:"dark", bg:{url:""}, alpha:{bg:0.6,tab:0.8}, color:{bg:""}}, + comp :{colors:{picker: true, rgb: false, quick: true, hex: false}, labels:true, pcmbot:false, pid:true, seglen:false} +}; +var hol = [ + [0,11,24,4,"https://aircoookie.github.io/xmas.png"], // christmas + [0,2,17,1,"https://images.alphacoders.com/491/491123.jpg"], // st. Patrick's day + [2022,3,17,2,"https://aircoookie.github.io/easter.png"], + [2023,3,9,2,"https://aircoookie.github.io/easter.png"], + [2024,2,31,2,"https://aircoookie.github.io/easter.png"] +]; + +function handleVisibilityChange() {if (!d.hidden && new Date () - lastUpdate > 3000) requestJson();} +function sCol(na, col) {d.documentElement.style.setProperty(na, col);} +function gId(c) {return d.getElementById(c);} +function gEBCN(c) {return d.getElementsByClassName(c);} +function isEmpty(o) {return Object.keys(o).length === 0;} +function isObj(i) { return (i && typeof i === 'object' && !Array.isArray(i)); } + +function applyCfg() +{ + cTheme(cfg.theme.base === "light"); + var bg = cfg.theme.color.bg; + if (bg) sCol('--c-1', bg); + var ccfg = cfg.comp.colors; + //gId('picker').style.display = "none"; // ccfg.picker ? "block":"none"; + //gId('vwrap').style.display = "none"; // ccfg.picker ? "block":"none"; + //gId('rgbwrap').style.display = ccfg.rgb ? "block":"none"; + gId('qcs-w').style.display = ccfg.quick ? "block":"none"; + var l = cfg.comp.labels; //l = false; + var e = d.querySelectorAll('.tab-label'); + for (var i=0; i { + var a = parseFloat(cfg.theme.alpha.bg); + if (isNaN(a)) a = 0.6; + bg.style.opacity = a; + bg.style.backgroundImage = `url(${img.src})`; + img = null; + }); +} + +function loadSkinCSS(cId) +{ + if (!gId(cId)) // check if element exists + { + var h = document.getElementsByTagName('head')[0]; + var l = document.createElement('link'); + l.id = cId; + l.rel = 'stylesheet'; + l.type = 'text/css'; + l.href = (loc?`http://${locip}`:'.') + '/skin.css'; + l.media = 'all'; + h.appendChild(l); + } +} + +async function onLoad() +{ + if (window.location.protocol == "file:") { + loc = true; + locip = localStorage.getItem('locIp'); + if (!locip) + { + locip = prompt("File Mode. Please enter WLED IP!"); + localStorage.setItem('locIp', locip); + } + } + var sett = localStorage.getItem('wledUiCfg'); + if (sett) cfg = mergeDeep(cfg, JSON.parse(sett)); + + makeWS(); + + applyCfg(); + if (cfg.theme.bg.url=="" || cfg.theme.bg.url === "https://picsum.photos/1920/1080") { + var iUrl = cfg.theme.bg.url; + fetch((loc?`http://${locip}`:'.') + "/holidays.json", { + method: 'get' + }) + .then((res)=>{ + return res.json(); + }) + .then((json)=>{ + if (Array.isArray(json)) hol = json; + //TODO: do some parsing first + }) + .catch((e)=>{ + console.log("holidays.json does not contain array of holidays. Defaults loaded."); + }) + .finally(()=>{ + var today = new Date(); + for (var i=0; i=hs && today{ + setColor(1); + }); + pmtLS = localStorage.getItem('wledPmt'); + + // Load initial data + loadPalettes(()=>{ + loadPalettesData(redrawPalPrev); + loadFX(()=>{ + loadFXData(); + loadPresets(()=>{ + requestJson(); + }); + }); + }); + + d.addEventListener("visibilitychange", handleVisibilityChange, false); + size(); + gId("cv").style.opacity=0; + var sls = d.querySelectorAll('input[type="range"]'); + for (var sl of sls) { + sl.addEventListener('touchstart', toggleBubble); + sl.addEventListener('touchend', toggleBubble); + } +} + +var timeout; +function showToast(text, error = false) +{ + if (error) gId('connind').style.backgroundColor = "var(--c-r)"; + var x = gId("toast"); + x.innerHTML = text; + x.className = error ? "error":"show"; + clearTimeout(timeout); + x.style.animation = 'none'; + timeout = setTimeout(()=>{ x.classList.remove("show"); }, 2900); + if (error) console.log(text); +} + +function showErrorToast() +{ + if (ws && ws.readyState === WebSocket.OPEN) { + // if we received a timeout force WS reconnect + ws.close(); + ws = null; + if (lastinfo.ws > -1) setTimeout(makeWS,500); + } + showToast('Connection to light failed!', true); +} + +function clearErrorToast() {gId("toast").className = gId("toast").className.replace("error", "");} + +function getRuntimeStr(rt) +{ + var t = parseInt(rt); + var days = Math.floor(t/86400); + var hrs = Math.floor((t - days*86400)/3600); + var mins = Math.floor((t - days*86400 - hrs*3600)/60); + var str = days ? (days + " " + (days == 1 ? "day" : "days") + ", ") : ""; + str += (hrs || days) ? (hrs + " " + (hrs == 1 ? "hour" : "hours")) : ""; + if (!days && hrs) str += ", "; + if (t > 59 && !days) str += mins + " min"; + if (t < 3600 && t > 59) str += ", "; + if (t < 3600) str += (t - mins*60) + " sec"; + return str; +} + +function inforow(key, val, unit = "") +{ + return `${key}${val}${unit}`; +} + +function pName(i) +{ + var n = "Preset " + i; + if (pJson && pJson[i] && pJson[i].n) n = pJson[i].n; + return n; +} + +function isPlaylist(i) +{ + return pJson[i].playlist && pJson[i].playlist.ps; +} + +function papiVal(i) +{ + if (!pJson || !pJson[i]) return ""; + var o = Object.assign({},pJson[i]); + if (o.win) return o.win; + delete o.n; delete o.p; delete o.ql; + return JSON.stringify(o); +} + +function qlName(i) +{ + if (!pJson || !pJson[i] || !pJson[i].ql) return ""; + return pJson[i].ql; +} + +function cpBck() +{ + var copyText = gId("bck"); + + copyText.select(); + copyText.setSelectionRange(0, 999999); + d.execCommand("copy"); + showToast("Copied to clipboard!"); +} + +function loadPresets(callback = null) +{ + //1st boot (because there is a callback) + if (callback && pmt == pmtLS && pmt > 0) { + //we have a copy of the presets in local storage and don't need to fetch another one + pJson = JSON.parse(localStorage.getItem("wledP")); + populatePresets(); + pmtLast = pmt; + callback(); + return; + } + + //afterwards + if (!callback && pmt == pmtLast) return; + + pmtLast = pmt; + + var url = (loc?`http://${locip}`:'') + '/presets.json'; + + fetch(url, { + method: 'get' + }) + .then(res => { + if (!res.ok) showErrorToast(); + return res.json(); + }) + .then(json => { + clearErrorToast(); + pJson = json; + populatePresets(); + }) + .catch(function (error) { + showToast(error, true); + console.log(error); + }) + .finally(()=>{ + if (callback) setTimeout(callback,99); + }); +} + +function loadPalettes(callback = null) +{ + var url = (loc?`http://${locip}`:'') + '/json/palettes'; + + fetch(url, { + method: 'get' + }) + .then(res => { + if (!res.ok) showErrorToast(); + return res.json(); + }) + .then(json => { + clearErrorToast(); + lJson = Object.entries(json); + populatePalettes(); + }) + .catch(function (error) { + showToast(error, true); + }) + .finally(()=>{ + if (callback) callback(); + }); +} + +function loadFX(callback = null) +{ + var url = (loc?`http://${locip}`:'') + '/json/effects'; + + fetch(url, { + method: 'get' + }) + .then(res => { + if (!res.ok) showErrorToast(); + return res.json(); + }) + .then(json => { + clearErrorToast(); + eJson = Object.entries(json); + populateEffects(); + }) + .catch(function (error) { + showToast(error, true); + }) + .finally(()=>{ + if (callback) callback(); + }); +} + +function loadFXData(callback = null) +{ + var url = (loc?`http://${locip}`:'') + '/json/fxdata'; + + fetch(url, { + method: 'get' + }) + .then(res => { + if (!res.ok) showErrorToast(); + return res.json(); + }) + .then(json => { + clearErrorToast(); + fxdata = json||[]; + // add default value for Solid + fxdata.shift() + fxdata.unshift("@;!;"); + }) + .catch(function (error) { + fxdata = []; + showToast(error, true); + }) + .finally(()=>{ + if (callback) callback(); + updateUI(); + }); +} + +var pQL = []; +function populateQL() +{ + var cn = ""; + if (pQL.length > 0) { + pQL.sort((a,b) => (a[0]>b[0])); + for (var key of (pQL||[])) { + cn += ``; + } + } + gId('pql').innerHTML = cn; +} + +function populatePresets() +{ + if (!pJson) {pJson={};return}; + delete pJson["0"]; + var cn = ""; //`

All presets

`; + var arr = Object.entries(pJson); + arr.sort(cmpP); + pQL = []; + var is = []; + pNum = 0; + for (var key of (arr||[])) + { + if (!isObj(key[1])) continue; + let i = parseInt(key[0]); + var qll = key[1].ql; + if (qll) pQL.push([i, qll, pName(i)]); + is.push(i); + + cn += `
`; + //if (cfg.comp.pid) cn += `
${i}
`; + cn += `${isPlaylist(i)?"":""}${pName(i)}
`; + pNum++; + } + gId('pcont').innerHTML = cn; + updatePA(); + populateQL(); +} + +function parseInfo() { + var li = lastinfo; + var name = li.name; + gId('namelabel').innerHTML = name; +// if (name === "Dinnerbone") d.documentElement.style.transform = "rotate(180deg)"; + if (li.live) name = "(Live) " + name; + if (loc) name = "(L) " + name; + d.title = name; + isRgbw = li.leds.wv; + ledCount = li.leds.count; + syncTglRecv = li.str; + maxSeg = li.leds.maxseg; + pmt = li.fs.pmt; + cct = li.leds.cct; +} + +function populateInfo(i) +{ + var cn=""; + var heap = i.freeheap/1000; + heap = heap.toFixed(1); + var pwr = i.leds.pwr; + var pwru = "Not calculated"; + if (pwr > 1000) {pwr /= 1000; pwr = pwr.toFixed((pwr > 10) ? 0 : 1); pwru = pwr + " A";} + else if (pwr > 0) {pwr = 50 * Math.round(pwr/50); pwru = pwr + " mA";} + var urows=""; + if (i.u) { + for (const [k, val] of Object.entries(i.u)) { + if (val[1]) + urows += inforow(k,val[0],val[1]); + else + urows += inforow(k,val); + } + } + var vcn = "Kuuhaku"; + if (i.ver.startsWith("0.14.")) vcn = "Hoshi"; + if (i.ver.includes("-bl")) vcn = "Supāku"; + if (i.cn) vcn = i.cn; + + cn += `v${i.ver} "${vcn}"

+${urows} +${inforow("Build",i.vid)} +${inforow("Signal strength",i.wifi.signal +"% ("+ i.wifi.rssi, " dBm)")} +${inforow("Uptime",getRuntimeStr(i.uptime))} +${inforow("Free heap",heap," kB")} +${i.psram?inforow("Free PSRAM",(i.psram/1024).toFixed(1)," kB"):""} +${inforow("Estimated current",pwru)} +${inforow("Average FPS",i.leds.fps)} +${inforow("MAC address",i.mac)} +${inforow("Filesystem",i.fs.u + "/" + i.fs.t + " kB (" +Math.round(i.fs.u*100/i.fs.t) + "%)")} +${inforow("Environment",i.arch + " " + i.core + " (" + i.lwip + ")")} +
`; + gId('kv').innerHTML = cn; +} + +function populateSegments(s) +{ + var cn = ""; + segCount = (s.seg||[]).length; + lowestUnused = 0; lSeg = 0; + + if (segCount > 1) { + for (var y = 0; y < segCount && y<4; y++) + { + var inst=s.seg[y]; + let i = parseInt(inst.id); + powered[i] = inst.on; + if (i == lowestUnused) lowestUnused = i+1; + if (i > lSeg) lSeg = i; + + cn += +`
${(inst.n&&inst.n!=='')?inst.n:('Segment '+y)}
+
+ +
+ +
+ +
+
+ +
+
`; + } + //if (gId('buttonBri').className !== 'active') tglBri(true); + } else { + //tglBri(false); + } + //gId('buttonBri').style.display = (segCount > 1) ? "block" : "none"; + gId('segcont').innerHTML = cn; + for (var i = 0; i < segCount && i<4; i++) updateTrail(gId(`seg${i}bri`)); +} + +function btype(b) +{ + switch (b) { + case 2: + case 32: return "ESP32"; + case 1: + case 82: return "ESP8266"; + } + return "?"; +} + +function bname(o) +{ + if (o.name=="WLED") return o.ip; + return o.name; +} + +function populateNodes(i,n) +{ + var cn=""; + var urows=""; + var nnodes = 0; + if (n.nodes) { + n.nodes.sort((a,b) => (a.name).localeCompare(b.name)); + for (var x=0;x${bname(o)}`; + urows += inforow(url,`${btype(o.type)}
${o.vid==0?"N/A":o.vid}`); + nnodes++; + } + } + } + if (i.ndc < 0) cn += `Instance List is disabled.`; + else if (nnodes == 0) cn += `No other instances found.`; + cn += ` + ${urows} + ${inforow("Current instance:",i.name)} +
`; + gId('kn').innerHTML = cn; +} + +function loadNodes() +{ + var url = (loc?`http://${locip}`:'') + '/json/nodes'; + fetch(url, { + method: 'get' + }) + .then(res => { + if (!res.ok) showToast('Could not load Node list!', true); + return res.json(); + }) + .then(json => { + clearErrorToast(); + populateNodes(lastinfo, json); + }) + .catch(function (error) { + showToast(error, true); + console.log(error); + }); +} + +function populateEffects() +{ + var effects = eJson; + var html = ""; + + effects.shift(); //remove solid + for (let i = 0; i < effects.length; i++) effects[i] = {id: effects[i][0], name:effects[i][1]}; + effects.sort((a,b) => (a.name).localeCompare(b.name)); + effects.unshift({ + "id": 0, + "name": "Solid@;!;0" + }); + + for (let i = 0; i < effects.length; i++) { + // WLEDSR: add slider and color control to setEffect (used by requestjson) + if (effects[i].name.indexOf("RSVD") < 0) { + var posAt = effects[i].name.indexOf("@"); + var extra = ''; + if (posAt > 0) + extra = effects[i].name.substr(posAt); + else + posAt = 999; + html += generateListItemHtml( + 'fx', + effects[i].id, + effects[i].name.substr(0,posAt), + 'setEffect', + '','', + extra + ); + } + } + gId('fxlist').innerHTML=html; +} + +function populatePalettes() +{ + var palettes = lJson; + palettes.shift(); //remove default + for (let i = 0; i < palettes.length; i++) { + palettes[i] = { + "id": palettes[i][0], + "name": palettes[i][1] + }; + } + palettes.sort((a,b) => (a.name).localeCompare(b.name)); + palettes.unshift({ + "id": 0, + "name": "Default", + }); + var html = ""; + for (let i = 0; i < palettes.length; i++) { + html += generateListItemHtml( + 'palette', + palettes[i].id, + palettes[i].name, + 'setPalette', + `
` + ); + } + gId('pallist').innerHTML=html; +} + +function redrawPalPrev() +{ + let palettes = d.querySelectorAll('#pallist .lstI'); + for (let i = 0; i < palettes.length; i++) { + let id = palettes[i].dataset.id; + let lstPrev = palettes[i].querySelector('.lstIprev'); + if (lstPrev) { + lstPrev.style = genPalPrevCss(id); + } + } +} + +function genPalPrevCss(id) +{ + if (!palettesData) return; + + var paletteData = palettesData[id]; + var previewCss = ""; + + if (!paletteData) return 'display: none'; + + // We need at least two colors for a gradient + if (paletteData.length == 1) { + paletteData[1] = paletteData[0]; + if (Array.isArray(paletteData[1])) { + paletteData[1][0] = 255; + } + } + + var gradient = []; + for (let j = 0; j < paletteData.length; j++) { + const element = paletteData[j]; + let r; + let g; + let b; + let index = false; + if (Array.isArray(element)) { + index = element[0]/255*100; + r = element[1]; + g = element[2]; + b = element[3]; + } else if (element == 'r') { + r = Math.random() * 255; + g = Math.random() * 255; + b = Math.random() * 255; + } else { + if (selColors) { + let e = element[1] - 1; + r = selColors[e][0]; + g = selColors[e][1]; + b = selColors[e][2]; + } + } + if (index === false) { + index = j / paletteData.length * 100; + } + + gradient.push(`rgb(${r},${g},${b}) ${index}%`); + } + + return `background: linear-gradient(to right,${gradient.join()});`; +} + +function generateOptionItemHtml(id, name) +{ + return ``; +} + +function generateListItemHtml(listName, id, name, clickAction, extraHtml = '', extraClass = '', extraPar = '') +{ + return `
+
+ + ${name} + +
+ ${extraHtml} +
`; +} + +//update the 'sliderdisplay' background div of a slider for a visual indication of slider position +function updateTrail(e) +{ + if (e==null) return; + var max = e.hasAttribute('max') ? e.attributes.max.value : 255; + var perc = e.value * 100 / max; + perc = parseInt(perc); + if (perc < 50) perc += 2; + var val = `linear-gradient(90deg, var(--c-f) ${perc}%, var(--c-4) ${perc}%)`; + e.parentNode.getElementsByClassName('sliderdisplay')[0].style.background = val; + var b = e.parentNode.parentNode.getElementsByTagName('output')[0]; + if (b) b.innerHTML = e.value; +} + +//rangetouch slider function +function toggleBubble(e) +{ + var b = e.target.parentNode.parentNode.getElementsByTagName('output')[0]; + b.classList.toggle('sliderbubbleshow'); +} + +function updatePA() +{ + var ps = gEBCN("pres"); + for (let i = 0; i < ps.length; i++) { + ps[i].classList.remove('selected');; + } + ps = gEBCN("psts"); + for (let i = 0; i < ps.length; i++) { + ps[i].classList.remove('selected');; + } + if (currentPreset > 0) { + var acv = gId(`p${currentPreset}o`); + if (acv) acv.classList.add('selected'); + acv = gId(`p${currentPreset}qlb`); + if (acv) acv.classList.add('selected'); + } +} + +function updateUI() +{ + gId('buttonPower').className = (isOn) ? "active":""; + + var sel = 0; + if (lJson && lJson.length) { + for (var i=0; i b[0]); + // playlists follow presets + var name = (a[1].playlist ? '~' : ' ') + a[1].n; + return name.localeCompare((b[1].playlist ? '~' : ' ') + b[1].n, undefined, {numeric: true}); +} + +function makeWS() { + if (ws) return; + ws = new WebSocket('ws://'+(loc?locip:window.location.hostname)+'/ws'); + ws.onmessage = (e)=>{ + var json = JSON.parse(e.data); + if (json.leds) return; //liveview packet + clearTimeout(jsonTimeout); + jsonTimeout = null; + lastUpdate = new Date(); + clearErrorToast(); + gId('connind').style.backgroundColor = "var(--c-l)"; + // json object should contain json.info AND json.state (but may not) + var i = json.info; + if (i) { + lastinfo = i; + parseInfo(); + if (isInfo) populateInfo(i); + } else + i = lastinfo; + var s = json.state ? json.state : json; + readState(s); + }; + ws.onclose = (e)=>{ + gId('connind').style.backgroundColor = "var(--c-r)"; + ws = null; + if (lastinfo.ws > -1) setTimeout(makeWS,500); + } + ws.onopen = (e)=>{ + ws.send("{'v':true}"); + reqsLegal = true; + clearErrorToast(); + } +} + +function readState(s,command=false) +{ + if (!s) return false; + + isOn = s.on; + gId('sliderBri').value= s.bri; + nlA = s.nl.on; + nlDur = s.nl.dur; + nlTar = s.nl.tbri; + nlFade = s.nl.fade; + syncSend = s.udpn.send; + if (s.pl<0) currentPreset = s.ps; + else currentPreset = s.pl; + tr = s.transition/10; + + var selc=0; var ind=0; + populateSegments(s); + for (let i = 0; i < (s.seg||[]).length; i++) + { + if(s.seg[i].sel) {selc = ind; break;} ind++; + } + var i=s.seg[selc]; + if (!i) { + showToast('No Segments!', true); + updateUI(); + return; + } + + selColors = i.col; + var cd = gId('csl').children; + for (let e = cd.length-1; e >= 0; e--) + { + var r,g,b,w; + r = i.col[e][0]; + g = i.col[e][1]; + b = i.col[e][2]; + if (isRgbw) w = i.col[e][3]; + cd[e].style.backgroundColor = "rgb(" + r + "," + g + "," + b + ")"; + if (isRgbw) whites[e] = parseInt(w); + selectSlot(csel); + } + gId('sliderW').value = whites[csel]; + if (i.cct && i.cct>=0) gId("sliderA").value = i.cct; + + gId('sliderSpeed').value = i.sx; + gId('sliderIntensity').value = i.ix; +/* + gId('sliderC1').value = i.f1x ? i.f1x : 0; + gId('sliderC2').value = i.f2x ? i.f2x : 0; + gId('sliderC3').value = i.f3x ? i.f3x : 0; +*/ + if (s.error && s.error != 0) { + var errstr = ""; + switch (s.error) { + case 10: + errstr = "Could not mount filesystem!"; + break; + case 11: + errstr = "Not enough space to save preset!"; + break; + case 12: + errstr = "Preset not found."; + break; + case 13: + errstr = "Missing IR.json."; + break; + case 19: + errstr = "A filesystem error has occured."; + break; + } + showToast('Error ' + s.error + ": " + errstr, true); + } + + selectedPal = i.pal; + selectedFx = i.fx; + updateUI(); +} + +var jsonTimeout; +var reqsLegal = false; + +function requestJson(command=null) +{ + gId('connind').style.backgroundColor = "var(--c-r)"; + if (command && !reqsLegal) return; //stop post requests from chrome onchange event on page restore + if (!jsonTimeout) jsonTimeout = setTimeout(showErrorToast, 3000); + var req = null; + var url = (loc?`http://${locip}`:'') + '/json/si'; + var useWs = (ws && ws.readyState === WebSocket.OPEN); + var type = command ? 'post':'get'; + if (command) { + if (useWs || !command.ps) command.v = true; // force complete /json/si API response + command.time = Math.floor(Date.now() / 1000); + req = JSON.stringify(command); + if (req.length > 1000) useWs = false; //do not send very long requests over websocket + }; + + if (useWs) { + ws.send(req?req:'{"v":true}'); + return; + } else if (command && command.ps) { //refresh UI if we don't use WS (async loading of presets) + setTimeout(requestJson,200); + } + + fetch(url, { + method: type, + headers: { + "Content-type": "application/json; charset=UTF-8" + }, + body: req + }) + .then(res => { + if (!res.ok) showErrorToast(); + return res.json(); + }) + .then(json => { + clearTimeout(jsonTimeout); + jsonTimeout = null; + lastUpdate = new Date(); + clearErrorToast(); + gId('connind').style.backgroundColor = "var(--c-g)"; + if (!json) { showToast('Empty response', true); return; } + if (json.success) return; + if (json.info) { + lastinfo = json.info; + parseInfo(); + if (isInfo) populateInfo(lastinfo); + } + var s = json.state ? json.state : json; + readState(s); + reqsLegal = true; + }) + .catch(function (error) { + showToast(error, true); + console.log(error); + }); +} + +function togglePower() +{ + isOn = !isOn; + var obj = {"on": isOn}; + requestJson(obj); +} + +function toggleInfo() +{ + if (isNodes) toggleNodes(); + isInfo = !isInfo; + if (isInfo) requestJson(); + gId('info').style.transform = (isInfo) ? "translateY(0px)":"translateY(100%)"; + gId('buttonI').className = (isInfo) ? "active":""; +} + +function toggleNodes() +{ + if (isInfo) toggleInfo(); + isNodes = !isNodes; + if (isNodes) loadNodes(); + gId('nodes').style.transform = (isNodes) ? "translateY(0px)":"translateY(100%)"; + gId('buttonNodes').className = (isNodes) ? "active":""; +} +/* +function tglBri(b=null) +{ + if (b===null) b = gId(`briwrap`).style.display === "block"; + gId('briwrap').style.display = !b ? "block":"none"; + gId('buttonBri').className = !b ? "active":""; + size(); +} +*/ +function tglCP() +{ + var p = gId('buttonCP').className === "active"; + gId('buttonCP').className = !p ? "active":""; + gId('picker').style.display = !p ? "block":"none"; + gId('vwrap').style.display = !p ? "block":"none"; + gId('rgbwrap').style.display = !p ? "block":"none"; + var csl = gId('Slots').style.display === "block"; + gId('Slots').style.display = !csl ? "block":"none"; + //var ps = gId(`Presets`).style.display === "block"; + //gId('Presets').style.display = !ps ? "block":"none"; +} + +function tglCs(i) +{ + var pss = gId(`p${i}cstgl`).checked; + gId(`p${i}o1`).style.display = pss? "block" : "none"; + gId(`p${i}o2`).style.display = !pss? "block" : "none"; +} + +function selSeg(s) +{ + var sel = gId(`seg${s}sel`).checked; + var obj = {"seg": {"id": s, "sel": sel}}; + requestJson(obj); +} + +function tglPalDropdown() +{ + var p = gId('palDropdown').style; + p.display = (p.display==='block'?'none':'block'); + gId('fxDropdown').style.display = 'none'; + if (p.display==='block') + gId('palDropdown').scrollIntoView({ + behavior: 'smooth', + block: 'center', + }); +} + +function tglFxDropdown() +{ + var p = gId('fxDropdown').style; + p.display = (p.display==='block'?'none':'block'); + gId('palDropdown').style.display = 'none'; + if (p.display==='block') + gId('fxDropdown').scrollIntoView({ + behavior: 'smooth', + block: 'center', + }); +} + +function setSegPwr(s) +{ + var obj = {"seg": {"id": s, "on": !powered[s]}}; + requestJson(obj); +} + +function setSegBri(s) +{ + var obj = {"seg": {"id": s, "bri": parseInt(gId(`seg${s}bri`).value)}}; + requestJson(obj); +} + +function setEffect(ind = 0) +{ + tglFxDropdown(); + var obj = {"seg": {"fx": parseInt(ind), "fxdef":true}}; // fxdef sets effect parameters to default values, TODO add client setting + requestJson(obj); +} + +function setPalette(paletteId = null) +{ + tglPalDropdown(); + var obj = {"seg": {"pal": paletteId}}; + requestJson(obj); +} + +function setBri() +{ + var obj = {"bri": parseInt(gId('sliderBri').value)}; + requestJson(obj); +} + +function setSpeed() +{ + var obj = {"seg": {"sx": parseInt(gId('sliderSpeed').value)}}; + requestJson(obj); +} + +function setIntensity() +{ + var obj = {"seg": {"ix": parseInt(gId('sliderIntensity').value)}}; + requestJson(obj); +} + +function setLor(i) +{ + var obj = {"lor": i}; + requestJson(obj); +} + +function setPreset(i) +{ + var obj = {"ps": i}; + if (isPlaylist(i)) obj.on = true; + showToast("Loading preset " + pName(i) +" (" + i + ")"); + requestJson(obj); +} + +function selectSlot(b) +{ + csel = b; + var cd = gId('csl').children; + for (let i = 0; i < cd.length; i++) cd[i].classList.remove('xxs-w'); + cd[b].classList.add('xxs-w'); + setPicker(cd[b].style.backgroundColor); + gId('sliderW').value = whites[b]; + redrawPalPrev(); + updatePSliders(); +} + +var lasth = 0; +function pC(col) +{ + if (col == "rnd") { + col = {h: 0, s: 0, v: 100}; + col.s = Math.floor((Math.random() * 50) + 50); + do { + col.h = Math.floor(Math.random() * 360); + } while (Math.abs(col.h - lasth) < 50); + lasth = col.h; + } + setPicker(col); + setColor(0); +} + +function updatePSliders() { + //update RGB sliders + var col = cpick.color.rgb; + gId('sliderR').value = col.r; + gId('sliderG').value = col.g; + gId('sliderB').value = col.b; + + //update hex field + var str = cpick.color.hexString.substring(1); + var w = whites[csel]; + if (w > 0) str += w.toString(16); + + //update value slider + var v = gId('sliderV'); + v.value = cpick.color.value; + //background color as if color had full value + var hsv = {"h":cpick.color.hue,"s":cpick.color.saturation,"v":100}; + var c = iro.Color.hsvToRgb(hsv); + var cs = 'rgb('+c.r+','+c.g+','+c.b+')'; + v.nextElementSibling.style.backgroundImage = `linear-gradient(90deg, #000 0%, ${cs})`; + + //update Kelvin slider + gId('sliderK').value = cpick.color.kelvin; +} + +function setPicker(rgb) { + var c = new iro.Color(rgb); + if (c.value > 0) cpick.color.set(c); + else cpick.color.setChannel('hsv', 'v', 0); +} + +function fromV() +{ + cpick.color.setChannel('hsv', 'v', d.getElementById('sliderV').value); +} + +function fromK() +{ + cpick.color.set({ kelvin: d.getElementById('sliderK').value }); +} + +function fromRgb() +{ + var r = gId('sliderR').value; + var g = gId('sliderG').value; + var b = gId('sliderB').value; + setPicker(`rgb(${r},${g},${b})`); + setColor(0); +} + +// sets color from picker: 0=all, 1=leaving picker/HSV, 2=ignore white channel +function setColor(sr) +{ + var cd = gId('csl').children; // color slots + if (sr == 1 && cd[csel].style.backgroundColor == 'rgb(0, 0, 0)') cpick.color.setChannel('hsv', 'v', 100); + cd[csel].style.backgroundColor = cpick.color.rgbString; + if (sr != 2) whites[csel] = parseInt(gId('sliderW').value); + var col = cpick.color.rgb; + var obj = {"seg": {"col": [[col.r, col.g, col.b, whites[csel]],[],[]]}}; + if (sr==1 || gId(`picker`).style.display !== "block") obj.seg.fx = 0; + if (csel == 1) { + obj = {"seg": {"col": [[],[col.r, col.g, col.b, whites[csel]],[]]}}; + } else if (csel == 2) { + obj = {"seg": {"col": [[],[],[col.r, col.g, col.b, whites[csel]]]}}; + } + requestJson(obj); +} + +function setBalance(b) +{ + var obj = {"seg": {"cct": parseInt(b)}}; + requestJson(obj); +} + +var hc = 0; +setInterval(()=>{if (!isInfo) return; hc+=18; if (hc>300) hc=0; if (hc>200)hc=306; if (hc==144) hc+=36; if (hc==108) hc+=18; +gId('heart').style.color = `hsl(${hc}, 100%, 50%)`;}, 910); + +function openGH() { window.open("https://github.com/Aircoookie/WLED/wiki"); } + +var cnfr = false; +function cnfReset() +{ + if (!cnfr) { + var bt = gId('resetbtn'); + bt.style.color = "#f00"; + bt.innerHTML = "Confirm Reboot"; + cnfr = true; return; + } + window.location.href = "/reset"; +} + +function loadPalettesData(callback = null) +{ + if (palettesData) return; + const lsKey = "wledPalx"; + var palettesDataJson = localStorage.getItem(lsKey); + if (palettesDataJson) { + try { + palettesDataJson = JSON.parse(palettesDataJson); + if (palettesDataJson && palettesDataJson.vid == lastinfo.vid) { + palettesData = palettesDataJson.p; + if (callback) callback(); //redrawPalPrev() + return; + } + } catch (e) {} + } + + palettesData = {}; + getPalettesData(0, ()=>{ + localStorage.setItem(lsKey, JSON.stringify({ + p: palettesData, + vid: lastinfo.vid + })); + if (callback) setTimeout(callback, 99); //redrawPalPrev() + }); +} + +function getPalettesData(page, callback) +{ + var url = (loc?`http://${locip}`:'') + `/json/palx?page=${page}`; + + fetch(url, { + method: 'get', + headers: { + "Content-type": "application/json; charset=UTF-8" + } + }) + .then((res)=>{ + if (!res.ok) showErrorToast(); + return res.json(); + }) + .then((json)=>{ + palettesData = Object.assign({}, palettesData, json.p); + if (page < json.m) setTimeout(()=>{ getPalettesData(page + 1, callback); }, 50); + else callback(); + }) + .catch((e)=>{ + showToast(e, true); + }); +} + +function search(f,l=null) +{ + f.nextElementSibling.style.display=(f.value!=='')?'block':'none'; + if (!l) return; + var el = gId(l).querySelectorAll('.lstI'); + for (i = 0; i < el.length; i++) { + var it = el[i]; + var itT = it.querySelector('.lstIname').innerText.toUpperCase(); + it.style.display = itT.indexOf(f.value.toUpperCase())>-1?'':'none'; + } +} + +function clean(c) +{ + c.style.display='none'; + var i=c.previousElementSibling; + i.value=''; + i.focus(); + i.dispatchEvent(new Event('input')); +} + +function unfocusSliders() +{ + gId("sliderBri").blur(); + gId("sliderSpeed").blur(); + gId("sliderIntensity").blur(); +} + +//sliding UI +const _C = d.querySelector('.container'), N = 1; + +let iSlide = 0, x0 = null, scrollS = 0, locked = false, w; + +function unify(e) { return e.changedTouches ? e.changedTouches[0] : e; } + +function hasIroClass(classList) +{ + for (var i = 0; i < classList.length; i++) { + var element = classList[i]; + if (element.startsWith('Iro')) return true; + } + return false; +} + +function lock(e) +{ + var l = e.target.classList; + var pl = e.target.parentElement.classList; + + if (l.contains('noslide') || hasIroClass(l) || hasIroClass(pl)) return; + + x0 = unify(e).clientX; + scrollS = gEBCN("tabcontent")[iSlide].scrollTop; + + _C.classList.toggle('smooth', !(locked = true)); +} + +function move(e) +{ + if(!locked) return; + var clientX = unify(e).clientX; + var dx = clientX - x0; + var s = Math.sign(dx); + var f = +(s*dx/w).toFixed(2); + + if((clientX != 0) && + (iSlide > 0 || s < 0) && (iSlide < N - 1 || s > 0) && + f > 0.12 && + gEBCN("tabcontent")[iSlide].scrollTop == scrollS) + { + _C.style.setProperty('--i', iSlide -= s); + f = 1 - f; + updateTablinks(iSlide); + } + _C.style.setProperty('--f', f); + _C.classList.toggle('smooth', !(locked = false)); + x0 = null; +} + +function size() +{ + var h = gId('top').clientHeight; + sCol('--th', h + "px"); + sCol("--tp", h - (gId(`briwrap`).style.display === "block" ? 0 : gId(`briwrap`).clientTop) + "px"); + sCol("--bh", "0px"); +} + +function mergeDeep(target, ...sources) +{ + if (!sources.length) return target; + const source = sources.shift(); + + if (isObj(target) && isObj(source)) { + for (const key in source) { + if (isObj(source[key])) { + if (!target[key]) Object.assign(target, { [key]: {} }); + mergeDeep(target[key], source[key]); + } else { + Object.assign(target, { [key]: source[key] }); + } + } + } + return mergeDeep(target, ...sources); +} + +size(); +window.addEventListener('resize', size, false); + +_C.addEventListener('mousedown', lock, false); +_C.addEventListener('touchstart', lock, false); + +_C.addEventListener('mouseout', move, false); +_C.addEventListener('mouseup', move, false); +_C.addEventListener('touchend', move, false); diff --git a/wled00/data/style.css b/wled00/data/style.css index 672965133..b6816d512 100644 --- a/wled00/data/style.css +++ b/wled00/data/style.css @@ -1,84 +1,131 @@ +html { + touch-action: manipulation; +} body { - font-family: Verdana, sans-serif; - text-align: center; - background: #222; - color: #fff; - line-height: 200%%; /* %% because of AsyncWebServer */ - margin: 0; + font-family: Verdana, sans-serif; + font-size: 1rem; + text-align: center; + background: #222; + color: #fff; + line-height: 200%; + margin: 0; } hr { - border-color: #666; + border-color: #666; } -a { - color: #28f; - text-decoration: none; +a, a:hover { + color: #28f; + text-decoration: none; } button, .btn { - background: #333; - color: #fff; - font-family: Verdana, sans-serif; - border: 0.3ch solid #333; - display: inline-block; - font-size: 20px; - margin: 12px 8px 8px; - padding: 1px 6px; - cursor: pointer; - text-decoration: none; + background: #333; + color: #fff; + font-family: Verdana, sans-serif; + border: 0.3ch solid #333; + border-radius: 24px; + display: inline-block; + font-size: 20px; + margin: 12px 8px 8px; + padding: 8px 12px; + min-width: 48px; + cursor: pointer; + text-decoration: none; +} +button.sml { + padding: 8px; + border-radius: 20px; + font-size: 15px; + min-width: 40px; + margin: 0 0 0 10px; +} +.toprow { + top: 0; + position: sticky; + background-color:#222; + z-index:1; } .lnk { - border: 0; + border: 0; } .helpB { - text-align: left; - position: absolute; - width: 60px; + text-align: left; + position: absolute; + width: 60px; } input { - background: #333; - color: #fff; - font-family: Verdana, sans-serif; - border: 0.5ch solid #333; + background: #333; + color: #fff; + font-family: Verdana, sans-serif; + border: 0.5ch solid #333; } input:disabled { - color: #888; + color: #888; } +input[type="text"], +input[type="number"], +select { + font-size: medium; + margin: 2px; + } input[type="number"] { - width: 4em; - margin: 2px; + width: 4em; } input[type="number"].xxl { - width: 100px; + width: 100px; } input[type="number"].xl { - width: 85px; + width: 85px; } input[type="number"].l { - width: 63px; + width: 64px; } input[type="number"].m { - width: 56px; + width: 56px; } input[type="number"].s { - width: 49px; + width: 48px; } input[type="number"].xs { - width: 42px; + width: 40px; } input[type="checkbox"] { - transform: scale(1.5); + transform: scale(1.5); + margin-right: 10px; +} +td input[type="checkbox"] { + margin-right: revert; +} +input[type=file] { + font-size: 16px } select { - background: #333; - color: #fff; - font-family: Verdana, sans-serif; - border: 0.5ch solid #333; + margin: 2px; + background: #333; + color: #fff; + font-family: Verdana, sans-serif; + border: 0.5ch solid #333; +} +tr { + line-height: 100%; } td { - padding: 2px; + padding: 2px; } .d5 { - width: 4.5em !important; + width: 4rem !important; } +.cal { + font-size:1.5rem; + cursor:pointer +} +#TMT table { + width: 100%; +} + +#msg { + display: none; +} + #toast { opacity: 0; background-color: #444; @@ -91,9 +138,9 @@ td { position: fixed; text-align: center; z-index: 5; - transform: translateX(-50%%); /* %% because of AsyncWebServer */ - max-width: 90%%; /* %% because of AsyncWebServer */ - left: 50%%; /* %% because of AsyncWebServer */ + transform: translateX(-50%); + max-width: 90%; + left: 50%; } #toast.show { @@ -107,3 +154,29 @@ td { background-color: #b21; animation: fadein 0.5s; } + +@media screen and (max-width: 767px) { + input[type="text"], + input[type="file"], + input[type="number"], + input[type="email"], + input[type="tel"], + input[type="password"] { + font-size: 16px; + } +} + +@media screen and (max-width: 480px) { + input[type="number"].s { + width: 40px; + } + input[type="number"].xs { + width: 32px; + } + input[type="file"] { + width: 224px; + } + #btns select { + width: 144px; + } +} \ No newline at end of file diff --git a/wled00/data/update.htm b/wled00/data/update.htm index 16dede5cb..184ab9481 100644 --- a/wled00/data/update.htm +++ b/wled00/data/update.htm @@ -1,52 +1,28 @@ - - - WLED Update - - + + WLED Update + + - -

WLED Software Update

-
- Installed version: ##VERSION##
- Download the latest binary: -
-
-
-
-
Updating...
Please do not close or refresh the page :)
+ +

WLED Software Update

+
+ Installed version: ##VERSION##
+ Download the latest binary: +
+
+
+ +
+
Updating...
Please do not close or refresh the page :)
- \ No newline at end of file diff --git a/wled00/e131.cpp b/wled00/e131.cpp index 4ac2081cb..cce5233ae 100644 --- a/wled00/e131.cpp +++ b/wled00/e131.cpp @@ -25,19 +25,21 @@ void handleDDPPacket(e131_packet_t* p) { } } - uint32_t start = htonl(p->channelOffset) /3; - start += DMXAddress /3; - uint16_t stop = start + htons(p->dataLen) /3; + uint8_t ddpChannelsPerLed = (p->dataType == DDP_TYPE_RGBW32) ? 4 : 3; // data type 0x1A is RGBW (type 3, 8 bit/channel) + + uint32_t start = htonl(p->channelOffset) / ddpChannelsPerLed; + start += DMXAddress / ddpChannelsPerLed; + uint16_t stop = start + htons(p->dataLen) / ddpChannelsPerLed; uint8_t* data = p->data; uint16_t c = 0; if (p->flags & DDP_TIMECODE_FLAG) c = 4; //packet has timecode flag, we do not support it, but data starts 4 bytes later realtimeLock(realtimeTimeoutMs, REALTIME_MODE_DDP); - if (!realtimeOverride) { + if (!realtimeOverride || (realtimeMode && useMainSegmentOnly)) { for (uint16_t i = start; i < stop; i++) { - setRealtimePixel(i, data[c], data[c+1], data[c+2], 0); - c+=3; + setRealtimePixel(i, data[c], data[c+1], data[c+2], ddpChannelsPerLed >3 ? data[c+3] : 0); + c += ddpChannelsPerLed; } } @@ -58,6 +60,10 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){ if (protocol == P_ARTNET) { + if (p->art_opcode == ARTNET_OPCODE_OPPOLL) { + handleArtnetPollReply(clientIP); + return; + } uni = p->art_universe; dmxChannels = htons(p->art_length); e131_data = p->art_data; @@ -84,14 +90,14 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){ #endif // only listen for universes we're handling & allocated memory - if (uni >= (e131Universe + E131_MAX_UNIVERSE_COUNT)) return; + if (uni < e131Universe || uni >= (e131Universe + E131_MAX_UNIVERSE_COUNT)) return; uint8_t previousUniverses = uni - e131Universe; if (e131SkipOutOfSequence) - if (seq < e131LastSequenceNumber[uni-e131Universe] && seq > 20 && e131LastSequenceNumber[uni-e131Universe] < 250){ + if (seq < e131LastSequenceNumber[previousUniverses] && seq > 20 && e131LastSequenceNumber[previousUniverses] < 250){ DEBUG_PRINT("skipping E1.31 frame (last seq="); - DEBUG_PRINT(e131LastSequenceNumber[uni-e131Universe]); + DEBUG_PRINT(e131LastSequenceNumber[previousUniverses]); DEBUG_PRINT(", current seq="); DEBUG_PRINT(seq); DEBUG_PRINT(", universe="); @@ -99,15 +105,23 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){ DEBUG_PRINTLN(")"); return; } - e131LastSequenceNumber[uni-e131Universe] = seq; + e131LastSequenceNumber[previousUniverses] = seq; // update status info realtimeIP = clientIP; byte wChannel = 0; uint16_t totalLen = strip.getLengthTotal(); - uint16_t availDMXLen = dmxChannels - DMXAddress + 1; + uint16_t availDMXLen = 0; uint16_t dataOffset = DMXAddress; + // For legacy DMX start address 0 the available DMX length offset is 0 + const uint16_t dmxLenOffset = (DMXAddress == 0) ? 0 : 1; + + // Check if DMX start address fits in available channels + if (dmxChannels >= DMXAddress) { + availDMXLen = (dmxChannels - DMXAddress) + dmxLenOffset; + } + // DMX data in Art-Net packet starts at index 0, for E1.31 at index 1 if (protocol == P_ARTNET && dataOffset > 0) { dataOffset--; @@ -121,8 +135,11 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){ case DMX_MODE_SINGLE_RGB: // RGB only if (uni != e131Universe) return; if (availDMXLen < 3) return; + realtimeLock(realtimeTimeoutMs, mde); - if (realtimeOverride) return; + + if (realtimeOverride && !(realtimeMode && useMainSegmentOnly)) return; + wChannel = (availDMXLen > 3) ? e131_data[dataOffset+3] : 0; for (uint16_t i = 0; i < totalLen; i++) setRealtimePixel(i, e131_data[dataOffset+0], e131_data[dataOffset+1], e131_data[dataOffset+2], wChannel); @@ -131,14 +148,16 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){ case DMX_MODE_SINGLE_DRGB: // Dimmer + RGB if (uni != e131Universe) return; if (availDMXLen < 4) return; + realtimeLock(realtimeTimeoutMs, mde); - if (realtimeOverride) return; + if (realtimeOverride && !(realtimeMode && useMainSegmentOnly)) return; wChannel = (availDMXLen > 4) ? e131_data[dataOffset+4] : 0; - if (DMXOldDimmer != e131_data[dataOffset+0]) { - DMXOldDimmer = e131_data[dataOffset+0]; + + if (bri != e131_data[dataOffset+0]) { bri = e131_data[dataOffset+0]; strip.setBrightness(bri, true); } + for (uint16_t i = 0; i < totalLen; i++) setRealtimePixel(i, e131_data[dataOffset+1], e131_data[dataOffset+2], e131_data[dataOffset+3], wChannel); break; @@ -150,12 +169,12 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){ applyPreset(e131_data[dataOffset+0], CALL_MODE_NOTIFICATION); return; } - if (DMXOldDimmer != e131_data[dataOffset+0]) { - DMXOldDimmer = e131_data[dataOffset+0]; + + if (bri != e131_data[dataOffset+0]) { bri = e131_data[dataOffset+0]; } - if (e131_data[dataOffset+1] < MODE_COUNT) - effectCurrent = e131_data[dataOffset+ 1]; + if (e131_data[dataOffset+1] < strip.getModeCount()) + effectCurrent = e131_data[dataOffset+ 1]; effectSpeed = e131_data[dataOffset+ 2]; // flickers effectIntensity = e131_data[dataOffset+ 3]; effectPalette = e131_data[dataOffset+ 4]; @@ -179,19 +198,19 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){ case DMX_MODE_MULTIPLE_RGB: case DMX_MODE_MULTIPLE_RGBW: { - realtimeLock(realtimeTimeoutMs, mde); bool is4Chan = (DMXMode == DMX_MODE_MULTIPLE_RGBW); const uint16_t dmxChannelsPerLed = is4Chan ? 4 : 3; const uint16_t ledsPerUniverse = is4Chan ? MAX_4_CH_LEDS_PER_UNIVERSE : MAX_3_CH_LEDS_PER_UNIVERSE; - if (realtimeOverride) return; + uint8_t stripBrightness = bri; uint16_t previousLeds, dmxOffset, ledsTotal; + if (previousUniverses == 0) { if (availDMXLen < 1) return; dmxOffset = dataOffset; previousLeds = 0; // First DMX address is dimmer in DMX_MODE_MULTIPLE_DRGB mode. if (DMXMode == DMX_MODE_MULTIPLE_DRGB) { - strip.setBrightness(e131_data[dmxOffset++], true); + stripBrightness = e131_data[dmxOffset++]; ledsTotal = (availDMXLen - 1) / dmxChannelsPerLed; } else { ledsTotal = availDMXLen / dmxChannelsPerLed; @@ -199,11 +218,31 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){ } else { // All subsequent universes start at the first channel. dmxOffset = (protocol == P_ARTNET) ? 0 : 1; - uint16_t dimmerOffset = (DMXMode == DMX_MODE_MULTIPLE_DRGB) ? 1 : 0; - uint16_t ledsInFirstUniverse = ((MAX_CHANNELS_PER_UNIVERSE - DMXAddress + 1) - dimmerOffset) / dmxChannelsPerLed; + const uint16_t dimmerOffset = (DMXMode == DMX_MODE_MULTIPLE_DRGB) ? 1 : 0; + uint16_t ledsInFirstUniverse = (((MAX_CHANNELS_PER_UNIVERSE - DMXAddress) + dmxLenOffset) - dimmerOffset) / dmxChannelsPerLed; previousLeds = ledsInFirstUniverse + (previousUniverses - 1) * ledsPerUniverse; ledsTotal = previousLeds + (dmxChannels / dmxChannelsPerLed); } + + // All LEDs already have values + if (previousLeds >= totalLen) { + return; + } + + realtimeLock(realtimeTimeoutMs, mde); + if (realtimeOverride && !(realtimeMode && useMainSegmentOnly)) return; + + if (ledsTotal > totalLen) { + ledsTotal = totalLen; + } + + if (DMXMode == DMX_MODE_MULTIPLE_DRGB && previousUniverses == 0) { + if (bri != stripBrightness) { + bri = stripBrightness; + strip.setBrightness(bri, true); + } + } + if (!is4Chan) { for (uint16_t i = previousLeds; i < ledsTotal; i++) { setRealtimePixel(i, e131_data[dmxOffset], e131_data[dmxOffset+1], e131_data[dmxOffset+2], 0); @@ -225,3 +264,197 @@ void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol){ e131NewData = true; } + +void handleArtnetPollReply(IPAddress ipAddress) { + ArtPollReply artnetPollReply; + prepareArtnetPollReply(&artnetPollReply); + + uint16_t startUniverse = e131Universe; + uint16_t endUniverse = e131Universe; + + switch (DMXMode) { + case DMX_MODE_DISABLED: + return; // nothing to do + break; + + case DMX_MODE_SINGLE_RGB: + case DMX_MODE_SINGLE_DRGB: + case DMX_MODE_EFFECT: + break; // 1 universe is enough + + case DMX_MODE_MULTIPLE_DRGB: + case DMX_MODE_MULTIPLE_RGB: + case DMX_MODE_MULTIPLE_RGBW: + { + bool is4Chan = (DMXMode == DMX_MODE_MULTIPLE_RGBW); + const uint16_t dmxChannelsPerLed = is4Chan ? 4 : 3; + const uint16_t dimmerOffset = (DMXMode == DMX_MODE_MULTIPLE_DRGB) ? 1 : 0; + const uint16_t dmxLenOffset = (DMXAddress == 0) ? 0 : 1; // For legacy DMX start address 0 + const uint16_t ledsInFirstUniverse = (((MAX_CHANNELS_PER_UNIVERSE - DMXAddress) + dmxLenOffset) - dimmerOffset) / dmxChannelsPerLed; + const uint16_t totalLen = strip.getLengthTotal(); + + if (totalLen > ledsInFirstUniverse) { + const uint16_t ledsPerUniverse = is4Chan ? MAX_4_CH_LEDS_PER_UNIVERSE : MAX_3_CH_LEDS_PER_UNIVERSE; + const uint16_t remainLED = totalLen - ledsInFirstUniverse; + + endUniverse += (remainLED / ledsPerUniverse); + + if ((remainLED % ledsPerUniverse) > 0) { + endUniverse++; + } + + if ((endUniverse - startUniverse) > E131_MAX_UNIVERSE_COUNT) { + endUniverse = startUniverse + E131_MAX_UNIVERSE_COUNT - 1; + } + } + break; + } + default: + DEBUG_PRINTLN(F("unknown E1.31 DMX mode")); + return; // nothing to do + break; + } + + for (uint16_t i = startUniverse; i <= endUniverse; ++i) { + sendArtnetPollReply(&artnetPollReply, ipAddress, i); + } +} + +void prepareArtnetPollReply(ArtPollReply *reply) { + // Art-Net + reply->reply_id[0] = 0x41; + reply->reply_id[1] = 0x72; + reply->reply_id[2] = 0x74; + reply->reply_id[3] = 0x2d; + reply->reply_id[4] = 0x4e; + reply->reply_id[5] = 0x65; + reply->reply_id[6] = 0x74; + reply->reply_id[7] = 0x00; + + reply->reply_opcode = ARTNET_OPCODE_OPPOLLREPLY; + + IPAddress localIP = Network.localIP(); + for (uint8_t i = 0; i < 4; i++) { + reply->reply_ip[i] = localIP[i]; + } + + reply->reply_port = ARTNET_DEFAULT_PORT; + + char * numberEnd = versionString; + reply->reply_version_h = (uint8_t)strtol(numberEnd, &numberEnd, 10); + numberEnd++; + reply->reply_version_l = (uint8_t)strtol(numberEnd, &numberEnd, 10); + + // Switch values depend on universe, set before sending + reply->reply_net_sw = 0x00; + reply->reply_sub_sw = 0x00; + + reply->reply_oem_h = 0x00; // TODO add assigned oem code + reply->reply_oem_l = 0x00; + + reply->reply_ubea_ver = 0x00; + + // Indicators in Normal Mode + // All or part of Port-Address programmed by network or Web browser + reply->reply_status_1 = 0xE0; + + reply->reply_esta_man = 0x0000; + + strlcpy((char *)(reply->reply_short_name), serverDescription, 18); + strlcpy((char *)(reply->reply_long_name), serverDescription, 64); + + reply->reply_node_report[0] = '\0'; + + reply->reply_num_ports_h = 0x00; + reply->reply_num_ports_l = 0x01; // One output port + + reply->reply_port_types[0] = 0x80; // Output DMX data + reply->reply_port_types[1] = 0x00; + reply->reply_port_types[2] = 0x00; + reply->reply_port_types[3] = 0x00; + + // No inputs + reply->reply_good_input[0] = 0x00; + reply->reply_good_input[1] = 0x00; + reply->reply_good_input[2] = 0x00; + reply->reply_good_input[3] = 0x00; + + // One output + reply->reply_good_output_a[0] = 0x80; // Data is being transmitted + reply->reply_good_output_a[1] = 0x00; + reply->reply_good_output_a[2] = 0x00; + reply->reply_good_output_a[3] = 0x00; + + // Values depend on universe, set before sending + reply->reply_sw_in[0] = 0x00; + reply->reply_sw_in[1] = 0x00; + reply->reply_sw_in[2] = 0x00; + reply->reply_sw_in[3] = 0x00; + + // Values depend on universe, set before sending + reply->reply_sw_out[0] = 0x00; + reply->reply_sw_out[1] = 0x00; + reply->reply_sw_out[2] = 0x00; + reply->reply_sw_out[3] = 0x00; + + reply->reply_sw_video = 0x00; + reply->reply_sw_macro = 0x00; + reply->reply_sw_remote = 0x00; + + reply->reply_spare[0] = 0x00; + reply->reply_spare[1] = 0x00; + reply->reply_spare[2] = 0x00; + + // A DMX to / from Art-Net device + reply->reply_style = 0x00; + + Network.localMAC(reply->reply_mac); + + for (uint8_t i = 0; i < 4; i++) { + reply->reply_bind_ip[i] = localIP[i]; + } + + reply->reply_bind_index = 1; + + // Product supports web browser configuration + // Node’s IP is DHCP or manually configured + // Node is DHCP capable + // Node supports 15 bit Port-Address (Art-Net 3 or 4) + // Node is able to switch between ArtNet and sACN + reply->reply_status_2 = (staticIP[0] == 0) ? 0x1F : 0x1D; + + // RDM is disabled + // Output style is continuous + reply->reply_good_output_b[0] = 0xC0; + reply->reply_good_output_b[1] = 0xC0; + reply->reply_good_output_b[2] = 0xC0; + reply->reply_good_output_b[3] = 0xC0; + + // Fail-over state: Hold last state + // Node does not support fail-over + reply->reply_status_3 = 0x00; + + for (uint8_t i = 0; i < 21; i++) { + reply->reply_filler[i] = 0x00; + } +} + +void sendArtnetPollReply(ArtPollReply *reply, IPAddress ipAddress, uint16_t portAddress) { + reply->reply_net_sw = (uint8_t)((portAddress >> 8) & 0x007F); + reply->reply_sub_sw = (uint8_t)((portAddress >> 4) & 0x000F); + reply->reply_sw_out[0] = (uint8_t)(portAddress & 0x000F); + + sprintf((char *)reply->reply_node_report, "#0001 [%04u] OK - WLED v" TOSTRING(WLED_VERSION), pollReplyCount); + + if (pollReplyCount < 9999) { + pollReplyCount++; + } else { + pollReplyCount = 0; + } + + notifierUdp.beginPacket(ipAddress, ARTNET_DEFAULT_PORT); + notifierUdp.write(reply->raw, sizeof(ArtPollReply)); + notifierUdp.endPacket(); + + reply->reply_bind_index++; +} \ No newline at end of file diff --git a/wled00/fcn_declare.h b/wled00/fcn_declare.h index 237a23221..ab5096b55 100644 --- a/wled00/fcn_declare.h +++ b/wled00/fcn_declare.h @@ -58,21 +58,23 @@ bool getJsonValue(const JsonVariant& element, DestType& destination, const Defau //colors.cpp +uint32_t color_blend(uint32_t,uint32_t,uint16_t,bool b16=false); +uint32_t color_add(uint32_t,uint32_t); inline uint32_t colorFromRgbw(byte* rgbw) { return uint32_t((byte(rgbw[3]) << 24) | (byte(rgbw[0]) << 16) | (byte(rgbw[1]) << 8) | (byte(rgbw[2]))); } void colorHStoRGB(uint16_t hue, byte sat, byte* rgb); //hue, sat to rgb void colorKtoRGB(uint16_t kelvin, byte* rgb); void colorCTtoRGB(uint16_t mired, byte* rgb); //white spectrum to rgb - void colorXYtoRGB(float x, float y, byte* rgb); // only defined if huesync disabled TODO void colorRGBtoXY(byte* rgb, float* xy); // only defined if huesync disabled TODO - void colorFromDecOrHexString(byte* rgb, char* in); bool colorFromHexString(byte* rgb, const char* in); - uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb); uint16_t approximateKelvinFromRGB(uint32_t rgb); - void setRandomColor(byte* rgb); +uint8_t gamma8_cal(uint8_t b, float gamma); +void calcGammaTable(float gamma); +uint8_t gamma8(uint8_t b); +uint32_t gamma32(uint32_t); //dmx.cpp void initDMX(); @@ -80,6 +82,9 @@ void handleDMX(); //e131.cpp void handleE131Packet(e131_packet_t* p, IPAddress clientIP, byte protocol); +void handleArtnetPollReply(IPAddress ipAddress); +void prepareArtnetPollReply(ArtPollReply* reply); +void sendArtnetPollReply(ArtPollReply* reply, IPAddress ipAddress, uint16_t portAddress); //file.cpp bool handleFileRead(AsyncWebServerRequest*, String path); @@ -105,7 +110,6 @@ void sendImprovInfoResponse(); void sendImprovRPCResponse(uint8_t commandId); //ir.cpp -//bool decodeIRCustom(uint32_t code); void applyRepeatActions(); byte relativeChange(byte property, int8_t amount, byte lowerBoundary = 0, byte higherBoundary = 0xFF); void decodeIR(uint32_t code); @@ -130,9 +134,11 @@ void handleIR(); void deserializeSegment(JsonObject elem, byte it, byte presetId = 0); bool deserializeState(JsonObject root, byte callMode = CALL_MODE_DIRECT_CHANGE, byte presetId = 0); -void serializeSegment(JsonObject& root, WS2812FX::Segment& seg, byte id, bool forPreset = false, bool segmentBounds = true); -void serializeState(JsonObject root, bool forPreset = false, bool includeBri = true, bool segmentBounds = true); +void serializeSegment(JsonObject& root, Segment& seg, byte id, bool forPreset = false, bool segmentBounds = true); +void serializeState(JsonObject root, bool forPreset = false, bool includeBri = true, bool segmentBounds = true, bool selectedSegmentsOnly = false); void serializeInfo(JsonObject root); +void serializeModeNames(JsonArray arr, const char *qstring); +void serializeModeData(JsonObject root); void serveJson(AsyncWebServerRequest* request); #ifdef WLED_ENABLE_JSONLIVE bool serveLiveLeds(AsyncWebServerRequest* request, uint32_t wsClient = 0); @@ -186,8 +192,10 @@ void shufflePlaylist(); void unloadPlaylist(); int16_t loadPlaylist(JsonObject playlistObject, byte presetId = 0); void handlePlaylist(); +void serializePlaylist(JsonObject obj); //presets.cpp +void handlePresets(); bool applyPreset(byte index, byte callMode = CALL_MODE_DIRECT_CHANGE); inline bool applyTemporaryPreset() {return applyPreset(255);}; void savePreset(byte index, const char* pname = nullptr, JsonObject saveobj = JsonObject()); @@ -198,9 +206,6 @@ void deletePreset(byte index); bool isAsterisksOnly(const char* str, byte maxLen); void handleSettingsSet(AsyncWebServerRequest *request, byte subPage); bool handleSet(AsyncWebServerRequest *request, const String& req, bool apply=true); -int getNumVal(const String* req, uint16_t pos); -void parseNumber(const char* str, byte* val, byte minv=0, byte maxv=255); -bool updateVal(const String* req, const char* key, byte* val, byte minv=0, byte maxv=255); //udp.cpp void notify(byte callMode, bool followUp=false); @@ -212,25 +217,57 @@ void setRealtimePixel(uint16_t i, byte r, byte g, byte b, byte w); void refreshNodeList(); void sendSysInfoUDP(); -//util.cpp -//bool oappend(const char* txt); // append new c string to temp buffer efficiently -//bool oappendi(int i); // append new number to temp buffer efficiently -//void sappend(char stype, const char* key, int val); -//void sappends(char stype, const char* key, char* val); -//void prepareHostname(char* hostname); -//bool isAsterisksOnly(const char* str, byte maxLen); -bool requestJSONBufferLock(uint8_t module=255); -void releaseJSONBufferLock(); -uint8_t extractModeName(uint8_t mode, const char *src, char *dest, uint8_t maxLen); +//network.cpp +int getSignalQuality(int rssi); +void WiFiEvent(WiFiEvent_t event); //um_manager.cpp +typedef enum UM_Data_Types { + UMT_BYTE = 0, + UMT_UINT16, + UMT_INT16, + UMT_UINT32, + UMT_INT32, + UMT_FLOAT, + UMT_DOUBLE, + UMT_BYTE_ARR, + UMT_UINT16_ARR, + UMT_INT16_ARR, + UMT_UINT32_ARR, + UMT_INT32_ARR, + UMT_FLOAT_ARR, + UMT_DOUBLE_ARR +} um_types_t; +typedef struct UM_Exchange_Data { + // should just use: size_t arr_size, void **arr_ptr, byte *ptr_type + size_t u_size; // size of u_data array + um_types_t *u_type; // array of data types + void **u_data; // array of pointers to data + UM_Exchange_Data() { + u_size = 0; + u_type = nullptr; + u_data = nullptr; + } + ~UM_Exchange_Data() { + if (u_type) delete[] u_type; + if (u_data) delete[] u_data; + } +} um_data_t; +const unsigned int um_data_size = sizeof(um_data_t); // 12 bytes + class Usermod { + protected: + um_data_t *um_data; // um_data should be allocated using new in (derived) Usermod's setup() or constructor public: - virtual void loop() {} + Usermod() { um_data = nullptr; } + virtual ~Usermod() { if (um_data) delete um_data; } + virtual void setup() = 0; // pure virtual, has to be overriden + virtual void loop() = 0; // pure virtual, has to be overriden virtual void handleOverlayDraw() {} virtual bool handleButton(uint8_t b) { return false; } - virtual void setup() {} + virtual bool getUMData(um_data_t **data) { if (data) *data = nullptr; return false; }; virtual void connected() {} + virtual void appendConfigData() {} virtual void addToJsonState(JsonObject& obj) {} virtual void addToJsonInfo(JsonObject& obj) {} virtual void readFromJsonState(JsonObject& obj) {} @@ -238,6 +275,7 @@ class Usermod { virtual bool readFromConfig(JsonObject& obj) { return true; } // Note as of 2021-06 readFromConfig() now needs to return a bool, see usermod_v2_example.h virtual void onMqttConnect(bool sessionPresent) {} virtual bool onMqttMessage(char* topic, char* payload) { return false; } + virtual void onUpdateBegin(bool) {} virtual uint16_t getId() {return USERMOD_ID_UNSPECIFIED;} }; @@ -250,8 +288,10 @@ class UsermodManager { void loop(); void handleOverlayDraw(); bool handleButton(uint8_t b); + bool getUMData(um_data_t **um_data, uint8_t mod_id = USERMOD_ID_RESERVED); // USERMOD_ID_RESERVED will poll all usermods void setup(); void connected(); + void appendConfigData(); void addToJsonState(JsonObject& obj); void addToJsonInfo(JsonObject& obj); void readFromJsonState(JsonObject& obj); @@ -259,9 +299,10 @@ class UsermodManager { bool readFromConfig(JsonObject& obj); void onMqttConnect(bool sessionPresent); bool onMqttMessage(char* topic, char* payload); + void onUpdateBegin(bool); bool add(Usermod* um); Usermod* lookup(uint16_t mod_id); - byte getModCount(); + byte getModCount() {return numMods;}; }; //usermods_list.cpp @@ -272,11 +313,55 @@ void userSetup(); void userConnected(); void userLoop(); +//util.cpp +int getNumVal(const String* req, uint16_t pos); +void parseNumber(const char* str, byte* val, byte minv=0, byte maxv=255); +bool getVal(JsonVariant elem, byte* val, byte minv=0, byte maxv=255); +bool updateVal(const char* req, const char* key, byte* val, byte minv=0, byte maxv=255); +bool oappend(const char* txt); // append new c string to temp buffer efficiently +bool oappendi(int i); // append new number to temp buffer efficiently +void sappend(char stype, const char* key, int val); +void sappends(char stype, const char* key, char* val); +void prepareHostname(char* hostname); +bool isAsterisksOnly(const char* str, byte maxLen); +bool requestJSONBufferLock(uint8_t module=255); +void releaseJSONBufferLock(); +uint8_t extractModeName(uint8_t mode, const char *src, char *dest, uint8_t maxLen); +uint8_t extractModeSlider(uint8_t mode, uint8_t slider, char *dest, uint8_t maxLen, uint8_t *var = nullptr); +int16_t extractModeDefaults(uint8_t mode, const char *segVar); +uint16_t crc16(const unsigned char* data_p, size_t length); +um_data_t* simulateSound(uint8_t simulationId); +void enumerateLedmaps(); + +#ifdef WLED_ADD_EEPROM_SUPPORT //wled_eeprom.cpp void applyMacro(byte index); void deEEP(); void deEEPSettings(); void clearEEPROM(); +#endif + +//wled_math.cpp +#ifndef WLED_USE_REAL_MATH + template T atan_t(T x); + float cos_t(float phi); + float sin_t(float x); + float tan_t(float x); + float acos_t(float x); + float asin_t(float x); + float floor_t(float x); + float fmod_t(float num, float denom); +#else + #include + #define sin_t sin + #define cos_t cos + #define tan_t tan + #define asin_t asin + #define acos_t acos + #define atan_t atan + #define fmod_t fmod + #define floor_t floor +#endif //wled_serial.cpp void handleSerial(); @@ -284,6 +369,7 @@ void updateBaudRate(uint32_t rate); //wled_server.cpp bool isIp(String str); +void createEditHandler(bool enable); bool captivePortal(AsyncWebServerRequest *request); void initServer(); void serveIndexOrWelcome(AsyncWebServerRequest *request); @@ -293,6 +379,7 @@ void serveMessage(AsyncWebServerRequest* request, uint16_t code, const String& h String settingsProcessor(const String& var); String dmxProcessor(const String& var); void serveSettings(AsyncWebServerRequest* request, bool post = false); +void serveSettingsJS(AsyncWebServerRequest* request); //ws.cpp void handleWs(); @@ -302,8 +389,6 @@ void sendDataWs(AsyncWebSocketClient * client = nullptr); //xml.cpp void XML_response(AsyncWebServerRequest *request, char* dest = nullptr); void URL_response(AsyncWebServerRequest *request); -void sappend(char stype, const char* key, int val); -void sappends(char stype, const char* key, char* val); void getSettingsJS(byte subPage, char* dest); #endif diff --git a/wled00/file.cpp b/wled00/file.cpp index 55ebf081a..e6d3c9946 100644 --- a/wled00/file.cpp +++ b/wled00/file.cpp @@ -380,10 +380,10 @@ String getContentType(AsyncWebServerRequest* request, String filename){ else if(filename.endsWith(".htm")) return "text/html"; else if(filename.endsWith(".html")) return "text/html"; else if(filename.endsWith(".css")) return "text/css"; -// else if(filename.endsWith(".js")) return "application/javascript"; + else if(filename.endsWith(".js")) return "application/javascript"; else if(filename.endsWith(".json")) return "application/json"; else if(filename.endsWith(".png")) return "image/png"; -// else if(filename.endsWith(".gif")) return "image/gif"; + else if(filename.endsWith(".gif")) return "image/gif"; else if(filename.endsWith(".jpg")) return "image/jpeg"; else if(filename.endsWith(".ico")) return "image/x-icon"; // else if(filename.endsWith(".xml")) return "text/xml"; diff --git a/wled00/html_other.h b/wled00/html_other.h index 32c2855ef..30edb39ab 100644 --- a/wled00/html_other.h +++ b/wled00/html_other.h @@ -6,16 +6,22 @@ */ // Autogenerated from wled00/data/usermod.htm, do not edit!! -const char PAGE_usermod[] PROGMEM = R"=====(No usermod custom web page set.)====="; +const uint16_t PAGE_usermod_length = 81; +const uint8_t PAGE_usermod[] PROGMEM = { + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0a, 0xb3, 0x51, 0x74, 0xf1, 0x77, 0x0e, + 0x89, 0x0c, 0x70, 0x55, 0xc8, 0x28, 0xc9, 0xcd, 0xb1, 0xb3, 0x81, 0x90, 0x49, 0xf9, 0x29, 0x95, + 0x76, 0x7e, 0xf9, 0x0a, 0xa5, 0xc5, 0xa9, 0x45, 0xb9, 0xf9, 0x29, 0x0a, 0xc9, 0xa5, 0xc5, 0x25, + 0xf9, 0xb9, 0x0a, 0xe5, 0xa9, 0x49, 0x0a, 0x05, 0x89, 0xe9, 0xa9, 0x0a, 0xc5, 0xa9, 0x25, 0x7a, + 0x36, 0xfa, 0x60, 0x55, 0x36, 0xfa, 0x60, 0x2d, 0x00, 0x1e, 0x93, 0x65, 0xc7, 0x48, 0x00, 0x00, + 0x00 +}; // Autogenerated from wled00/data/msg.htm, do not edit!! const char PAGE_msg[] PROGMEM = R"=====( WLED Message

%MSG%)====="; +

%MSG%)====="; #ifdef WLED_ENABLE_DMX @@ -35,70 +41,361 @@ const char PAGE_dmxmap[] PROGMEM = R"=====()====="; #endif // Autogenerated from wled00/data/update.htm, do not edit!! -const char PAGE_update[] PROGMEM = R"=====( -WLED Update

WLED Software Update

-Installed version: 0.13.3
Download the latest binary: -


Updating...
-Please do not close or refresh the page :)
)====="; +const uint16_t PAGE_update_length = 615; +const uint8_t PAGE_update[] PROGMEM = { + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0a, 0x75, 0x53, 0x5d, 0x6f, 0xd4, 0x30, + 0x10, 0x7c, 0xcf, 0xaf, 0x70, 0xfd, 0x74, 0x27, 0x71, 0x4e, 0x41, 0xbc, 0x50, 0x92, 0x14, 0x8e, + 0x56, 0xa8, 0x12, 0x52, 0x4f, 0x6a, 0x0b, 0xe2, 0x09, 0x39, 0xf6, 0x26, 0x31, 0xe7, 0xd8, 0xa9, + 0xbd, 0xb9, 0xd3, 0x09, 0xf5, 0xbf, 0xb3, 0x71, 0xee, 0x0a, 0xe2, 0xe3, 0x25, 0x8a, 0xb3, 0xb3, + 0xe3, 0xdd, 0x99, 0x49, 0x71, 0x76, 0x75, 0xfb, 0xe1, 0xfe, 0xeb, 0xe6, 0x9a, 0x75, 0xd8, 0xdb, + 0xaa, 0x38, 0x3e, 0x41, 0xea, 0xaa, 0xe8, 0x01, 0x25, 0x53, 0xde, 0x21, 0x38, 0x2c, 0xf9, 0xde, + 0x68, 0xec, 0x4a, 0x0d, 0x3b, 0xa3, 0x60, 0x95, 0x0e, 0x9c, 0x39, 0xd9, 0x43, 0xc9, 0x77, 0x06, + 0xf6, 0x83, 0x0f, 0xc8, 0xab, 0xac, 0x40, 0x83, 0x16, 0xaa, 0x2f, 0x9f, 0xae, 0xaf, 0xd8, 0xc3, + 0xa0, 0x25, 0x42, 0x91, 0xcf, 0x9f, 0x8a, 0xa8, 0x82, 0x19, 0xb0, 0xca, 0x9a, 0xd1, 0x29, 0x34, + 0xde, 0xb1, 0xf5, 0x62, 0xf9, 0x63, 0x6f, 0x9c, 0xf6, 0x7b, 0xd1, 0x99, 0x88, 0x3e, 0x1c, 0x44, + 0x2d, 0xd5, 0x76, 0xb1, 0x7c, 0x7a, 0x86, 0x3c, 0x10, 0x44, 0x7b, 0x35, 0xf6, 0x34, 0x81, 0x68, + 0x01, 0xaf, 0x2d, 0x4c, 0xaf, 0xeb, 0xc3, 0x8d, 0x5e, 0xf0, 0xb1, 0xe1, 0x4b, 0x11, 0xf1, 0x60, + 0x41, 0x68, 0x13, 0x07, 0x2b, 0x0f, 0x25, 0x77, 0xde, 0x01, 0x7f, 0xf1, 0xdf, 0x96, 0x3e, 0xb6, + 0x7f, 0xf7, 0xd4, 0xd6, 0xab, 0x2d, 0x7f, 0xca, 0x8a, 0xfc, 0x38, 0xe2, 0x71, 0x54, 0x16, 0x83, + 0x2a, 0x79, 0x1e, 0x01, 0xd1, 0xb8, 0x36, 0xe6, 0x51, 0x7c, 0x8f, 0x97, 0x43, 0xf9, 0x86, 0x57, + 0xbf, 0x21, 0x27, 0xaa, 0x2a, 0x7b, 0x67, 0xfa, 0x49, 0x00, 0x36, 0x06, 0xbb, 0xe0, 0x33, 0xbd, + 0x8a, 0x91, 0x2f, 0xdf, 0x12, 0x32, 0x21, 0x8a, 0x7c, 0x96, 0xb4, 0xf6, 0xfa, 0xc0, 0xbc, 0xb3, + 0x5e, 0xea, 0x92, 0x7f, 0x04, 0xfc, 0xbc, 0x58, 0x12, 0x5d, 0xf7, 0xaa, 0xca, 0x92, 0x64, 0x77, + 0xbe, 0xc1, 0xbd, 0x0c, 0xf0, 0xac, 0x1d, 0x55, 0x8a, 0xc6, 0x87, 0x9e, 0x91, 0x17, 0x9d, 0xa7, + 0x9e, 0xcd, 0xed, 0xdd, 0x3d, 0x67, 0x32, 0xc9, 0x43, 0xc3, 0x8d, 0x09, 0xc7, 0x99, 0xa1, 0x12, + 0xe9, 0xc1, 0x32, 0x20, 0xe5, 0x0e, 0x03, 0x99, 0xd2, 0x8f, 0x16, 0xcd, 0x20, 0x03, 0xe6, 0x53, + 0xff, 0x8a, 0x60, 0x92, 0xd3, 0xcd, 0x71, 0xac, 0x7b, 0x43, 0x6e, 0x3e, 0x4c, 0x17, 0xdf, 0xb8, + 0x88, 0xd2, 0x5a, 0xd0, 0x6c, 0x07, 0x21, 0x12, 0xe3, 0x05, 0x2b, 0xe2, 0x20, 0x1d, 0xcb, 0x94, + 0x95, 0x31, 0x96, 0x3c, 0x9a, 0x81, 0x57, 0xe7, 0xe2, 0xe5, 0x6b, 0x71, 0xbe, 0xaa, 0xcf, 0x69, + 0x19, 0x2a, 0xd2, 0x12, 0xa1, 0xba, 0xf2, 0xfb, 0xb4, 0x04, 0xc3, 0x0e, 0x98, 0xa5, 0x11, 0x22, + 0xb2, 0xda, 0x38, 0x19, 0x0e, 0x44, 0x21, 0x59, 0xd6, 0x05, 0x68, 0x4a, 0xde, 0x21, 0x0e, 0xf1, + 0x22, 0xcf, 0x5b, 0x83, 0xdd, 0x58, 0x0b, 0xe5, 0xfb, 0xfc, 0xbd, 0x09, 0xca, 0x7b, 0xbf, 0x35, + 0x90, 0x4f, 0x1b, 0xe7, 0x01, 0x2c, 0xc8, 0x08, 0x91, 0x33, 0x94, 0x81, 0xec, 0x2a, 0xf9, 0xb7, + 0xda, 0x4a, 0xb7, 0x25, 0x55, 0x4c, 0xdf, 0xb2, 0x2c, 0x79, 0x70, 0xe2, 0xa1, 0x2f, 0x22, 0x76, + 0x06, 0xac, 0x8e, 0xc2, 0xf8, 0x23, 0xed, 0x89, 0xe2, 0x4f, 0x6a, 0x11, 0x77, 0xed, 0x65, 0x52, + 0xbf, 0x6c, 0x68, 0xc2, 0x55, 0x7c, 0x1c, 0x49, 0xd9, 0x29, 0xa3, 0xb9, 0x4c, 0x3b, 0x14, 0xc6, + 0x0d, 0x23, 0xb2, 0x59, 0xae, 0xc6, 0x58, 0x38, 0xe5, 0xf9, 0x24, 0x6a, 0x80, 0xc7, 0xd1, 0x04, + 0xd0, 0x33, 0xba, 0x1e, 0x11, 0x29, 0x92, 0x33, 0x7c, 0x96, 0x91, 0xc8, 0x66, 0xa3, 0xce, 0x8a, + 0x7c, 0x2e, 0xff, 0x03, 0x3a, 0x1f, 0x26, 0xed, 0x95, 0x35, 0x6a, 0x5b, 0xf2, 0xf5, 0x24, 0xfd, + 0x9a, 0x92, 0xfe, 0xab, 0x29, 0x79, 0x54, 0x15, 0xda, 0xec, 0xb2, 0x64, 0xe5, 0x94, 0x53, 0xa2, + 0xa9, 0x12, 0x3b, 0x85, 0x4f, 0x08, 0x41, 0xe0, 0x44, 0xbe, 0x49, 0xcb, 0x32, 0xed, 0x99, 0xf3, + 0xc8, 0x94, 0xf5, 0x74, 0xf0, 0x81, 0x66, 0x6d, 0x02, 0xc4, 0x2e, 0xf9, 0x31, 0xc8, 0x16, 0xd8, + 0xc5, 0xb2, 0xc8, 0x89, 0x6f, 0x5a, 0x77, 0x0a, 0xdd, 0x94, 0xc0, 0xe9, 0xd7, 0xfe, 0x09, 0x43, + 0x44, 0x4f, 0x48, 0xf0, 0x03, 0x00, 0x00 +}; // Autogenerated from wled00/data/welcome.htm, do not edit!! -const char PAGE_welcome[] PROGMEM = R"=====(Welcome! -

Welcome to WLED!

-Thank you for installing my application!

Next steps:

-Connect the module to your local WiFi here!

-Just trying this out in AP mode?

-)====="; +const uint16_t PAGE_welcome_length = 1528; +const uint8_t PAGE_welcome[] PROGMEM = { + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0a, 0x95, 0x56, 0x5b, 0x93, 0xaa, 0x3a, + 0x16, 0x7e, 0xef, 0x5f, 0xc1, 0x76, 0xea, 0xd4, 0x79, 0x70, 0x77, 0x73, 0x13, 0x51, 0xdb, 0xee, + 0x19, 0xc5, 0x4b, 0x7b, 0x03, 0x6f, 0x78, 0x7b, 0x0b, 0x10, 0x20, 0x08, 0x04, 0x93, 0x80, 0x97, + 0xae, 0xfe, 0xef, 0x13, 0x74, 0xf7, 0xd4, 0x3e, 0x75, 0x1e, 0xa6, 0x4e, 0x2c, 0x21, 0xf9, 0x92, + 0xb5, 0xd6, 0xb7, 0x92, 0xb8, 0x3e, 0xdb, 0x3f, 0x7a, 0x96, 0xb1, 0xde, 0xcf, 0xfb, 0x42, 0xc8, + 0x92, 0xf8, 0xbd, 0xfd, 0xeb, 0x09, 0x81, 0xf7, 0xde, 0x4e, 0x20, 0x03, 0x82, 0x1b, 0x02, 0x42, + 0x21, 0x7b, 0xab, 0xe4, 0xcc, 0x7f, 0x6e, 0x54, 0x7e, 0xa1, 0x4f, 0x2e, 0x4e, 0x19, 0x4c, 0x39, + 0x7c, 0x46, 0x1e, 0x0b, 0xdf, 0x3c, 0x58, 0x20, 0x17, 0x3e, 0xdf, 0x07, 0x15, 0x21, 0x05, 0x09, + 0x7c, 0xab, 0x14, 0x08, 0x9e, 0x33, 0x4c, 0xd8, 0xb7, 0xcd, 0x03, 0x65, 0x21, 0x4c, 0xe0, 0xb3, + 0x8b, 0x63, 0x4c, 0x2a, 0xbf, 0xb9, 0xf9, 0x97, 0x72, 0x6f, 0x7c, 0x2d, 0x43, 0x2c, 0x86, 0xef, + 0x5b, 0x18, 0xbb, 0x38, 0x81, 0x3f, 0xda, 0xe2, 0x63, 0xdc, 0xa6, 0xec, 0xca, 0x5f, 0x4f, 0x0e, + 0xf6, 0xae, 0x9f, 0x3e, 0xb7, 0x7a, 0xf6, 0x41, 0x82, 0xe2, 0x6b, 0x6b, 0x03, 0x89, 0x07, 0x52, + 0xf0, 0xf3, 0x03, 0xc6, 0x05, 0x64, 0xc8, 0x05, 0x3f, 0x29, 0x48, 0xe9, 0x33, 0x85, 0x04, 0xf9, + 0xaf, 0x0c, 0x5e, 0xd8, 0x33, 0x88, 0x51, 0x90, 0xb6, 0x5c, 0x1e, 0x06, 0x92, 0x57, 0x07, 0xb8, + 0xc7, 0x80, 0xe0, 0x3c, 0xf5, 0x1e, 0x1c, 0x5a, 0x65, 0xe0, 0xd7, 0x04, 0x90, 0x00, 0xa5, 0x2d, + 0xe9, 0xf5, 0x17, 0xe6, 0xfb, 0xfe, 0x97, 0x93, 0x33, 0x86, 0xd3, 0x4f, 0x9c, 0xb3, 0x18, 0xa5, + 0xb0, 0x9c, 0xcb, 0x09, 0xe5, 0x93, 0x19, 0x46, 0x77, 0x4f, 0x19, 0xf0, 0x3c, 0x94, 0x06, 0xad, + 0x46, 0x76, 0xf9, 0xb6, 0x97, 0x25, 0xde, 0xbf, 0xef, 0x41, 0x4b, 0x51, 0xcb, 0xfe, 0x3d, 0x3e, + 0x23, 0x9c, 0x90, 0x8f, 0x49, 0xd2, 0xca, 0xb3, 0x0c, 0x12, 0x17, 0x50, 0xf8, 0xfa, 0x7b, 0x06, + 0xe1, 0x37, 0xf3, 0x07, 0x4a, 0xd1, 0x0d, 0xb6, 0xe4, 0x26, 0xb7, 0xfe, 0x3b, 0x57, 0x55, 0x55, + 0x7f, 0xa3, 0xf8, 0xea, 0x60, 0xe2, 0x41, 0xd2, 0x92, 0x04, 0x8a, 0x63, 0xe4, 0x09, 0xbf, 0x61, + 0xcf, 0x04, 0x78, 0x28, 0xa7, 0x2d, 0x45, 0xcb, 0x2e, 0x5f, 0x28, 0x09, 0x3e, 0x1f, 0xac, 0x9a, + 0x9a, 0x74, 0x67, 0x7b, 0x79, 0x9c, 0x54, 0xab, 0xa1, 0xfc, 0xf1, 0x8a, 0x12, 0x10, 0xc0, 0x67, + 0x02, 0x53, 0x6e, 0x56, 0xe6, 0x93, 0xa1, 0x0b, 0x8c, 0x01, 0x83, 0xde, 0xdf, 0x66, 0x5c, 0x82, + 0x68, 0xf6, 0x0c, 0xbd, 0x00, 0xd2, 0xef, 0x8c, 0x6b, 0x45, 0x28, 0x48, 0xe5, 0xe7, 0x15, 0xa4, + 0x7c, 0x39, 0x43, 0x38, 0x6d, 0xf9, 0x48, 0x90, 0xe9, 0xd7, 0x7f, 0x8e, 0xf0, 0xea, 0x13, 0x7e, + 0xe4, 0x54, 0xf0, 0xd1, 0xa7, 0x4f, 0x70, 0xf2, 0x89, 0x33, 0xe0, 0x22, 0x76, 0x6d, 0x49, 0x5f, + 0x0c, 0xff, 0x6f, 0x20, 0x7f, 0x7d, 0xbd, 0x24, 0x00, 0xa5, 0x9f, 0x7f, 0x75, 0xf0, 0xa2, 0x51, + 0xe1, 0x45, 0xa7, 0x82, 0x83, 0x59, 0xf8, 0xf5, 0xd4, 0x16, 0x1f, 0xc7, 0xdf, 0x16, 0x1f, 0x37, + 0xb3, 0xbc, 0x05, 0xef, 0x6d, 0x9e, 0x97, 0x00, 0x62, 0x7e, 0x79, 0xf8, 0x45, 0xa2, 0xc4, 0x7d, + 0xab, 0x78, 0x80, 0x81, 0xd6, 0x9d, 0xb5, 0x98, 0xa5, 0x01, 0xdf, 0x3f, 0x0a, 0xeb, 0xb5, 0x9f, + 0x68, 0xd3, 0xb5, 0x96, 0x67, 0x69, 0x32, 0x0c, 0x70, 0x87, 0x37, 0x73, 0x65, 0x87, 0x7d, 0x3b, + 0xe0, 0xbd, 0xa1, 0x54, 0x8e, 0x7d, 0xa3, 0x33, 0xe3, 0xaf, 0x1e, 0xb8, 0x4d, 0xad, 0xbc, 0x04, + 0x3a, 0x3b, 0x73, 0xb5, 0x94, 0x46, 0x1d, 0x42, 0x6b, 0x6e, 0x7d, 0x51, 0x02, 0xcb, 0x74, 0x61, + 0xcb, 0xdd, 0x4e, 0xc7, 0xb8, 0x44, 0xe7, 0xa2, 0xb1, 0x5f, 0xd8, 0x1c, 0xeb, 0x4e, 0xed, 0xfe, + 0xc5, 0x5e, 0xde, 0xe7, 0xbb, 0x0d, 0x39, 0x30, 0x6c, 0xf1, 0x36, 0x39, 0x89, 0xa2, 0x98, 0x60, + 0x9d, 0x6e, 0x67, 0x66, 0xc3, 0xb1, 0xaa, 0x87, 0xd1, 0x9e, 0x1d, 0x40, 0x67, 0xde, 0x24, 0x9d, + 0x79, 0xf5, 0x63, 0x46, 0x0d, 0x34, 0xac, 0xae, 0x3b, 0x23, 0x2b, 0x9d, 0xad, 0xa4, 0xc9, 0xc9, + 0xb4, 0xf5, 0xc9, 0x3c, 0x9d, 0xda, 0x07, 0x8b, 0x9c, 0xea, 0x05, 0xb7, 0xac, 0x19, 0x9d, 0x60, + 0x18, 0x62, 0x30, 0xad, 0x8a, 0x45, 0xdd, 0x08, 0x70, 0xff, 0x32, 0x5b, 0xdf, 0x09, 0xc5, 0x49, + 0xcd, 0x6a, 0x94, 0x9d, 0x83, 0x37, 0x18, 0x5b, 0xb6, 0xf8, 0x7f, 0xda, 0xb9, 0xd3, 0x35, 0x3b, + 0x27, 0xb5, 0x34, 0x30, 0x76, 0xdd, 0xd1, 0x76, 0x57, 0xe6, 0xa7, 0xf7, 0xf8, 0xc3, 0x3a, 0x9f, + 0x3f, 0x3e, 0x9c, 0x7a, 0x78, 0x2c, 0xa7, 0x4c, 0x29, 0xee, 0x2f, 0x36, 0xcb, 0xd1, 0x4a, 0x57, + 0x37, 0xd1, 0x66, 0x6a, 0xcc, 0xba, 0x9d, 0xfe, 0x7e, 0x44, 0x1a, 0xc6, 0x7e, 0x72, 0x24, 0x5e, + 0xa4, 0xfa, 0xf2, 0x58, 0xd5, 0x6f, 0x60, 0x37, 0x30, 0xb2, 0xb5, 0x55, 0xcd, 0x10, 0xe8, 0x05, + 0xce, 0xfc, 0xc4, 0x2f, 0x66, 0x73, 0xb3, 0x90, 0x4e, 0x57, 0x52, 0x4c, 0xa2, 0xda, 0xa9, 0x9e, + 0x48, 0x07, 0x22, 0x87, 0xd5, 0x99, 0x7e, 0x19, 0xc8, 0xb7, 0x65, 0x92, 0x6e, 0x6f, 0xa7, 0x4d, + 0x53, 0x94, 0x3c, 0x25, 0x62, 0x6c, 0x88, 0x99, 0x25, 0xe7, 0x45, 0xd3, 0xb3, 0x2d, 0xe7, 0x0c, + 0x23, 0x0d, 0x9f, 0x32, 0xdd, 0xbf, 0x6d, 0x37, 0xf3, 0xb8, 0x91, 0xd6, 0x9b, 0x20, 0x23, 0x37, + 0x6c, 0xd9, 0xb6, 0xe3, 0x14, 0xde, 0xc8, 0xd9, 0xa8, 0xd6, 0xf4, 0x8c, 0xd8, 0xce, 0xad, 0x17, + 0xab, 0xa1, 0xec, 0xe9, 0x49, 0x63, 0xa4, 0xfa, 0x70, 0xd5, 0x37, 0xa5, 0x48, 0x31, 0xa0, 0xe9, + 0x58, 0xfb, 0xda, 0xec, 0x82, 0x82, 0xc8, 0x9a, 0x1a, 0x6e, 0x8d, 0x1e, 0xe8, 0x7a, 0xa3, 0xc4, + 0xb2, 0x6b, 0x5c, 0xaf, 0xb5, 0xf3, 0x68, 0x34, 0x9d, 0x4e, 0xa3, 0xce, 0x85, 0x5d, 0x8f, 0x31, + 0x3b, 0x29, 0xc4, 0x59, 0xdb, 0xd5, 0x13, 0x92, 0x64, 0x53, 0x23, 0x3b, 0xd3, 0x52, 0x62, 0x08, + 0x06, 0xd6, 0x12, 0xa3, 0x08, 0x28, 0xb1, 0x36, 0x9b, 0x69, 0x40, 0x52, 0x80, 0xdb, 0xdc, 0x03, + 0xb9, 0xbe, 0x3a, 0x6a, 0x2c, 0x00, 0x73, 0x62, 0x67, 0xd1, 0x21, 0x77, 0xa4, 0xee, 0xb4, 0xbe, + 0x3f, 0xad, 0x2e, 0x93, 0xb3, 0xf3, 0xa1, 0xeb, 0x3b, 0xdb, 0x4e, 0x56, 0xc7, 0xf1, 0x6e, 0x15, + 0x37, 0x16, 0x0c, 0xcc, 0xf2, 0xeb, 0x38, 0x3c, 0x69, 0x09, 0x98, 0x6a, 0xe9, 0x7a, 0xb2, 0xc9, + 0x0e, 0x86, 0xac, 0x6e, 0x12, 0x36, 0xcb, 0xd6, 0x83, 0xb5, 0x12, 0xd4, 0x8a, 0x71, 0xb4, 0xce, + 0x87, 0xfe, 0xec, 0x76, 0xdb, 0xf9, 0x0c, 0xd9, 0x87, 0x34, 0xf4, 0x58, 0xe0, 0xc8, 0x17, 0xec, + 0x17, 0xd7, 0x6c, 0x89, 0x53, 0x6d, 0x1d, 0x99, 0xe9, 0x65, 0x6f, 0x36, 0x6f, 0x63, 0x5c, 0x9f, + 0x68, 0x24, 0x5f, 0x8d, 0x6e, 0x0b, 0x36, 0xcc, 0x37, 0x87, 0x54, 0xba, 0x34, 0x65, 0x32, 0x29, + 0xbc, 0x8f, 0x6e, 0x91, 0xa8, 0xcd, 0xbe, 0xbe, 0xba, 0x1e, 0x6a, 0x57, 0xa9, 0x3e, 0xbc, 0x35, + 0xba, 0xbd, 0xee, 0x60, 0x72, 0xa3, 0xbb, 0x24, 0x74, 0xcf, 0x57, 0x9f, 0x0e, 0x0f, 0xcd, 0x4d, + 0xe6, 0x84, 0x18, 0x19, 0x28, 0x05, 0x97, 0xb9, 0x99, 0x0c, 0xb7, 0xc9, 0x76, 0x4b, 0x4c, 0x5b, + 0x89, 0xba, 0xd2, 0xa9, 0xfe, 0x51, 0x58, 0xa1, 0x29, 0x8f, 0x6d, 0x66, 0xa0, 0xcb, 0x82, 0x65, + 0x4a, 0xa0, 0xc9, 0x87, 0x8d, 0xbd, 0x1b, 0xcd, 0x57, 0xca, 0x72, 0xd6, 0xe9, 0x55, 0xab, 0x6b, + 0x25, 0xdd, 0x8b, 0x3d, 0xd2, 0x8b, 0xac, 0x59, 0xcf, 0xaa, 0x9d, 0x0d, 0x3d, 0x4a, 0xcc, 0x7d, + 0xa4, 0x7a, 0xba, 0x9a, 0x92, 0x5d, 0x1e, 0x34, 0xf6, 0xac, 0x99, 0x9b, 0xdd, 0xc6, 0xc5, 0xb4, + 0x65, 0x77, 0x62, 0xee, 0xb6, 0xb1, 0x3d, 0x30, 0x77, 0x46, 0xb4, 0x91, 0xb3, 0x43, 0x38, 0x5c, + 0xf7, 0x1b, 0x6a, 0xa2, 0x14, 0x5b, 0x7f, 0xef, 0x8b, 0xe6, 0x30, 0xaa, 0x75, 0x03, 0xf9, 0x96, + 0x6b, 0xe3, 0x9e, 0x2a, 0xce, 0xd2, 0x0f, 0xed, 0xb0, 0xf5, 0xa7, 0xd6, 0x91, 0x3a, 0x9c, 0xd2, + 0x30, 0x0a, 0x76, 0x33, 0x53, 0x13, 0x0d, 0x65, 0xb8, 0xdb, 0x0f, 0x07, 0x83, 0x6d, 0xd3, 0x4c, + 0x78, 0xfd, 0xae, 0xef, 0x72, 0x26, 0x0d, 0x93, 0xea, 0x98, 0x82, 0xab, 0x11, 0x35, 0x6e, 0x52, + 0x1a, 0x46, 0xc3, 0x62, 0x3f, 0xbe, 0x6d, 0xfc, 0x1a, 0x58, 0xdd, 0x38, 0x13, 0x4a, 0xaa, 0xb9, + 0xbe, 0x50, 0xc6, 0x23, 0x0f, 0xef, 0xd4, 0xbd, 0xb5, 0x88, 0x2c, 0x1a, 0x27, 0x74, 0x2b, 0x47, + 0x63, 0x55, 0x56, 0xa4, 0xb4, 0xdf, 0x0c, 0x7c, 0xac, 0x37, 0xc3, 0x35, 0x30, 0x3d, 0x97, 0x9e, + 0x36, 0xcb, 0xba, 0x2c, 0xc5, 0x85, 0x55, 0xd3, 0xb3, 0x38, 0x06, 0xb0, 0xb9, 0x84, 0x87, 0x86, + 0xa4, 0xdd, 0x26, 0x58, 0x01, 0x2a, 0x02, 0x7b, 0xcd, 0xd5, 0xb5, 0x6c, 0x9d, 0x6c, 0x8c, 0x4e, + 0xdd, 0x1b, 0x6b, 0x1f, 0xba, 0x29, 0x51, 0x22, 0x02, 0x3a, 0x3f, 0x77, 0x61, 0xe8, 0xe8, 0xc8, + 0xef, 0x87, 0x39, 0x5d, 0xde, 0x7f, 0x5a, 0xfd, 0x78, 0xb0, 0x3e, 0xae, 0xf2, 0x45, 0x62, 0x18, + 0x95, 0xf7, 0xa7, 0xb6, 0x87, 0x0a, 0xc1, 0x8d, 0x01, 0xa5, 0x6f, 0x95, 0xb2, 0x28, 0x71, 0x39, + 0x0a, 0xe5, 0x6f, 0x2d, 0x12, 0x18, 0x16, 0xb6, 0xd3, 0x7e, 0x8f, 0x6b, 0x12, 0x07, 0xdb, 0xa1, + 0xfa, 0xfe, 0xb4, 0x0e, 0x41, 0x7a, 0x14, 0xae, 0x38, 0x17, 0x78, 0x7d, 0x17, 0x50, 0x4a, 0x19, + 0x88, 0xb9, 0x5a, 0x04, 0x42, 0x72, 0x15, 0x40, 0x96, 0xc5, 0xbc, 0xa8, 0x97, 0x15, 0xad, 0xb4, + 0x50, 0x79, 0xd5, 0x7a, 0x37, 0xb9, 0x1e, 0x08, 0x94, 0xc1, 0x8c, 0xb6, 0xda, 0xa2, 0xc3, 0x11, + 0x72, 0xff, 0x3e, 0x19, 0x38, 0x4d, 0xa1, 0xcb, 0x04, 0x2e, 0x8e, 0x42, 0x82, 0xbd, 0x3c, 0xbe, + 0x07, 0xe3, 0x7e, 0x89, 0x10, 0x63, 0x17, 0xc4, 0xc2, 0x16, 0x0d, 0x90, 0x10, 0x42, 0xc2, 0x05, + 0xf1, 0x6e, 0x73, 0xd7, 0x26, 0xe1, 0x09, 0xa7, 0x2e, 0x8f, 0x71, 0x7c, 0xfb, 0xf3, 0x8c, 0x52, + 0x0f, 0x9f, 0x5f, 0xca, 0xc5, 0x65, 0xc0, 0x97, 0x90, 0x40, 0xff, 0xad, 0x22, 0x72, 0xe1, 0x66, + 0x9c, 0x0e, 0x15, 0xcf, 0xc8, 0x47, 0x95, 0x3f, 0xdf, 0xef, 0x6e, 0xbe, 0x41, 0xce, 0xe0, 0xee, + 0xe6, 0x41, 0x03, 0xbd, 0x3f, 0x8d, 0x73, 0xca, 0x29, 0x90, 0x6b, 0x99, 0x00, 0x0b, 0x11, 0x15, + 0xb8, 0xf6, 0xf1, 0xa4, 0x84, 0xce, 0xbc, 0x24, 0x05, 0xff, 0xdd, 0x16, 0xd1, 0xfb, 0x3f, 0x0b, + 0xcf, 0x55, 0x09, 0x12, 0xca, 0x03, 0xaf, 0xf1, 0x3d, 0xb7, 0x52, 0xee, 0x09, 0x8e, 0xe9, 0x8f, + 0xbf, 0xc6, 0x16, 0xf9, 0xb6, 0xf3, 0xcd, 0x17, 0x1f, 0x65, 0x5d, 0xbc, 0xff, 0x07, 0xf9, 0x2f, + 0xec, 0xfa, 0x82, 0xd2, 0x99, 0x08, 0x00, 0x00 +}; // Autogenerated from wled00/data/liveview.htm, do not edit!! -const char PAGE_liveview[] PROGMEM = R"=====( -WLED Live Preview
)====="; +const uint16_t PAGE_liveview_length = 547; +const uint8_t PAGE_liveview[] PROGMEM = { + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0a, 0x95, 0x53, 0x4d, 0x6f, 0xdb, 0x30, + 0x0c, 0xbd, 0xe7, 0x57, 0x78, 0x2a, 0x5a, 0x48, 0x88, 0x63, 0x3b, 0xc5, 0xba, 0x8f, 0xf8, 0xe3, + 0xb0, 0xb5, 0x87, 0x02, 0x05, 0xd6, 0x43, 0x81, 0x61, 0x18, 0x76, 0x50, 0x24, 0xc6, 0xd6, 0x2a, + 0x4b, 0x81, 0x4c, 0xb9, 0x08, 0xd2, 0xfc, 0xf7, 0xc9, 0x76, 0xd2, 0x62, 0xc0, 0x30, 0xa0, 0x3e, + 0xc8, 0x94, 0xf8, 0xf4, 0x48, 0x3e, 0x52, 0xc5, 0xbb, 0xeb, 0x6f, 0x5f, 0x1f, 0x7e, 0xdc, 0xdf, + 0x44, 0x0d, 0xb6, 0xba, 0x2a, 0x8e, 0x2b, 0x70, 0x59, 0x15, 0x2d, 0x20, 0x8f, 0x0c, 0x6f, 0xa1, + 0x24, 0xbd, 0x82, 0xa7, 0xad, 0x75, 0x48, 0xa2, 0x99, 0xb0, 0x06, 0xc1, 0x60, 0x49, 0x9e, 0x94, + 0xc4, 0xa6, 0x94, 0xd0, 0x2b, 0x01, 0x8b, 0x71, 0x13, 0x2b, 0xa3, 0x50, 0x71, 0xbd, 0xe8, 0x04, + 0xd7, 0x50, 0x2e, 0xe3, 0x36, 0x1c, 0xb4, 0xbe, 0x3d, 0xed, 0xc9, 0x91, 0x73, 0x26, 0x1a, 0xee, + 0x3a, 0x08, 0x1c, 0x1e, 0x37, 0x8b, 0x4f, 0xe4, 0xaf, 0x50, 0xd8, 0x40, 0x0b, 0x0b, 0x61, 0xb5, + 0x75, 0x24, 0x7a, 0x09, 0x76, 0x76, 0x39, 0x7e, 0x01, 0x8a, 0x0a, 0x35, 0x54, 0xb3, 0xef, 0x77, + 0x37, 0xd7, 0xd1, 0x9d, 0xea, 0x21, 0xba, 0x77, 0x30, 0xa4, 0x57, 0xa4, 0x93, 0xa7, 0xe8, 0x70, + 0x37, 0x00, 0xd6, 0x56, 0xee, 0xf6, 0x2d, 0x77, 0xb5, 0x32, 0xab, 0xec, 0x70, 0x26, 0xb8, 0xe9, + 0xf7, 0x6b, 0x2e, 0x1e, 0x6b, 0x67, 0xbd, 0x91, 0xab, 0xb3, 0x2c, 0xcb, 0xf2, 0x8d, 0xd2, 0x08, + 0x6e, 0xb5, 0x76, 0xaa, 0x6e, 0xd0, 0x40, 0xd7, 0xd1, 0xe5, 0xc7, 0xab, 0x73, 0x96, 0x8f, 0xd5, + 0xac, 0x96, 0x59, 0x76, 0x9e, 0x37, 0x30, 0xf8, 0x26, 0x7b, 0x6b, 0xbb, 0x50, 0x9f, 0x35, 0x2b, + 0xbe, 0xee, 0xac, 0xf6, 0x08, 0x87, 0x59, 0x91, 0x4e, 0xe1, 0x8a, 0x74, 0xd2, 0x6c, 0x88, 0x5a, + 0x15, 0x52, 0xf5, 0x91, 0x92, 0x25, 0x19, 0x82, 0x86, 0x94, 0x3b, 0xe1, 0xd4, 0x16, 0xab, 0x99, + 0xdf, 0x4a, 0x8e, 0x40, 0x59, 0xde, 0x73, 0x17, 0x61, 0x6b, 0x3d, 0x96, 0xc6, 0x6b, 0x9d, 0x6f, + 0xbc, 0x11, 0x03, 0x6f, 0x74, 0x02, 0xec, 0xd5, 0x86, 0x4a, 0x2b, 0x7c, 0x1b, 0x6a, 0x4f, 0x1a, + 0x25, 0x25, 0x18, 0xe6, 0x00, 0xbd, 0x33, 0x91, 0xd0, 0xc0, 0xdd, 0x83, 0x6a, 0x21, 0xdc, 0xa6, + 0x23, 0x07, 0x8b, 0x7b, 0xab, 0xe4, 0x64, 0x97, 0x41, 0xd5, 0x93, 0x73, 0x22, 0x8b, 0x2f, 0xaf, + 0x32, 0xc6, 0xf2, 0x0d, 0xa0, 0x68, 0x28, 0x49, 0x7f, 0x77, 0xd6, 0xa4, 0x3a, 0xc8, 0x46, 0x58, + 0x12, 0x94, 0x36, 0x14, 0xcb, 0x8a, 0x62, 0x62, 0x1f, 0x9f, 0x9f, 0xe9, 0xbf, 0xa8, 0xff, 0xc3, + 0x1a, 0x68, 0x63, 0x4c, 0x06, 0x42, 0xca, 0xd8, 0x2b, 0xdb, 0x7e, 0x28, 0x2e, 0xf4, 0x51, 0x2b, + 0x13, 0xe8, 0x16, 0xb5, 0xe3, 0x52, 0x85, 0x32, 0xe8, 0xe7, 0x4c, 0x42, 0x1d, 0x93, 0xd8, 0x97, + 0x98, 0x68, 0x90, 0x5d, 0x58, 0x4c, 0x8d, 0x4d, 0xbe, 0xb1, 0x8e, 0xaa, 0x32, 0xcb, 0x55, 0xe1, + 0x73, 0x35, 0x9f, 0xb3, 0xf1, 0xbe, 0x3d, 0x82, 0x7e, 0xaa, 0x5f, 0xb9, 0x3d, 0x22, 0xab, 0x0f, + 0x17, 0x17, 0xd4, 0x96, 0x36, 0xe9, 0xfc, 0xba, 0x43, 0xa7, 0x4c, 0x4d, 0x2f, 0x43, 0x0a, 0x30, + 0x0f, 0xd3, 0x41, 0xe6, 0x36, 0x0e, 0x04, 0x8b, 0x65, 0x80, 0x0c, 0x07, 0x31, 0x61, 0x87, 0xe1, + 0xcf, 0x48, 0xfc, 0xa2, 0x63, 0x0d, 0x78, 0xa3, 0x61, 0x30, 0xbf, 0xec, 0x6e, 0x25, 0x9d, 0x9a, + 0xc3, 0x92, 0xb1, 0x7f, 0xc9, 0xeb, 0x64, 0x94, 0x10, 0xbf, 0x45, 0x88, 0xf7, 0x19, 0x3b, 0xb0, + 0x44, 0xf0, 0x41, 0x5e, 0x7a, 0x6a, 0x24, 0x45, 0xb6, 0x7f, 0xb3, 0x9a, 0x07, 0xc6, 0xc6, 0x79, + 0x9a, 0x86, 0xa5, 0x48, 0xa7, 0x51, 0x4a, 0xc7, 0x17, 0xf9, 0x07, 0xe3, 0xed, 0x60, 0x85, 0xa7, + 0x03, 0x00, 0x00 +}; // Autogenerated from wled00/data/liveviewws.htm, do not edit!! -const char PAGE_liveviewws[] PROGMEM = R"=====( -WLED Live Preview
)====="; +const uint16_t PAGE_liveviewws_length = 711; +const uint8_t PAGE_liveviewws[] PROGMEM = { + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0a, 0x6d, 0x54, 0x5d, 0x8f, 0x9b, 0x3a, + 0x10, 0x7d, 0xcf, 0xaf, 0xa0, 0xde, 0xdb, 0x2d, 0x56, 0x08, 0x24, 0xdb, 0xdb, 0x2f, 0xc0, 0x59, + 0xb5, 0xb7, 0x79, 0xa8, 0xb4, 0x6a, 0x57, 0xda, 0x5e, 0xad, 0xaa, 0x55, 0xa4, 0x1a, 0x98, 0x80, + 0xef, 0x82, 0x1d, 0xd9, 0x43, 0x50, 0x84, 0xf8, 0xef, 0x77, 0x20, 0xdb, 0xac, 0x2a, 0x95, 0x07, + 0xdb, 0xc3, 0x9c, 0x99, 0x33, 0x33, 0x3e, 0x90, 0xbe, 0xf8, 0xfc, 0xed, 0x9f, 0xef, 0x3f, 0x6e, + 0x37, 0x5e, 0x85, 0x4d, 0xbd, 0x4e, 0x9f, 0x56, 0x90, 0xc5, 0x3a, 0x6d, 0x00, 0xa5, 0xa7, 0x65, + 0x03, 0x82, 0x1d, 0x14, 0x74, 0x7b, 0x63, 0x91, 0x79, 0xb3, 0xdc, 0x68, 0x04, 0x8d, 0x82, 0x75, + 0xaa, 0xc0, 0x4a, 0x14, 0x70, 0x50, 0x39, 0x2c, 0x26, 0x23, 0x50, 0x5a, 0xa1, 0x92, 0xf5, 0xc2, + 0xe5, 0xb2, 0x06, 0xb1, 0x0a, 0x1a, 0x7a, 0xd1, 0xb4, 0xcd, 0x2f, 0x9b, 0x3d, 0xe5, 0x9c, 0xe5, + 0x95, 0xb4, 0x0e, 0x28, 0x47, 0x8b, 0xbb, 0xc5, 0x7b, 0xf6, 0x1b, 0x15, 0x56, 0xd0, 0xc0, 0x22, + 0x37, 0xb5, 0xb1, 0xcc, 0x3b, 0x93, 0x5d, 0x5c, 0x4d, 0x0f, 0x41, 0x51, 0x61, 0x0d, 0xeb, 0xd9, + 0xfd, 0xcd, 0xe6, 0xb3, 0x77, 0xa3, 0x0e, 0xe0, 0xdd, 0x5a, 0x18, 0xcb, 0x4b, 0xa3, 0x93, 0x27, + 0x75, 0x78, 0x1c, 0x01, 0x99, 0x29, 0x8e, 0x7d, 0x23, 0x6d, 0xa9, 0x74, 0xbc, 0x1c, 0x2e, 0x72, + 0xa9, 0x0f, 0x7d, 0x26, 0xf3, 0xc7, 0xd2, 0x9a, 0x56, 0x17, 0xf1, 0xc5, 0x72, 0xb9, 0x4c, 0x76, + 0xaa, 0x46, 0xb0, 0x71, 0x66, 0x55, 0x59, 0xa1, 0x06, 0xe7, 0xfc, 0xd5, 0xbb, 0x37, 0x2f, 0x79, + 0x32, 0x75, 0x13, 0xaf, 0x96, 0xcb, 0x97, 0x49, 0x05, 0xa3, 0xef, 0x74, 0xde, 0x1b, 0x47, 0xfd, + 0x19, 0x1d, 0xcb, 0xcc, 0x99, 0xba, 0x45, 0x18, 0x66, 0x69, 0x74, 0xa2, 0x4b, 0xa3, 0xd3, 0xcc, + 0x46, 0xd6, 0x75, 0x5a, 0xa8, 0x83, 0xa7, 0x0a, 0xc1, 0x46, 0x52, 0x2a, 0x39, 0x22, 0x9b, 0xea, + 0xca, 0xad, 0xda, 0xe3, 0x7a, 0x76, 0x90, 0xd6, 0xeb, 0x5c, 0x82, 0xf6, 0xd8, 0x77, 0x4e, 0xa0, + 0xd9, 0x87, 0x9d, 0xd2, 0x85, 0xe9, 0xc2, 0xce, 0x0d, 0xb9, 0xc4, 0xbc, 0xf2, 0x81, 0xf7, 0x43, + 0xe7, 0x2e, 0x2f, 0x3b, 0x17, 0x5a, 0xca, 0x7a, 0xbc, 0x43, 0x89, 0x20, 0x84, 0xb8, 0x87, 0xec, + 0xce, 0xe4, 0x8f, 0x80, 0xe1, 0xb7, 0xdb, 0xcd, 0xd7, 0x6b, 0x72, 0x3b, 0xd0, 0x85, 0xcf, 0xfa, + 0x57, 0xf5, 0xe1, 0x55, 0x8c, 0xb6, 0x85, 0x81, 0xf1, 0xd8, 0xa7, 0xa1, 0x51, 0x79, 0x10, 0x2a, + 0xbd, 0x33, 0x3e, 0xbb, 0x05, 0x78, 0xf4, 0xee, 0xef, 0x3c, 0xb3, 0x07, 0xad, 0x74, 0xc9, 0x78, + 0xe0, 0x13, 0xad, 0x86, 0xce, 0x3b, 0xa7, 0xf3, 0x7d, 0x56, 0x21, 0xee, 0x5d, 0xcc, 0x84, 0x78, + 0xaa, 0xa5, 0x36, 0x54, 0x0a, 0xb5, 0x1a, 0xee, 0xad, 0x41, 0x43, 0xb7, 0x71, 0xcd, 0x3a, 0xe7, + 0x58, 0x4c, 0x2b, 0xe3, 0x73, 0x16, 0x47, 0x11, 0x9b, 0x17, 0x26, 0x6f, 0x1b, 0xba, 0x9d, 0x67, + 0x70, 0x65, 0x1c, 0xce, 0x59, 0x34, 0x62, 0x78, 0x68, 0xf4, 0x48, 0x29, 0x76, 0xad, 0xce, 0x47, + 0xa7, 0xcf, 0xfb, 0x3f, 0x17, 0x3c, 0xf0, 0x80, 0x1c, 0x99, 0xd2, 0xd2, 0x1e, 0xbf, 0x1f, 0xf7, + 0x24, 0x01, 0x69, 0xad, 0x3c, 0x66, 0xed, 0x6e, 0x07, 0x96, 0x8d, 0x3e, 0x59, 0x14, 0x9b, 0x03, + 0x11, 0xdd, 0x28, 0x47, 0x6a, 0x00, 0xeb, 0xb3, 0x86, 0xee, 0x4a, 0x96, 0xc0, 0x02, 0x10, 0xeb, + 0x7e, 0x9c, 0xa4, 0xda, 0xf9, 0xec, 0xc1, 0x64, 0xff, 0x41, 0x8e, 0xde, 0xc7, 0x31, 0xfc, 0xd3, + 0x14, 0xbe, 0xa5, 0x8e, 0x68, 0xc4, 0x77, 0x68, 0xa9, 0xf5, 0x90, 0x54, 0x58, 0xfb, 0x10, 0x16, + 0x12, 0x25, 0xe7, 0x7d, 0x0d, 0xe8, 0xc1, 0x34, 0x88, 0x7f, 0x95, 0xc6, 0xf7, 0x53, 0x94, 0x0f, + 0x23, 0xcf, 0x09, 0x91, 0x50, 0xce, 0x77, 0x6f, 0x5f, 0x08, 0x78, 0x58, 0x6e, 0xb9, 0x05, 0x6c, + 0xad, 0x4e, 0xc6, 0x18, 0x52, 0x63, 0xad, 0x34, 0x48, 0xbb, 0x28, 0xad, 0x2c, 0x14, 0xe1, 0xfd, + 0x0f, 0xcb, 0x02, 0xca, 0x80, 0x05, 0x5a, 0x40, 0x58, 0x83, 0x2e, 0xe9, 0x53, 0xb0, 0xe2, 0x4a, + 0x50, 0xe8, 0x6a, 0x7b, 0xfd, 0x77, 0x7c, 0x95, 0xec, 0x8c, 0xf5, 0x95, 0xb0, 0x89, 0x4a, 0x75, + 0xa2, 0xe6, 0xe2, 0x35, 0xc7, 0xb9, 0xf8, 0x69, 0xcb, 0xcc, 0xff, 0xab, 0x87, 0x07, 0xb5, 0x1d, + 0x82, 0x69, 0x9f, 0xaf, 0xce, 0xa7, 0xab, 0xed, 0xc0, 0x7f, 0x06, 0x04, 0x5f, 0xbc, 0xbe, 0xbc, + 0xf4, 0x09, 0xcd, 0x02, 0xc6, 0x93, 0x71, 0xe7, 0x2c, 0x38, 0x0f, 0xbe, 0x04, 0xdc, 0xd4, 0x30, + 0x1e, 0x3f, 0x1d, 0xbf, 0xd0, 0x5c, 0x27, 0xbd, 0xf1, 0x70, 0x92, 0x64, 0xf8, 0x2c, 0x76, 0x81, + 0xc3, 0xb3, 0xb2, 0x7e, 0xa9, 0x03, 0xac, 0xa5, 0xa2, 0xce, 0xf2, 0x98, 0xcc, 0x98, 0x06, 0xca, + 0x87, 0x81, 0x8f, 0xb2, 0x3e, 0xa9, 0x35, 0x8d, 0x4e, 0x8a, 0x8e, 0xa6, 0x1f, 0xc3, 0xff, 0x16, + 0x7e, 0x9e, 0x8e, 0x2e, 0x04, 0x00, 0x00 +}; + + +// Autogenerated from wled00/data/liveviewws2D.htm, do not edit!! +const uint16_t PAGE_liveviewws2D_length = 818; +const uint8_t PAGE_liveviewws2D[] PROGMEM = { + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0a, 0x6d, 0x54, 0x6d, 0x6f, 0xdb, 0x36, + 0x10, 0xfe, 0xee, 0x5f, 0xa1, 0x70, 0x43, 0x2b, 0xda, 0xb2, 0xe4, 0xb8, 0xed, 0x96, 0xd9, 0xa2, + 0x8b, 0x36, 0x35, 0xb0, 0x02, 0xd9, 0x6a, 0xc0, 0x19, 0x82, 0x21, 0x30, 0x50, 0x5a, 0x3a, 0x5b, + 0x5c, 0x25, 0xd2, 0xa0, 0xce, 0x96, 0x35, 0x47, 0xff, 0x7d, 0x47, 0xc9, 0xf1, 0x32, 0x74, 0xfa, + 0x40, 0x91, 0xf7, 0xf2, 0xdc, 0xf1, 0xb9, 0x3b, 0xc6, 0x57, 0x9f, 0xbe, 0xdc, 0xde, 0xff, 0xb9, + 0x98, 0x7b, 0x19, 0x16, 0xf9, 0x2c, 0x3e, 0xaf, 0x20, 0xd3, 0x59, 0x5c, 0x00, 0x4a, 0x4f, 0xcb, + 0x02, 0x04, 0x3b, 0x28, 0xa8, 0x76, 0xc6, 0x22, 0xf3, 0x7a, 0x89, 0xd1, 0x08, 0x1a, 0x05, 0xab, + 0x54, 0x8a, 0x99, 0x48, 0xe1, 0xa0, 0x12, 0x18, 0xb6, 0x87, 0x40, 0x69, 0x85, 0x4a, 0xe6, 0xc3, + 0x32, 0x91, 0x39, 0x88, 0xeb, 0xa0, 0x20, 0x41, 0xb1, 0x2f, 0x9e, 0xcf, 0xec, 0x8c, 0xd9, 0x4b, + 0x32, 0x69, 0x4b, 0x20, 0x8c, 0x3d, 0x6e, 0x86, 0x37, 0xec, 0x3f, 0xa1, 0x30, 0x83, 0x02, 0x86, + 0x89, 0xc9, 0x8d, 0x65, 0xde, 0x25, 0xd8, 0x0f, 0xe3, 0xf6, 0x23, 0x53, 0x54, 0x98, 0xc3, 0xac, + 0xf7, 0x70, 0x37, 0xff, 0xe4, 0xdd, 0xa9, 0x03, 0x78, 0x0b, 0x0b, 0x2e, 0xbd, 0x38, 0xea, 0x34, + 0x71, 0x89, 0x35, 0xfd, 0xd6, 0x26, 0xad, 0x4f, 0x85, 0xb4, 0x5b, 0xa5, 0x27, 0xa3, 0x26, 0x8e, + 0x3a, 0x69, 0x1c, 0x75, 0x57, 0x73, 0xda, 0x59, 0x9c, 0x48, 0x7d, 0x90, 0xa5, 0xd7, 0x53, 0xa9, + 0x60, 0x6e, 0x4f, 0xe8, 0x51, 0x27, 0x23, 0x94, 0xc4, 0xaa, 0x1d, 0xce, 0x7a, 0x07, 0x69, 0xbd, + 0x44, 0xa4, 0x26, 0xd9, 0x17, 0x94, 0x48, 0xb8, 0x05, 0x9c, 0xe7, 0xe0, 0xb6, 0x1f, 0xeb, 0xcf, + 0xa9, 0xdf, 0xb9, 0xf1, 0x20, 0x87, 0xb4, 0x14, 0x8c, 0x05, 0x98, 0x59, 0x83, 0x94, 0x45, 0x2a, + 0xae, 0xae, 0xa7, 0x9b, 0xbd, 0x4e, 0x50, 0x19, 0xed, 0xd1, 0x55, 0x6f, 0x5b, 0x58, 0x9f, 0x9f, + 0x92, 0xb0, 0xe3, 0x2d, 0xfc, 0xe5, 0xa6, 0x5f, 0x29, 0x9d, 0x9a, 0x2a, 0x54, 0x5a, 0x83, 0x7d, + 0x68, 0x09, 0x4c, 0xc2, 0x0c, 0xd4, 0x36, 0xc3, 0xef, 0xd4, 0xbf, 0xb6, 0xe2, 0xe6, 0x05, 0xd2, + 0xb4, 0xcd, 0x0c, 0x8f, 0x22, 0x71, 0x49, 0xdd, 0x3a, 0xa2, 0x8e, 0xe8, 0xb3, 0x71, 0xca, 0xf8, + 0x54, 0x6d, 0x7c, 0xd2, 0xf0, 0x93, 0x33, 0xa9, 0xca, 0x29, 0xda, 0xfa, 0x54, 0x95, 0x02, 0xcd, + 0x2e, 0x3c, 0x63, 0x56, 0x65, 0x93, 0x48, 0x4c, 0x32, 0x1f, 0xf9, 0xa9, 0xa9, 0xca, 0x57, 0xaf, + 0xaa, 0x32, 0xb4, 0xc4, 0x4c, 0xbd, 0x44, 0x89, 0x20, 0x84, 0x78, 0x80, 0xf5, 0xd2, 0x24, 0xdf, + 0x00, 0xc3, 0x2f, 0x8b, 0xf9, 0xef, 0xef, 0x49, 0x5d, 0x82, 0xa6, 0xfb, 0x9e, 0x5e, 0xe7, 0x87, + 0xd7, 0x13, 0xb4, 0x7b, 0x68, 0x18, 0x9f, 0xf8, 0x84, 0xaa, 0xa1, 0xf2, 0x2e, 0xd6, 0xbe, 0xcf, + 0x32, 0xc4, 0x5d, 0x39, 0x61, 0x42, 0x9c, 0x43, 0xe5, 0x86, 0x22, 0x11, 0x0b, 0xe1, 0x8e, 0xa8, + 0x31, 0x54, 0xd7, 0xf7, 0xac, 0x2a, 0x4b, 0x36, 0xa1, 0x95, 0xf1, 0x01, 0x9b, 0x44, 0x11, 0x1b, + 0x5c, 0xe8, 0xbd, 0x18, 0x67, 0xa6, 0xc4, 0x01, 0x8b, 0x9c, 0x0d, 0x0f, 0x8d, 0x36, 0x3b, 0xd0, + 0xc2, 0xe7, 0x62, 0x76, 0xfa, 0xff, 0x4c, 0x9a, 0x80, 0xe4, 0x6b, 0xa5, 0xa5, 0xad, 0xef, 0xeb, + 0x1d, 0x75, 0x91, 0xb4, 0x56, 0xd6, 0xeb, 0xfd, 0x66, 0x03, 0x96, 0x39, 0x9d, 0x4c, 0xd3, 0xf9, + 0x81, 0x22, 0xdc, 0xa9, 0x92, 0x1a, 0x0a, 0xac, 0xcf, 0x0a, 0x28, 0x4b, 0xb9, 0x05, 0xaa, 0x19, + 0xa1, 0x3a, 0x86, 0x88, 0x34, 0xf6, 0x68, 0xd6, 0x7f, 0x41, 0x82, 0xde, 0x07, 0xe7, 0xfe, 0xb1, + 0x75, 0x5f, 0xd1, 0x55, 0x88, 0xba, 0x25, 0x5a, 0xa5, 0xb7, 0x21, 0x35, 0x72, 0xee, 0x63, 0x98, + 0x4a, 0x94, 0x9c, 0x9f, 0x72, 0x40, 0x0f, 0x5b, 0x06, 0xfe, 0x50, 0x1a, 0x6f, 0x5a, 0x2f, 0x1f, + 0x5c, 0x9c, 0xce, 0xc2, 0x15, 0xe2, 0xe7, 0x9f, 0xae, 0x04, 0x3e, 0x8e, 0x56, 0x4f, 0x4f, 0x63, + 0xb7, 0xb9, 0xa6, 0xcd, 0x95, 0x2b, 0x8e, 0x05, 0xdc, 0x5b, 0x3d, 0x75, 0x10, 0x96, 0xe4, 0xe3, + 0x55, 0xa0, 0xe9, 0xf7, 0x66, 0x15, 0x18, 0xf1, 0x9b, 0xc4, 0x2c, 0xa4, 0xd9, 0xf1, 0xcf, 0xcd, + 0x12, 0xd9, 0x4b, 0x63, 0x44, 0x9a, 0x07, 0xb2, 0x33, 0xd8, 0xe4, 0xc6, 0x58, 0xff, 0xd9, 0x66, + 0x68, 0xfa, 0x96, 0x47, 0xe3, 0xae, 0x2d, 0x40, 0xbc, 0x9d, 0x6e, 0x48, 0x59, 0x8b, 0xf0, 0xdd, + 0xb4, 0x8e, 0xf5, 0xb4, 0x1e, 0x0c, 0xb8, 0x13, 0x1c, 0x9d, 0xe0, 0x18, 0xdb, 0xe9, 0x91, 0x04, + 0x94, 0x45, 0xb8, 0x51, 0x79, 0xbe, 0x74, 0xa3, 0x21, 0xbe, 0xda, 0xed, 0xda, 0xff, 0xf1, 0x84, + 0x8f, 0xb0, 0x6a, 0x82, 0xf6, 0x3f, 0xb8, 0xbe, 0xec, 0xc6, 0xab, 0x86, 0x7f, 0x0d, 0x9c, 0xc3, + 0x1a, 0x68, 0xa2, 0x16, 0x14, 0xdf, 0xe7, 0xed, 0x59, 0xda, 0xc4, 0x3f, 0xf6, 0xcd, 0x40, 0x06, + 0x75, 0xdf, 0x04, 0xe1, 0x5b, 0x5a, 0x46, 0xc1, 0xb8, 0xdf, 0x66, 0xb8, 0xf8, 0xdc, 0xd9, 0xb8, + 0x20, 0x64, 0x0e, 0x03, 0xf1, 0xa6, 0xf9, 0xb7, 0xef, 0x68, 0xb6, 0x4b, 0x93, 0x43, 0x08, 0xd6, + 0x52, 0x66, 0x6c, 0x01, 0xf0, 0xcd, 0x7b, 0x58, 0x7a, 0xed, 0x71, 0x42, 0x65, 0xe1, 0x4d, 0xc3, + 0x9b, 0x73, 0x13, 0x7d, 0x5f, 0x3e, 0x0b, 0xa5, 0xfa, 0xfb, 0xb9, 0x7a, 0xcf, 0x53, 0xf7, 0xf4, + 0xe4, 0xbf, 0x98, 0x90, 0x97, 0xd3, 0x38, 0x0a, 0x48, 0x71, 0xaf, 0x0a, 0x30, 0x7b, 0x6a, 0x52, + 0xfe, 0xd2, 0x89, 0x46, 0xb5, 0x09, 0xc6, 0xef, 0x46, 0x9c, 0x37, 0xbc, 0x47, 0x0f, 0x45, 0x37, + 0xf8, 0x71, 0xd4, 0xbd, 0x11, 0x51, 0xfb, 0x22, 0xfe, 0x03, 0x69, 0xb3, 0xce, 0x95, 0x27, 0x05, + 0x00, 0x00 +}; // Autogenerated from wled00/data/404.htm, do not edit!! -const char PAGE_404[] PROGMEM = R"=====(Not found -

404 Not Found

Akemi does not know where you are headed...

- -)====="; +const uint16_t PAGE_404_length = 868; +const uint8_t PAGE_404[] PROGMEM = { + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0a, 0x65, 0x54, 0x5b, 0x73, 0xaa, 0x3a, + 0x14, 0x7e, 0xef, 0xaf, 0xe0, 0x78, 0xe6, 0xcc, 0x7e, 0x68, 0x2d, 0xa8, 0xd8, 0x2a, 0xa2, 0x33, + 0x01, 0x51, 0xec, 0xc5, 0x7a, 0xa3, 0xd6, 0xbe, 0x05, 0x12, 0x21, 0x15, 0x08, 0x4d, 0x82, 0x62, + 0x3b, 0xfd, 0xef, 0x3b, 0x40, 0xf7, 0x9c, 0xce, 0xec, 0x35, 0x03, 0x2b, 0xf9, 0x56, 0xd6, 0x7d, + 0x25, 0xe6, 0x3f, 0xe3, 0x27, 0x7b, 0xb3, 0x5b, 0x38, 0x4a, 0x24, 0x92, 0x78, 0x64, 0x7e, 0xff, + 0x31, 0x44, 0x23, 0x33, 0xc1, 0x02, 0x2a, 0x41, 0x04, 0x19, 0xc7, 0x62, 0xd8, 0xc8, 0xc5, 0xbe, + 0xd9, 0x6b, 0x7c, 0xa3, 0x17, 0x01, 0x4d, 0x05, 0x4e, 0x25, 0x7c, 0x22, 0x48, 0x44, 0x43, 0x84, + 0x8f, 0x24, 0xc0, 0xcd, 0x6a, 0xd3, 0x50, 0x52, 0x98, 0xe0, 0x61, 0xe3, 0x48, 0xf0, 0x29, 0xa3, + 0x4c, 0xfc, 0xd1, 0xa9, 0x51, 0x11, 0xe1, 0x04, 0x37, 0x03, 0x1a, 0x53, 0xd6, 0xf8, 0x61, 0xe6, + 0xdf, 0x76, 0x45, 0xf2, 0xac, 0x20, 0x22, 0xc6, 0xa3, 0x39, 0x15, 0xca, 0x9e, 0xe6, 0x29, 0x32, + 0xd5, 0x1a, 0x30, 0xb9, 0x38, 0x4b, 0x76, 0xe1, 0x53, 0x74, 0xfe, 0xdc, 0x4b, 0xb5, 0xe6, 0x1e, + 0x26, 0x24, 0x3e, 0x1b, 0xcf, 0x98, 0x21, 0x98, 0xc2, 0x2b, 0x17, 0xc7, 0x47, 0x2c, 0x48, 0x00, + 0xaf, 0x38, 0x4c, 0x79, 0x93, 0x63, 0x46, 0xf6, 0x03, 0x81, 0x0b, 0xd1, 0x84, 0x31, 0x09, 0x53, + 0x23, 0x90, 0x7e, 0x30, 0x1b, 0xf8, 0x30, 0x38, 0x84, 0xac, 0xb4, 0x5c, 0x07, 0x61, 0x94, 0x9e, + 0x07, 0x09, 0x64, 0x21, 0x49, 0x0d, 0x6d, 0xf0, 0x8d, 0xed, 0xf7, 0xfb, 0x2f, 0x92, 0x84, 0x9f, + 0x55, 0x42, 0x86, 0xae, 0x69, 0x59, 0x21, 0xcf, 0x14, 0x75, 0x82, 0x46, 0x57, 0xfb, 0x6f, 0x40, + 0x12, 0x18, 0xe2, 0x26, 0xc3, 0x29, 0x92, 0x8e, 0xd2, 0xd0, 0xc8, 0x48, 0x81, 0x63, 0x28, 0x30, + 0xfa, 0x4b, 0x12, 0x30, 0xc2, 0xb3, 0x26, 0x46, 0x21, 0xe6, 0x7f, 0xfc, 0xb4, 0xbb, 0x59, 0xa1, + 0x68, 0x4a, 0xb3, 0xa5, 0x95, 0xfc, 0xcb, 0xcf, 0x85, 0xa0, 0xe9, 0x27, 0xcd, 0x45, 0x4c, 0x52, + 0x5c, 0x46, 0x91, 0x33, 0x2e, 0xc3, 0xc8, 0x28, 0xa9, 0x62, 0xce, 0x20, 0x42, 0xa5, 0xa5, 0x5e, + 0x15, 0x45, 0x65, 0xa1, 0xd4, 0x1c, 0xd4, 0xd1, 0xb4, 0x3b, 0xe5, 0xba, 0xca, 0x54, 0x30, 0x99, + 0xfa, 0x9e, 0xb2, 0xc4, 0xc8, 0xb3, 0x0c, 0xb3, 0x00, 0x72, 0x3c, 0xf8, 0x59, 0xab, 0xe8, 0x4f, + 0x8d, 0x6a, 0x94, 0x93, 0x0f, 0x6c, 0xb4, 0xfa, 0x52, 0xfb, 0xef, 0xaa, 0x74, 0x3a, 0x9d, 0x1f, + 0xc5, 0x18, 0xf8, 0x94, 0xc9, 0x74, 0x0c, 0x4d, 0xe1, 0x34, 0x26, 0x48, 0xf9, 0x81, 0x35, 0x19, + 0x44, 0x24, 0xe7, 0x55, 0x4e, 0x5f, 0x17, 0xa6, 0x5a, 0xf7, 0xc9, 0x54, 0xeb, 0x19, 0x2a, 0xdb, + 0x35, 0x32, 0x65, 0x29, 0x15, 0x18, 0xcb, 0x36, 0xcb, 0x96, 0x73, 0x16, 0x0c, 0x1b, 0x08, 0x0a, + 0x68, 0x54, 0x85, 0x52, 0xb3, 0x34, 0x94, 0xee, 0x39, 0xbe, 0xd1, 0xaf, 0xc8, 0xb3, 0xf5, 0xb4, + 0x3a, 0x69, 0xf7, 0xd3, 0x90, 0x02, 0x49, 0xf3, 0xb5, 0x17, 0x39, 0x5e, 0x28, 0x57, 0x76, 0xb9, + 0x05, 0xa1, 0x0d, 0x1e, 0x25, 0xb3, 0x9c, 0x6c, 0xc6, 0xa6, 0x15, 0xf2, 0x32, 0x5f, 0xaf, 0xb4, + 0x19, 0x60, 0x5c, 0x0f, 0x6e, 0x96, 0x25, 0xb0, 0x4a, 0x97, 0x5e, 0xcb, 0x92, 0x0a, 0xc5, 0xdb, + 0xe9, 0xd8, 0xdb, 0x2d, 0xbd, 0x12, 0xf4, 0x3d, 0xa7, 0xf0, 0x56, 0x95, 0xdc, 0xea, 0xb5, 0x42, + 0xdb, 0x53, 0x3f, 0xee, 0xdf, 0xd5, 0x92, 0xfa, 0xfe, 0xb6, 0x45, 0x6d, 0x10, 0x4e, 0x23, 0x0a, + 0x4b, 0xf1, 0x74, 0xf1, 0xf0, 0xd2, 0xab, 0x2c, 0xdf, 0xa1, 0xc9, 0xdd, 0x93, 0xa7, 0xfe, 0x4f, + 0x60, 0x32, 0x5f, 0x60, 0x6b, 0x56, 0xc9, 0x02, 0x27, 0x7a, 0x0d, 0x4e, 0x00, 0x8c, 0x79, 0xb9, + 0xbd, 0x05, 0x60, 0xcb, 0xb6, 0x64, 0x79, 0x28, 0x03, 0x45, 0x6b, 0x6f, 0x65, 0x3d, 0x8f, 0xa3, + 0x45, 0x11, 0xf4, 0xfd, 0x31, 0xf5, 0x42, 0x07, 0xcc, 0x97, 0xd8, 0x5f, 0xa8, 0x13, 0x2f, 0x77, + 0x1f, 0xdf, 0xac, 0xe9, 0x4e, 0xb5, 0x2e, 0x3b, 0xce, 0x6e, 0x75, 0xbb, 0x72, 0xb5, 0x77, 0x5b, + 0x7d, 0xb5, 0x82, 0x1b, 0xf7, 0x64, 0xc7, 0x6f, 0xa1, 0xfb, 0x74, 0x59, 0xbc, 0xce, 0x9e, 0xd7, + 0xb3, 0x36, 0xdf, 0x85, 0x2e, 0x9c, 0xde, 0x3a, 0xd6, 0x36, 0xea, 0xbd, 0x6d, 0x69, 0xb1, 0x61, + 0xb6, 0x35, 0x41, 0xe3, 0xbb, 0x4b, 0x6b, 0xac, 0xc7, 0xfe, 0xcc, 0x2d, 0x40, 0xf0, 0xd1, 0x03, + 0x0b, 0xf0, 0xfc, 0xb0, 0xe1, 0xec, 0xd5, 0xd1, 0xf1, 0x72, 0xdc, 0x7d, 0xff, 0x10, 0x9d, 0x00, + 0x4c, 0x36, 0x3b, 0x7a, 0xb0, 0xf5, 0x9d, 0x3d, 0xef, 0x4f, 0xcf, 0x7e, 0x98, 0xeb, 0x67, 0xb0, + 0x14, 0xd6, 0xe4, 0x61, 0xf9, 0xe2, 0xe6, 0x2e, 0xb0, 0xc0, 0xed, 0xdd, 0x23, 0x7e, 0x72, 0x6c, + 0xd5, 0xd1, 0xb6, 0xdd, 0xfc, 0xdc, 0x0f, 0x8f, 0xfa, 0x91, 0x76, 0x97, 0xee, 0x7d, 0x9b, 0xdc, + 0x9e, 0xdf, 0xdb, 0x76, 0xcf, 0x03, 0xd6, 0xa3, 0xee, 0x26, 0x0f, 0x97, 0xf6, 0x7a, 0xf3, 0x62, + 0x6f, 0x26, 0xad, 0x31, 0xb3, 0x5f, 0x6e, 0x2e, 0xa7, 0x59, 0xff, 0xc3, 0xea, 0xa2, 0x2a, 0x5b, + 0xe0, 0xc4, 0x93, 0xcd, 0x61, 0x9d, 0x2f, 0x13, 0xdb, 0x6e, 0x8c, 0x2e, 0xcc, 0xa8, 0x35, 0xd2, + 0x35, 0x5d, 0x29, 0xef, 0xeb, 0xa4, 0xbe, 0xaf, 0x12, 0x31, 0xfd, 0x11, 0x38, 0xe0, 0x84, 0x28, + 0x88, 0x62, 0xae, 0xa4, 0x52, 0x76, 0x48, 0xe9, 0x49, 0x39, 0x45, 0x98, 0x61, 0xe5, 0x4c, 0x73, + 0x05, 0x4a, 0x5e, 0x0e, 0x08, 0x46, 0xd7, 0xd7, 0xd7, 0xa6, 0xea, 0x4b, 0x0d, 0x56, 0x7d, 0x17, + 0x66, 0x7d, 0x19, 0x14, 0x9a, 0x06, 0x31, 0x09, 0x0e, 0xc3, 0x5f, 0x27, 0x92, 0x22, 0x7a, 0xba, + 0x8e, 0x69, 0x00, 0x05, 0xa1, 0xe9, 0x75, 0xc4, 0xf0, 0x7e, 0xd8, 0x50, 0xb9, 0x1c, 0x43, 0xcc, + 0x78, 0xe3, 0xd7, 0xc8, 0x92, 0xb3, 0xab, 0x08, 0xaa, 0x94, 0x6f, 0x09, 0xa3, 0x31, 0x97, 0xe6, + 0x2a, 0x13, 0xd2, 0x96, 0x5a, 0x8f, 0x9f, 0x5a, 0xbd, 0x6a, 0xbf, 0x01, 0x22, 0xc8, 0xb7, 0x64, + 0xeb, 0x04, 0x00, 0x00 +}; // Autogenerated from wled00/data/favicon.ico, do not edit!! @@ -116,3 +413,755 @@ const uint8_t favicon[] PROGMEM = { 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; + +// Autogenerated from wled00/data/iro.js, do not edit!! +const uint16_t iroJs_length = 9992; +const uint8_t iroJs[] PROGMEM = { + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0a, 0xc5, 0x7d, 0x79, 0x77, 0xe3, 0x36, + 0x96, 0xef, 0xff, 0x73, 0xce, 0x7c, 0x07, 0x99, 0x49, 0x7c, 0x48, 0x0b, 0xa2, 0x25, 0x79, 0xa9, + 0x32, 0x55, 0x1c, 0x9d, 0xa4, 0xb2, 0x55, 0x77, 0x2a, 0x95, 0x49, 0x55, 0xa7, 0xa7, 0xa3, 0x28, + 0x39, 0x14, 0x05, 0x49, 0x2c, 0xd3, 0xa4, 0xc2, 0x45, 0xb6, 0x63, 0xe9, 0xbb, 0xbf, 0xdf, 0xc5, + 0x42, 0x82, 0x5a, 0x6c, 0x27, 0x6f, 0xfa, 0xbd, 0x93, 0x94, 0x08, 0x62, 0xb9, 0x00, 0x2e, 0xee, + 0x0e, 0x80, 0x3e, 0x3d, 0x39, 0xfa, 0xcf, 0xff, 0x68, 0x9d, 0xb4, 0xa2, 0x2c, 0x75, 0x3f, 0xe6, + 0xad, 0xd5, 0x85, 0x7b, 0xe1, 0xf6, 0x45, 0x4e, 0xbf, 0xdb, 0xbb, 0xec, 0xf4, 0xbb, 0xfd, 0x5e, + 0xeb, 0x6f, 0xc1, 0x0d, 0xcf, 0x5b, 0x5f, 0x06, 0x49, 0xc4, 0x63, 0x51, 0xf4, 0x5d, 0x14, 0xf2, + 0x24, 0xe7, 0xd3, 0x56, 0x99, 0x4c, 0x79, 0xd6, 0x7a, 0xfb, 0xc3, 0x77, 0xad, 0xbe, 0xdb, 0x15, + 0x45, 0xf3, 0xa8, 0x58, 0x94, 0x13, 0x37, 0x4c, 0x6f, 0x4e, 0x3f, 0x06, 0xd4, 0xee, 0x54, 0x42, + 0xa6, 0xc2, 0xd3, 0xff, 0xfc, 0x8f, 0xa3, 0x59, 0x99, 0x84, 0x45, 0x94, 0x26, 0x76, 0xc1, 0x12, + 0xe7, 0xc1, 0x4a, 0x27, 0x1f, 0x79, 0x58, 0x58, 0xbe, 0x5f, 0xdc, 0x2f, 0x79, 0x3a, 0x6b, 0xf1, + 0xbb, 0x65, 0x9a, 0x15, 0xf9, 0xf1, 0xb1, 0x45, 0xa0, 0x67, 0x51, 0xc2, 0xa7, 0xd6, 0x91, 0x2e, + 0xbc, 0x49, 0xa7, 0x65, 0xcc, 0x87, 0xf2, 0xe1, 0xaa, 0xaa, 0x7e, 0x62, 0x3b, 0x9e, 0xa5, 0xc1, + 0xd6, 0x90, 0x64, 0xeb, 0xe3, 0x63, 0xf9, 0x74, 0x83, 0x9b, 0xe9, 0x50, 0x26, 0xed, 0xc4, 0xf1, + 0xec, 0xc2, 0x2f, 0xd6, 0xeb, 0x9c, 0xc7, 0x33, 0xc7, 0xc5, 0xf8, 0x08, 0xc6, 0xc6, 0x2e, 0x16, + 0x51, 0xce, 0xaa, 0xf1, 0x61, 0x70, 0x65, 0xce, 0x5b, 0x79, 0x91, 0x45, 0x18, 0xe0, 0x60, 0x15, + 0x64, 0xad, 0x1b, 0x96, 0xb3, 0x84, 0x45, 0x2c, 0x65, 0x77, 0xfe, 0xc3, 0x86, 0x7d, 0xf4, 0x47, + 0x63, 0x96, 0xf9, 0xa7, 0x41, 0x18, 0x15, 0x6b, 0x7e, 0x67, 0x0f, 0xbd, 0x7c, 0x3d, 0x5f, 0x27, + 0xeb, 0xe5, 0xfa, 0x53, 0x67, 0x9d, 0x2d, 0x17, 0xeb, 0x79, 0x16, 0x4d, 0xd7, 0xe9, 0x6d, 0xbe, + 0xbe, 0x49, 0xc2, 0x75, 0x52, 0xdc, 0xae, 0xd1, 0xfb, 0x28, 0x5c, 0x8c, 0xd7, 0x7f, 0xa4, 0xe9, + 0xfa, 0xd7, 0x34, 0x9b, 0xae, 0x7f, 0xed, 0x74, 0x4e, 0xa3, 0x81, 0xee, 0xb3, 0xf5, 0x56, 0x62, + 0x65, 0x96, 0x66, 0x36, 0xf5, 0x17, 0xb5, 0xa2, 0xa4, 0x95, 0x38, 0xc5, 0x28, 0x1a, 0xfb, 0x09, + 0x7e, 0x06, 0x19, 0x2f, 0xca, 0x2c, 0x69, 0x15, 0x9b, 0xaa, 0xc5, 0xbd, 0x5d, 0x38, 0x0f, 0x54, + 0x37, 0xf1, 0x0b, 0x77, 0x19, 0x64, 0x3c, 0x29, 0xbe, 0x4f, 0xa7, 0x7c, 0x90, 0x1c, 0x1f, 0x27, + 0x6e, 0xc6, 0x6f, 0xd2, 0x15, 0x7f, 0xbd, 0x88, 0xe2, 0x29, 0xaa, 0xd5, 0x8d, 0x16, 0xd4, 0x0d, + 0x8b, 0x64, 0xc3, 0x8c, 0x71, 0x56, 0x62, 0x4a, 0xb1, 0x1f, 0x64, 0xf3, 0xf2, 0x06, 0x00, 0xf2, + 0x41, 0x34, 0xb3, 0x13, 0xff, 0xad, 0x8d, 0x39, 0x26, 0x0e, 0x3b, 0x7b, 0x55, 0x15, 0xb8, 0x31, + 0x4f, 0xe6, 0xc5, 0xc2, 0xa1, 0x01, 0x46, 0x3e, 0x46, 0x84, 0xe9, 0x9f, 0x0d, 0xb2, 0x9d, 0x0a, + 0x83, 0xac, 0xdd, 0x76, 0x22, 0x77, 0x59, 0xe6, 0x0b, 0x3b, 0x1e, 0x65, 0x63, 0x47, 0x40, 0x2c, + 0xe3, 0xf8, 0xc8, 0x8f, 0x8e, 0x8f, 0xed, 0xc4, 0x0d, 0x69, 0x4c, 0x18, 0xac, 0x1f, 0x39, 0x4c, + 0xe6, 0x17, 0x18, 0xb0, 0x4c, 0xb8, 0x58, 0xa6, 0xa0, 0x8c, 0x8b, 0x1f, 0xb2, 0x74, 0x99, 0x8b, + 0xae, 0x38, 0xe1, 0x61, 0x2b, 0x7f, 0x95, 0x46, 0xd3, 0x56, 0xd7, 0xf7, 0x81, 0x18, 0x3e, 0x26, + 0x98, 0x78, 0x6c, 0xb5, 0x45, 0x8e, 0xa3, 0x51, 0x86, 0x25, 0x76, 0xaf, 0xf9, 0xbd, 0xea, 0xcc, + 0x2e, 0x7d, 0xc2, 0xce, 0xcc, 0x21, 0xf2, 0x88, 0x79, 0xc1, 0x5b, 0xe2, 0x55, 0x95, 0xa6, 0x46, + 0x2e, 0xb5, 0x09, 0x05, 0xba, 0x52, 0x56, 0x1a, 0x28, 0x94, 0x79, 0x11, 0xcb, 0x24, 0x12, 0xb9, + 0xff, 0x40, 0x64, 0xe7, 0x15, 0x6c, 0x49, 0x3d, 0x7b, 0x09, 0x43, 0x43, 0x0f, 0xc5, 0x7c, 0xe6, + 0x65, 0x2c, 0xf1, 0x08, 0x30, 0x8b, 0xe4, 0x83, 0x7b, 0x5d, 0x96, 0xca, 0x64, 0x2c, 0x1f, 0xa1, + 0x7a, 0xa4, 0x09, 0xa8, 0xad, 0x0c, 0x8b, 0x34, 0xf3, 0xe4, 0xf4, 0x36, 0x7a, 0xf8, 0x37, 0xee, + 0x2a, 0xc1, 0xb2, 0x1e, 0x1f, 0xab, 0x84, 0xcd, 0x1d, 0xc6, 0xeb, 0xd1, 0xbc, 0x23, 0x2a, 0xd0, + 0xc4, 0x51, 0x21, 0xb7, 0x2e, 0x7f, 0x23, 0xe9, 0x8a, 0x08, 0xdc, 0x15, 0x03, 0xf4, 0x0b, 0x26, + 0x5e, 0xd0, 0x65, 0xc1, 0xef, 0x0a, 0xdf, 0xa8, 0x7b, 0x2b, 0xeb, 0xaa, 0x05, 0x03, 0x7e, 0x9d, + 0x0a, 0x70, 0x34, 0x44, 0xa1, 0x1b, 0x31, 0xfc, 0x73, 0x13, 0x37, 0x02, 0x83, 0xde, 0xbd, 0x9b, + 0xa1, 0xeb, 0x76, 0xcf, 0x11, 0x33, 0x18, 0x54, 0x84, 0x3b, 0x48, 0x5e, 0x15, 0xa8, 0xa2, 0xa8, + 0x21, 0x21, 0x6a, 0xd0, 0x04, 0x00, 0xc2, 0x41, 0xd1, 0x28, 0x19, 0x3b, 0x7a, 0xc5, 0x23, 0x37, + 0xd5, 0x7d, 0x20, 0xa9, 0xa6, 0xbc, 0x87, 0xa3, 0x0b, 0x97, 0x12, 0x34, 0x06, 0xd9, 0x5d, 0x3d, + 0xe6, 0xa0, 0xe2, 0x02, 0x16, 0xd5, 0xa4, 0x46, 0x4c, 0xee, 0x46, 0x4e, 0x4d, 0x58, 0xa1, 0x64, + 0xad, 0xc2, 0x4d, 0xe9, 0xc5, 0x9d, 0x04, 0x39, 0xf7, 0x05, 0xe6, 0x13, 0xbf, 0xfb, 0xa7, 0x47, + 0xfc, 0xd0, 0x00, 0x43, 0x03, 0x9f, 0x64, 0x3c, 0xb8, 0xde, 0xa8, 0x99, 0xd0, 0x90, 0x36, 0xf5, + 0x08, 0x39, 0x8d, 0xd0, 0x3e, 0x2a, 0xdc, 0x19, 0x68, 0x15, 0xbf, 0xfe, 0x51, 0x17, 0xd0, 0x7a, + 0xa0, 0xdf, 0x5c, 0xb2, 0x49, 0xe1, 0xac, 0xd7, 0xd1, 0x91, 0xef, 0xdf, 0x80, 0x84, 0x27, 0x29, + 0x9a, 0xf1, 0x1f, 0x39, 0x09, 0xd7, 0x28, 0x99, 0xa3, 0x22, 0x86, 0xb0, 0xa7, 0x80, 0xd9, 0x7b, + 0x32, 0xd7, 0xeb, 0xc4, 0xb1, 0x4b, 0xc7, 0xa0, 0xd5, 0xd2, 0x96, 0xc8, 0x51, 0x14, 0xab, 0xf9, + 0x5d, 0xac, 0x56, 0xee, 0xe6, 0x10, 0xa2, 0x76, 0x53, 0x2a, 0xab, 0x29, 0x24, 0xee, 0xd4, 0xe5, + 0x9d, 0x82, 0x7e, 0x37, 0xce, 0xa0, 0xa0, 0x91, 0xa6, 0x4b, 0xdb, 0x19, 0x38, 0x72, 0x16, 0x99, + 0x1f, 0xf9, 0x92, 0x4c, 0x59, 0xe9, 0xdb, 0xdc, 0x87, 0xc8, 0x28, 0x1c, 0x77, 0xea, 0xb8, 0x29, + 0x23, 0x6e, 0x5b, 0x42, 0xa2, 0x24, 0x6e, 0xc9, 0xf0, 0xcf, 0x3f, 0xea, 0xb1, 0x54, 0x4c, 0x42, + 0x08, 0xcc, 0x6b, 0x3b, 0xc5, 0x18, 0x84, 0x78, 0x01, 0x19, 0x27, 0xee, 0x2d, 0x93, 0x60, 0x30, + 0xfb, 0xd4, 0x4d, 0x6f, 0x13, 0x9e, 0xbd, 0xff, 0xe9, 0x9b, 0xaf, 0x62, 0x4e, 0x12, 0x85, 0x49, + 0xce, 0x61, 0x31, 0x93, 0xd4, 0x58, 0x62, 0xf9, 0xb9, 0xe3, 0x95, 0x0e, 0x9b, 0xda, 0x11, 0x35, + 0xcf, 0x8e, 0xfc, 0xf2, 0xf8, 0x38, 0x40, 0xa6, 0x39, 0xe5, 0xf7, 0x36, 0x4d, 0xb5, 0xa8, 0x27, + 0xcb, 0x72, 0x89, 0x83, 0x90, 0x05, 0x6c, 0xc6, 0x16, 0x6c, 0xc5, 0xa6, 0x6c, 0xce, 0x26, 0x24, + 0x76, 0xb0, 0xb6, 0xeb, 0xf5, 0x47, 0xb6, 0xf4, 0x27, 0x7a, 0xf5, 0xb1, 0xf2, 0xe8, 0xea, 0x0e, + 0x23, 0x8e, 0x7d, 0xb9, 0xe0, 0xe5, 0xb0, 0x1c, 0x75, 0xc7, 0xde, 0x92, 0x88, 0x8f, 0x75, 0x25, + 0xf9, 0x39, 0x2c, 0xf4, 0xbb, 0x0c, 0x9c, 0xe0, 0x7f, 0x6e, 0xe3, 0xb7, 0xd6, 0x1c, 0x45, 0xc5, + 0x3d, 0x20, 0x3a, 0x91, 0x06, 0x19, 0xfa, 0xc4, 0x36, 0x44, 0x27, 0xbc, 0xdd, 0x53, 0x73, 0xf1, + 0xed, 0x99, 0x3f, 0x19, 0x85, 0x63, 0xac, 0xfc, 0x8c, 0x86, 0x01, 0x91, 0xe1, 0xfb, 0x33, 0x7a, + 0xd0, 0x1b, 0xd1, 0xba, 0x4f, 0xef, 0x94, 0x70, 0xa8, 0x9e, 0xc2, 0xf6, 0x80, 0xc7, 0xd0, 0x4b, + 0xb4, 0x78, 0x01, 0xc8, 0x36, 0x78, 0xb5, 0x1c, 0x04, 0xa0, 0x56, 0xea, 0x46, 0xc0, 0x0b, 0x88, + 0x4c, 0x1f, 0x07, 0xf6, 0x40, 0xb5, 0x34, 0x34, 0x49, 0xb5, 0x33, 0x31, 0xd1, 0x0d, 0x80, 0x2c, + 0xb0, 0x3e, 0x09, 0x50, 0x37, 0xf3, 0x67, 0xeb, 0xf5, 0x5d, 0x85, 0x41, 0x29, 0xaf, 0x80, 0x46, + 0x86, 0x6e, 0x0b, 0x25, 0x3c, 0x67, 0xf4, 0x3c, 0xf2, 0x03, 0x60, 0x6a, 0xee, 0x83, 0xf2, 0x46, + 0x63, 0x47, 0x52, 0x73, 0x80, 0xc9, 0x86, 0xeb, 0xf5, 0x82, 0x15, 0x5a, 0xbe, 0x2f, 0x0c, 0x91, + 0x32, 0x45, 0xfd, 0x29, 0x72, 0xb4, 0xe8, 0x77, 0x63, 0x67, 0x41, 0xbf, 0x68, 0x24, 0x11, 0x2e, + 0xa7, 0x88, 0xfa, 0xa5, 0x4f, 0xa3, 0x58, 0x1c, 0xf9, 0x31, 0xc8, 0x5a, 0xb4, 0x5d, 0x18, 0x5a, + 0x0e, 0x3c, 0xe8, 0x55, 0x40, 0x51, 0x23, 0x36, 0xca, 0x8e, 0x48, 0x72, 0x25, 0x6e, 0xb0, 0x5c, + 0x82, 0x31, 0xa4, 0x0a, 0x5c, 0x38, 0x02, 0xae, 0x54, 0xaf, 0x7e, 0xcc, 0x08, 0x7d, 0x48, 0xac, + 0xdc, 0x04, 0x22, 0xf0, 0x7d, 0x34, 0x89, 0x25, 0xaf, 0x49, 0x8c, 0xfa, 0x7d, 0x12, 0x00, 0x2b, + 0x74, 0xe8, 0x08, 0x0c, 0xb5, 0x8a, 0x01, 0x89, 0xbc, 0x9c, 0x67, 0xc5, 0x17, 0x1c, 0x10, 0xb8, + 0xbd, 0x60, 0xb1, 0xb3, 0xb1, 0xd2, 0xa5, 0x12, 0x52, 0x91, 0xc0, 0xad, 0xd0, 0x6e, 0xab, 0x20, + 0x2e, 0xb9, 0x6f, 0x59, 0xce, 0x26, 0xc6, 0x78, 0x0d, 0xe8, 0x6c, 0x8f, 0x54, 0xab, 0xda, 0x45, + 0x98, 0xfc, 0x02, 0x32, 0x43, 0x71, 0x5f, 0xd8, 0x6e, 0xb3, 0x62, 0xe3, 0x80, 0xc0, 0x52, 0x7f, + 0xaa, 0x30, 0x05, 0x4a, 0xaf, 0x21, 0x1c, 0x35, 0x21, 0x08, 0x4d, 0x19, 0xfa, 0xa5, 0x26, 0xe2, + 0xb0, 0xd3, 0x19, 0x38, 0xaa, 0x19, 0x88, 0xe7, 0xf8, 0xf8, 0xde, 0xa6, 0xa7, 0x33, 0x90, 0xf5, + 0x96, 0x66, 0x85, 0x89, 0xa8, 0xf0, 0xbd, 0x4d, 0x4f, 0x26, 0x48, 0x92, 0x78, 0x60, 0xae, 0x40, + 0x76, 0x07, 0xe1, 0xab, 0x79, 0x05, 0x16, 0xb4, 0xf6, 0x95, 0x3d, 0xa7, 0x8a, 0xf3, 0x51, 0xbb, + 0x5d, 0x3d, 0x0c, 0xe6, 0xfb, 0x5c, 0x9b, 0x17, 0xd5, 0xda, 0x44, 0x8a, 0xf9, 0x1d, 0x45, 0xf9, + 0xb0, 0xbd, 0xac, 0x49, 0x9a, 0xc6, 0x3c, 0x30, 0xc5, 0xbb, 0x03, 0xa3, 0x45, 0x19, 0x0d, 0x89, + 0x68, 0xe8, 0x38, 0x15, 0x21, 0x7c, 0x9e, 0x65, 0xc1, 0xbd, 0x1b, 0xe5, 0xe2, 0x09, 0x0e, 0x73, + 0xb4, 0xaa, 0xc9, 0x30, 0xbc, 0x0c, 0xa2, 0xdb, 0x30, 0x3c, 0xd0, 0x3f, 0x6c, 0x0e, 0x31, 0x04, + 0xd5, 0x5e, 0x01, 0x1d, 0x26, 0xf6, 0x1e, 0x26, 0x3d, 0x38, 0x1c, 0x2d, 0x06, 0x89, 0x20, 0x51, + 0xd7, 0x22, 0x53, 0x30, 0x99, 0x1b, 0x35, 0xd0, 0x2c, 0x29, 0x6f, 0x26, 0x3c, 0xdb, 0xd3, 0x2a, + 0x14, 0xb0, 0x99, 0x12, 0x60, 0x62, 0x36, 0x83, 0xba, 0x43, 0x37, 0x95, 0xaa, 0xc4, 0x17, 0x5a, + 0x49, 0x2b, 0xd8, 0x81, 0x34, 0xe4, 0x60, 0x5b, 0x88, 0x15, 0x05, 0x3b, 0x08, 0x65, 0xcd, 0x8a, + 0xca, 0x74, 0xa9, 0xec, 0x99, 0x44, 0x28, 0x1f, 0xb0, 0xe5, 0x86, 0x90, 0xe1, 0x15, 0x55, 0x41, + 0x54, 0xaf, 0xc3, 0x4c, 0xaf, 0x83, 0xd5, 0xb1, 0x84, 0xa1, 0xd4, 0x1d, 0x0f, 0x0b, 0x37, 0xe7, + 0xc2, 0x3a, 0x02, 0x1d, 0xdf, 0x93, 0x90, 0x44, 0x5b, 0x68, 0x36, 0x7f, 0x67, 0x22, 0x58, 0xb2, + 0x23, 0x52, 0x4f, 0x99, 0x5b, 0xf0, 0xbc, 0x80, 0xcd, 0x3c, 0x8c, 0xda, 0xd6, 0xf2, 0xce, 0xf2, + 0xd4, 0x8a, 0x0e, 0x2d, 0xcb, 0x33, 0xfa, 0xfa, 0xd1, 0xae, 0xb4, 0x8b, 0x14, 0xb4, 0x4a, 0xea, + 0xb2, 0x50, 0xa0, 0x0e, 0xe3, 0xa7, 0x21, 0x40, 0x45, 0xf0, 0xa1, 0x15, 0xc6, 0x41, 0x9e, 0x7f, + 0x0f, 0xdf, 0x40, 0x8c, 0x4a, 0xbd, 0x03, 0xb0, 0xa7, 0x52, 0x46, 0xae, 0xa8, 0xe5, 0x25, 0x90, + 0x90, 0x96, 0xb6, 0x6a, 0x44, 0x71, 0x4d, 0x18, 0x58, 0x95, 0xfb, 0x58, 0x42, 0x72, 0x84, 0xc0, + 0xc0, 0x0c, 0x29, 0x87, 0xed, 0x2c, 0x57, 0xe4, 0x94, 0x6e, 0x98, 0xe7, 0x1f, 0xc8, 0xde, 0x89, + 0xa4, 0x24, 0xd8, 0xb7, 0xaa, 0x19, 0x68, 0xb5, 0xae, 0x68, 0x59, 0x50, 0x58, 0x52, 0xda, 0x67, + 0x82, 0xe6, 0x52, 0xb2, 0x45, 0x33, 0x07, 0xe8, 0x11, 0xa9, 0x08, 0xb2, 0xdb, 0xa6, 0xb9, 0x82, + 0xdb, 0x69, 0xa6, 0x91, 0xa8, 0x14, 0x8b, 0x22, 0x07, 0xa0, 0xa2, 0x51, 0x3c, 0x26, 0x2c, 0xe2, + 0x21, 0x6b, 0x42, 0x99, 0x21, 0xed, 0x6c, 0xa8, 0x7f, 0x2b, 0xd5, 0xeb, 0x02, 0xbe, 0x96, 0x13, + 0x1b, 0xf5, 0xc6, 0x43, 0x1b, 0x3e, 0xcd, 0x91, 0x40, 0x16, 0x59, 0xa5, 0xcb, 0x38, 0x08, 0xb9, + 0x7d, 0xfa, 0x3a, 0x58, 0x62, 0x7d, 0xf9, 0xa7, 0xa7, 0xd4, 0x13, 0xf8, 0xc8, 0xb7, 0xc1, 0x99, + 0x89, 0x5b, 0xa4, 0xdf, 0xa5, 0xb7, 0x3c, 0x7b, 0x0d, 0x1b, 0xc4, 0x76, 0x1c, 0x32, 0x93, 0x87, + 0xb0, 0x26, 0x1d, 0x37, 0x8f, 0xe1, 0xa2, 0xd9, 0x7d, 0x88, 0x8f, 0xa1, 0x9d, 0xad, 0xd7, 0x85, + 0x1b, 0x4c, 0xa7, 0x5f, 0xad, 0x20, 0x1b, 0xbf, 0x8b, 0xf2, 0x82, 0x43, 0xc5, 0x62, 0xed, 0x57, + 0x42, 0x9a, 0x83, 0xd6, 0xd6, 0x6b, 0xfa, 0x85, 0x4b, 0xe3, 0x38, 0x44, 0x0b, 0x44, 0x13, 0xca, + 0x77, 0xd8, 0xd7, 0x04, 0xde, 0x56, 0x8c, 0x1c, 0x8b, 0x24, 0x2c, 0xc6, 0x5d, 0x04, 0x73, 0xb1, + 0x4a, 0xea, 0x15, 0xd3, 0xbf, 0x51, 0xe9, 0x23, 0x48, 0xb5, 0x44, 0x98, 0xee, 0x43, 0x41, 0x63, + 0x26, 0xe5, 0x78, 0xfb, 0xe4, 0x18, 0x9a, 0x4f, 0x83, 0x64, 0xce, 0xb3, 0xb4, 0xcc, 0xe3, 0xfb, + 0xf7, 0xbc, 0x78, 0x93, 0xa0, 0xdb, 0x6f, 0x3f, 0xbc, 0xfd, 0x4e, 0x41, 0xb4, 0x77, 0xf1, 0xf2, + 0xeb, 0x1d, 0x04, 0xeb, 0xb5, 0x37, 0x94, 0x78, 0x19, 0xaa, 0x3e, 0xd6, 0x6b, 0x41, 0xbc, 0xd1, + 0x50, 0x4f, 0xe4, 0xf3, 0x02, 0xcb, 0x3c, 0x29, 0x0b, 0xfe, 0xfd, 0x7b, 0xdb, 0x5a, 0x14, 0xc5, + 0xd2, 0x3b, 0x3d, 0xbd, 0xbd, 0xbd, 0x75, 0x6f, 0xcf, 0xdc, 0x34, 0x9b, 0x9f, 0xf6, 0xae, 0xae, + 0xae, 0x4e, 0x05, 0x24, 0x8b, 0x6d, 0x23, 0xd5, 0x13, 0x3c, 0xf3, 0xd7, 0x01, 0x10, 0x8f, 0x3d, + 0x39, 0x2c, 0x72, 0x4a, 0x9b, 0xfd, 0x08, 0xe6, 0x34, 0xa4, 0xea, 0xca, 0xb4, 0xf1, 0xc9, 0x76, + 0x2f, 0x46, 0x52, 0x4c, 0x8c, 0x61, 0x05, 0x72, 0x5a, 0xa7, 0xa1, 0x7a, 0x92, 0x7d, 0x6c, 0xba, + 0x7b, 0xd7, 0xf6, 0x96, 0xe5, 0x47, 0x6c, 0x29, 0xb9, 0xb4, 0x61, 0x0c, 0xb1, 0x25, 0xbb, 0x67, + 0xb7, 0xec, 0x9a, 0xc8, 0x0a, 0x70, 0x89, 0x9c, 0x2b, 0xc3, 0x2c, 0x71, 0x0d, 0xef, 0xa4, 0x21, + 0x19, 0x61, 0x0e, 0xa0, 0x5f, 0xd2, 0x9b, 0x98, 0xc3, 0xa0, 0xc8, 0xee, 0xa5, 0x52, 0xde, 0xa3, + 0xe9, 0xae, 0x85, 0xc8, 0x5d, 0x92, 0x6d, 0x28, 0xc4, 0xda, 0xbd, 0x8f, 0xb6, 0xd7, 0xda, 0x07, + 0xf9, 0x40, 0x3a, 0xec, 0xf8, 0x38, 0x1b, 0x05, 0x2e, 0x94, 0xca, 0xad, 0x1f, 0x0c, 0xef, 0x87, + 0xf7, 0xb2, 0xa6, 0x54, 0xa8, 0x5e, 0xe0, 0x46, 0x70, 0xa6, 0x22, 0x37, 0x1c, 0x4e, 0xc8, 0x74, + 0xc2, 0x90, 0xa0, 0x74, 0x43, 0x38, 0xf0, 0x64, 0xe7, 0x78, 0xb6, 0x85, 0xba, 0x45, 0x4a, 0x7d, + 0x59, 0xa0, 0xb9, 0xeb, 0xe3, 0xe3, 0x6b, 0xb7, 0xca, 0x01, 0xba, 0xc9, 0x3c, 0x1e, 0x52, 0x1b, + 0xb4, 0xe4, 0xb7, 0x40, 0xca, 0x92, 0xdd, 0x3a, 0x9e, 0x5d, 0xe7, 0xbc, 0x11, 0x39, 0x6c, 0x66, + 0xce, 0xd4, 0xbf, 0x66, 0x33, 0xd5, 0xd6, 0xff, 0xc3, 0x61, 0xb0, 0xa5, 0xee, 0xdd, 0xbc, 0x9c, + 0xd8, 0x33, 0xaa, 0x27, 0x3d, 0xa9, 0x25, 0x52, 0x79, 0x11, 0x14, 0x1c, 0x5c, 0xa4, 0x52, 0xc4, + 0x49, 0x12, 0x8e, 0x70, 0xae, 0x6e, 0x91, 0xbe, 0xf5, 0x33, 0xb6, 0xc0, 0x38, 0xc9, 0xf0, 0xc7, + 0xeb, 0x8d, 0xa1, 0x0c, 0x67, 0xee, 0x47, 0x50, 0x36, 0x7e, 0x7d, 0xd5, 0x5c, 0xdb, 0x45, 0xd7, + 0xee, 0x9c, 0x17, 0x5f, 0xc2, 0xa8, 0x5f, 0xf1, 0xe9, 0x7b, 0x2a, 0xf8, 0x3a, 0x4b, 0x6f, 0x84, + 0x67, 0x7b, 0x7c, 0xfc, 0x56, 0x34, 0xd0, 0x2d, 0x86, 0xf4, 0x22, 0x0c, 0x6a, 0x24, 0x1c, 0x0f, + 0x3f, 0xec, 0x70, 0x63, 0x7b, 0x29, 0x6a, 0x39, 0x6c, 0xe1, 0xc8, 0x01, 0x3c, 0xd6, 0x8f, 0x1c, + 0x09, 0xcd, 0xe5, 0x66, 0x99, 0x26, 0x20, 0xae, 0x7f, 0x46, 0x71, 0xfc, 0x16, 0x1e, 0x47, 0x41, + 0xd6, 0xdf, 0x6e, 0xae, 0xad, 0x07, 0x6f, 0x14, 0x7e, 0x19, 0x4d, 0x55, 0x8b, 0x54, 0x2a, 0xe2, + 0x99, 0x53, 0x89, 0xdf, 0xe7, 0x8d, 0x00, 0xd6, 0xdd, 0x81, 0xa1, 0xfc, 0xc8, 0x43, 0x8e, 0x56, + 0xaa, 0xee, 0x23, 0x85, 0x72, 0x71, 0x8f, 0x0c, 0x38, 0xf9, 0x22, 0x2d, 0xe3, 0xe9, 0x6b, 0xdd, + 0xe0, 0x1f, 0xcb, 0x29, 0x3a, 0x56, 0x5a, 0xef, 0x40, 0xa9, 0x44, 0x1d, 0x00, 0x49, 0x47, 0x72, + 0x87, 0x04, 0x68, 0x2d, 0x99, 0x58, 0xe4, 0x1e, 0x43, 0xe9, 0x14, 0x3a, 0x09, 0x7a, 0x5a, 0x76, + 0x98, 0x0f, 0xf3, 0x23, 0xb2, 0x12, 0xd3, 0x61, 0xee, 0xe1, 0x57, 0xba, 0xfc, 0x09, 0xbc, 0x07, + 0xf2, 0x1d, 0xa4, 0x25, 0x9f, 0xd4, 0x0e, 0x28, 0x99, 0xf4, 0x78, 0x85, 0x9d, 0x2e, 0x2c, 0x4a, + 0x4a, 0x80, 0xce, 0xc1, 0x60, 0xca, 0x18, 0xdd, 0xec, 0xc5, 0x86, 0x9e, 0xc3, 0xde, 0xec, 0x6a, + 0xf0, 0x1b, 0x69, 0x00, 0xab, 0xd1, 0xb3, 0xa9, 0xa6, 0xa2, 0x2d, 0x92, 0xdd, 0x37, 0x37, 0xc1, + 0xec, 0x6f, 0x15, 0xb3, 0xeb, 0x99, 0x8a, 0x89, 0x52, 0x03, 0xbf, 0xc0, 0x44, 0x34, 0xbf, 0x68, + 0xec, 0xb0, 0x1d, 0xe8, 0x8e, 0x98, 0xf6, 0xe7, 0xca, 0x43, 0x82, 0xf3, 0x10, 0x28, 0xff, 0xe4, + 0x9d, 0x5e, 0xed, 0x80, 0x0c, 0x9e, 0x61, 0xa0, 0xb8, 0x5f, 0x6b, 0x7e, 0x2f, 0xa8, 0x89, 0x0b, + 0xf4, 0x22, 0x8c, 0xfa, 0xd7, 0x12, 0xa4, 0x70, 0x47, 0xdf, 0xda, 0x82, 0x05, 0x32, 0x1a, 0xd8, + 0x56, 0x39, 0xc4, 0x38, 0xe8, 0x5d, 0xfb, 0x10, 0xa2, 0xf8, 0x7d, 0x12, 0x2c, 0xb1, 0xc8, 0xca, + 0x9a, 0x97, 0x28, 0x02, 0x0b, 0xcf, 0x0f, 0x97, 0xda, 0x90, 0x93, 0x00, 0xf3, 0x7e, 0x4b, 0x9c, + 0x92, 0x30, 0x45, 0x8f, 0x32, 0x9a, 0xe0, 0xa6, 0x03, 0x42, 0xc1, 0x8d, 0x76, 0x95, 0x35, 0x73, + 0x57, 0x28, 0x74, 0x18, 0xe4, 0x5b, 0x10, 0xc7, 0xc4, 0x04, 0xd5, 0x80, 0x56, 0xf5, 0xc8, 0x4c, + 0xae, 0xd1, 0x83, 0xda, 0x97, 0x6b, 0x0b, 0xa1, 0xed, 0xb0, 0x89, 0x00, 0x7f, 0x8d, 0x96, 0x91, + 0xb4, 0x53, 0x84, 0x65, 0x21, 0xec, 0x43, 0xd3, 0xa9, 0x37, 0xc5, 0xbf, 0x14, 0xfd, 0x18, 0xb6, + 0xf4, 0x85, 0x41, 0x80, 0x72, 0xa1, 0x56, 0x5a, 0x32, 0x93, 0xe4, 0x87, 0xff, 0x92, 0xaf, 0xe6, + 0xc2, 0x26, 0x11, 0xab, 0xb3, 0x5e, 0x73, 0x6d, 0xbc, 0x6b, 0x16, 0x2a, 0x85, 0xad, 0x93, 0x83, + 0x74, 0xf3, 0x57, 0x95, 0xdb, 0x91, 0x37, 0x22, 0x27, 0xf0, 0x47, 0x46, 0x39, 0x39, 0xa4, 0x8a, + 0xd1, 0x15, 0xb0, 0xe1, 0x19, 0x92, 0xa1, 0x4b, 0x91, 0x2c, 0x12, 0xfb, 0x5e, 0xe8, 0xc6, 0x29, + 0x90, 0x42, 0x76, 0x44, 0x55, 0x07, 0x3c, 0x56, 0xf8, 0x21, 0xa3, 0xf6, 0xd2, 0x27, 0x94, 0x8e, + 0x6a, 0x6d, 0x43, 0x1b, 0x06, 0xbc, 0x6e, 0xa2, 0xf4, 0xd2, 0x34, 0x0d, 0x45, 0x38, 0xd2, 0x0d, + 0xd1, 0xa4, 0xe0, 0x64, 0xc8, 0x91, 0x4b, 0x68, 0xaf, 0x28, 0x8a, 0xc1, 0x87, 0x5b, 0xc5, 0x2a, + 0xd2, 0xb0, 0x5f, 0xaf, 0xf7, 0xbb, 0xdd, 0xee, 0x29, 0x21, 0x82, 0xa9, 0x2e, 0xbc, 0xfd, 0xad, + 0x6d, 0x55, 0xcc, 0x4a, 0xe9, 0x48, 0x1b, 0x1a, 0xb2, 0x9e, 0x35, 0xfc, 0x58, 0x7f, 0xa5, 0x70, + 0x21, 0x9c, 0x39, 0x78, 0x64, 0xa5, 0x11, 0x47, 0x1b, 0x2b, 0x4b, 0xb3, 0x70, 0xb1, 0xc2, 0x81, + 0xbf, 0x82, 0xdd, 0x70, 0x24, 0x9d, 0x27, 0xa3, 0x85, 0xff, 0x51, 0x1a, 0x77, 0x92, 0x8a, 0x54, + 0xb4, 0x8f, 0xa6, 0x07, 0xcb, 0x0c, 0xec, 0x67, 0x57, 0xeb, 0x09, 0xef, 0xdd, 0x71, 0x0f, 0xd8, + 0x53, 0xf0, 0xee, 0x57, 0x07, 0xcb, 0x62, 0xd2, 0x64, 0xeb, 0x75, 0x40, 0xcb, 0x36, 0x03, 0x77, + 0x92, 0x3c, 0x79, 0x47, 0x4c, 0xf9, 0x4e, 0x18, 0x8a, 0x91, 0xae, 0xe8, 0xcf, 0x44, 0x09, 0x6c, + 0x73, 0xb2, 0x48, 0x77, 0xa9, 0x4d, 0x39, 0x02, 0xc2, 0xe1, 0x2c, 0xa5, 0x45, 0x2c, 0x1e, 0xc9, + 0x7a, 0x4d, 0xee, 0x42, 0xa9, 0x62, 0x3b, 0xa3, 0x72, 0x0c, 0x9e, 0xad, 0x6b, 0x25, 0x0e, 0xdf, + 0xeb, 0xe6, 0x26, 0xa8, 0x88, 0xbe, 0x84, 0x2d, 0x40, 0x74, 0x59, 0x0a, 0xa7, 0x80, 0x87, 0xd7, + 0x7c, 0xaa, 0x5e, 0x09, 0x94, 0x30, 0xa2, 0xa9, 0xa2, 0xea, 0x82, 0xa0, 0xab, 0x2e, 0xe0, 0x2a, + 0xc1, 0xd4, 0x59, 0x80, 0x0d, 0x62, 0x29, 0x82, 0x92, 0x2d, 0x01, 0xc3, 0x30, 0xeb, 0x9a, 0xbb, + 0xc9, 0x94, 0xe5, 0xd1, 0x3c, 0x79, 0x27, 0xb7, 0x28, 0x8e, 0xf4, 0x32, 0x1e, 0x1f, 0x4b, 0x3e, + 0xba, 0x23, 0x30, 0x84, 0x2b, 0x35, 0x24, 0x0c, 0x1d, 0x8b, 0x5b, 0x59, 0x4c, 0x2b, 0x69, 0xb5, + 0x20, 0x47, 0x26, 0x8e, 0xc8, 0xd7, 0x53, 0x59, 0xb6, 0x4a, 0x29, 0x93, 0x58, 0xd5, 0x20, 0xc3, + 0x58, 0x25, 0x1d, 0x56, 0x4d, 0x6d, 0x07, 0xac, 0x2a, 0x20, 0xc0, 0x2a, 0x29, 0x40, 0x57, 0xd9, + 0x76, 0x95, 0xae, 0x2b, 0x93, 0xf8, 0x2b, 0x36, 0x76, 0x44, 0x1e, 0xa3, 0x21, 0x0a, 0x42, 0x47, + 0x9a, 0x6f, 0xd3, 0x68, 0x36, 0x43, 0x25, 0x29, 0xd6, 0x37, 0x61, 0x50, 0x84, 0x14, 0x90, 0x7c, + 0xb8, 0x71, 0x53, 0xe5, 0x42, 0x6e, 0x6a, 0xb7, 0xb3, 0xb6, 0x2b, 0xa7, 0x5b, 0xbb, 0x15, 0x03, + 0x8a, 0x93, 0xea, 0x10, 0x21, 0xec, 0xc0, 0x68, 0xd7, 0x04, 0xb0, 0x77, 0xa0, 0x47, 0xee, 0xd4, + 0xd9, 0xdc, 0xb8, 0x21, 0x85, 0xb6, 0x43, 0xea, 0xbd, 0x02, 0xff, 0x95, 0x76, 0x5f, 0x09, 0xd6, + 0xbe, 0x70, 0xf0, 0xb0, 0x90, 0x56, 0x73, 0x58, 0x66, 0x14, 0xf5, 0xf1, 0x93, 0x1d, 0xd8, 0x66, + 0x04, 0xf6, 0xfb, 0x9d, 0x4d, 0x0f, 0x12, 0x73, 0x37, 0x6e, 0x99, 0xdc, 0x48, 0xe3, 0xa4, 0x4a, + 0x02, 0x00, 0x83, 0x3a, 0xd1, 0x41, 0xae, 0xaf, 0xec, 0x4c, 0x39, 0xf1, 0x30, 0xe5, 0x41, 0x73, + 0x87, 0x02, 0xd3, 0xa0, 0x84, 0x48, 0xe9, 0x7a, 0x48, 0x4f, 0x38, 0xe7, 0x84, 0x73, 0xe1, 0xa6, + 0xc7, 0x7e, 0x15, 0x04, 0x40, 0x59, 0x26, 0x7c, 0x7e, 0x21, 0xbd, 0xb2, 0x2d, 0x3d, 0x2d, 0xfb, + 0x17, 0xc8, 0xdb, 0x5f, 0xb4, 0x8b, 0x3f, 0x60, 0x2c, 0x93, 0xba, 0x27, 0x83, 0x02, 0xd6, 0x41, + 0x3c, 0xea, 0x23, 0x11, 0xe2, 0xb9, 0x84, 0x78, 0x2e, 0x5f, 0x65, 0x5a, 0x3c, 0x97, 0x10, 0xcf, + 0x19, 0xd8, 0x81, 0xa2, 0x3d, 0xf4, 0x94, 0x51, 0x12, 0x39, 0x32, 0x4e, 0x31, 0x22, 0x6e, 0xac, + 0xc0, 0x1f, 0x1a, 0x65, 0xa6, 0xdb, 0x61, 0x58, 0xc7, 0x12, 0xc5, 0x55, 0xf5, 0xf9, 0x16, 0x3d, + 0xa0, 0xe7, 0xe8, 0x55, 0x65, 0xd1, 0x44, 0x14, 0xa4, 0x94, 0x11, 0x1b, 0xb9, 0x97, 0xe5, 0xf2, + 0x04, 0x82, 0x34, 0x0b, 0x26, 0x31, 0x8d, 0xbd, 0x7e, 0x21, 0x27, 0x89, 0xd1, 0xf4, 0x93, 0x59, + 0x34, 0x2f, 0x65, 0x39, 0x8c, 0xe6, 0x9a, 0xcb, 0xc8, 0x43, 0xcf, 0xdc, 0xdb, 0x2c, 0x2a, 0x54, + 0x99, 0xc3, 0x24, 0x8f, 0xba, 0x72, 0x83, 0xaf, 0x0a, 0x6f, 0x14, 0x00, 0x43, 0x91, 0x93, 0xcc, + 0xa4, 0x83, 0x89, 0xad, 0xe7, 0x63, 0x4f, 0x7c, 0xd5, 0x2e, 0xc8, 0x73, 0x30, 0x3a, 0x14, 0xad, + 0x11, 0x1d, 0xd2, 0xd3, 0x48, 0xea, 0x7d, 0x31, 0x16, 0xf9, 0x3d, 0x4c, 0x69, 0x67, 0xb7, 0x6b, + 0x7b, 0x6a, 0xba, 0x29, 0x97, 0xb1, 0x02, 0xd5, 0x49, 0xed, 0x8c, 0x2c, 0x82, 0xfc, 0xdd, 0x6d, + 0xa2, 0x87, 0x29, 0x05, 0x39, 0xc9, 0x4b, 0xe2, 0x5d, 0xda, 0xc6, 0xca, 0x68, 0xe7, 0x6a, 0x53, + 0x6d, 0xf6, 0x39, 0x14, 0xc5, 0x8c, 0xef, 0xe5, 0xfe, 0x64, 0xd5, 0x39, 0xb8, 0x86, 0x36, 0x21, + 0xdf, 0x18, 0x70, 0xe1, 0x3e, 0xbe, 0x97, 0x76, 0x46, 0x23, 0x9c, 0x2f, 0x17, 0x43, 0x2c, 0xde, + 0x47, 0x12, 0x15, 0x94, 0x10, 0xf6, 0xc8, 0xf1, 0xb1, 0xcc, 0x24, 0xb1, 0x2e, 0x12, 0xd2, 0x87, + 0xa8, 0x2b, 0x38, 0xce, 0x60, 0x1f, 0xb9, 0xc3, 0x48, 0xa6, 0xbd, 0x15, 0x3b, 0x62, 0xf5, 0x8e, + 0x92, 0xe3, 0xac, 0xd7, 0x6f, 0x29, 0xc7, 0xd8, 0xd0, 0x13, 0xa5, 0x42, 0x22, 0x51, 0x42, 0x6c, + 0x03, 0x24, 0x2a, 0xf7, 0x46, 0x85, 0xe4, 0x1c, 0xc6, 0x45, 0x29, 0x3c, 0xdd, 0xc6, 0x54, 0x80, + 0xc2, 0x50, 0x99, 0x5d, 0xbe, 0xb9, 0x2a, 0x35, 0xc8, 0x26, 0x20, 0x74, 0xab, 0x3b, 0xe9, 0x1e, + 0x00, 0xa9, 0xdc, 0xb8, 0x77, 0x2c, 0xa7, 0x6d, 0x88, 0xc4, 0xdf, 0x33, 0x33, 0x2c, 0xc9, 0x4d, + 0x94, 0xf3, 0xa1, 0x7a, 0x1a, 0x8d, 0x8b, 0x05, 0x4f, 0xdc, 0x09, 0x54, 0xb5, 0xad, 0xcb, 0x32, + 0x9e, 0xa7, 0xf1, 0x4a, 0x04, 0x08, 0x80, 0xf7, 0x0f, 0xd1, 0x0d, 0x4f, 0x4b, 0xf0, 0xc3, 0xde, + 0x7d, 0x9a, 0x9b, 0x1d, 0x6b, 0xac, 0xa6, 0xaf, 0x6c, 0x40, 0x8a, 0x28, 0x1a, 0x90, 0xd5, 0x04, + 0xbe, 0x85, 0x3f, 0x0a, 0x3a, 0x38, 0xca, 0xdc, 0x48, 0xca, 0x50, 0x29, 0x24, 0x2a, 0x96, 0xd3, + 0xb6, 0x57, 0x23, 0x73, 0x9f, 0xf7, 0xf4, 0x55, 0x96, 0x91, 0xa3, 0x5e, 0xd1, 0x84, 0xfd, 0xbc, + 0x16, 0x14, 0x4d, 0xdc, 0x72, 0xce, 0xb2, 0x86, 0x18, 0x7f, 0x4d, 0xe2, 0xc7, 0x21, 0x4b, 0x3e, + 0x4a, 0x4a, 0x3e, 0xd8, 0x53, 0x48, 0x1b, 0x5f, 0x8a, 0x76, 0xa9, 0xd7, 0x6b, 0x3f, 0xd3, 0x42, + 0x8b, 0xb6, 0x20, 0x21, 0xa5, 0x8b, 0x45, 0x96, 0xde, 0x82, 0xae, 0x59, 0xea, 0xdf, 0x89, 0x90, + 0x67, 0xe1, 0x5b, 0xf6, 0xd0, 0x1b, 0x75, 0x7e, 0xf9, 0xa5, 0x3d, 0x1e, 0xfe, 0xf2, 0xcb, 0xf4, + 0xe4, 0x97, 0x5f, 0x5c, 0x3c, 0xda, 0x9f, 0x0d, 0x9d, 0x75, 0xa3, 0x80, 0x72, 0x2c, 0x16, 0xfb, + 0xd6, 0xe8, 0x97, 0x5f, 0xf2, 0xf5, 0x2f, 0xbf, 0xd8, 0xe3, 0xb6, 0x6d, 0xb5, 0x8b, 0xb6, 0xe5, + 0x8c, 0x18, 0x5e, 0xf3, 0x43, 0xaf, 0x48, 0x03, 0xa6, 0x33, 0xb4, 0xd8, 0xf2, 0x4f, 0xb7, 0x3d, + 0x08, 0xea, 0x37, 0x11, 0x35, 0xf8, 0x91, 0xcf, 0xbf, 0xba, 0x5b, 0xda, 0x56, 0x36, 0x9f, 0x58, + 0x6d, 0x18, 0x04, 0xdf, 0x6e, 0xe7, 0x06, 0x56, 0x7b, 0xe9, 0xb0, 0x1f, 0x1a, 0xd9, 0x8b, 0x3c, + 0x16, 0x95, 0x3f, 0xdd, 0xce, 0x95, 0x95, 0x3f, 0xf8, 0xd6, 0xaf, 0x98, 0xf7, 0x27, 0xc3, 0x75, + 0xf7, 0x8e, 0x26, 0xfc, 0x4f, 0xe0, 0x67, 0xd4, 0xed, 0x5c, 0x05, 0x9d, 0xd9, 0xe7, 0x9d, 0xaf, + 0xc7, 0x0f, 0xbd, 0x0d, 0x32, 0x5f, 0x6f, 0x65, 0xf6, 0x29, 0xf3, 0x4b, 0x13, 0xe0, 0x87, 0xf6, + 0x3f, 0xc5, 0x7f, 0xd6, 0xa7, 0x96, 0xc3, 0xbe, 0xde, 0x57, 0xa2, 0xca, 0xbe, 0x6b, 0x96, 0xbd, + 0x16, 0xff, 0x89, 0x92, 0x2f, 0xf6, 0x95, 0xa8, 0xb2, 0xdf, 0xfd, 0xb7, 0x41, 0xb1, 0x80, 0xe5, + 0x3e, 0x67, 0xdf, 0xc8, 0x24, 0x6c, 0xc9, 0x64, 0xca, 0x7e, 0x96, 0x2f, 0xb3, 0x38, 0x4d, 0xb3, + 0xfa, 0x68, 0xc3, 0xdf, 0xb6, 0x74, 0x89, 0xa8, 0x73, 0x13, 0x25, 0xb6, 0x4c, 0x04, 0x77, 0x42, + 0x56, 0x35, 0xf4, 0xc9, 0xdf, 0x4d, 0xf1, 0xd5, 0xe9, 0xbd, 0x2a, 0x2a, 0x53, 0xd9, 0xfa, 0x0c, + 0xfd, 0x67, 0xfe, 0x32, 0xc8, 0x72, 0xfe, 0x75, 0x9c, 0x06, 0xa4, 0xb2, 0xab, 0x00, 0xf8, 0x30, + 0x39, 0xed, 0x75, 0xbb, 0x27, 0x99, 0x97, 0xd5, 0xa0, 0xfe, 0xdb, 0x08, 0x9e, 0x89, 0x56, 0x6f, + 0x48, 0xcd, 0xb3, 0xde, 0xa5, 0xd1, 0xdd, 0x3f, 0x1a, 0x9b, 0xe8, 0x45, 0xfa, 0x5e, 0x04, 0x86, + 0x6d, 0xd4, 0x71, 0x97, 0x01, 0xb1, 0x49, 0x56, 0xd8, 0x7d, 0x66, 0x75, 0x2d, 0x67, 0x43, 0x43, + 0xfa, 0xc9, 0x37, 0x8e, 0x8a, 0x54, 0x40, 0x62, 0x63, 0xa7, 0xfd, 0x53, 0xff, 0x61, 0xe1, 0x75, + 0x59, 0x8e, 0x7f, 0x2b, 0xfc, 0x0b, 0xbc, 0x1e, 0x64, 0xab, 0x12, 0x5b, 0x60, 0xcb, 0x4a, 0x66, + 0xa5, 0xc9, 0xeb, 0x05, 0x99, 0xe2, 0x70, 0xa2, 0xc5, 0x7b, 0x94, 0x44, 0x45, 0x14, 0xc4, 0x3f, + 0x09, 0x0b, 0x71, 0x52, 0x49, 0xe4, 0x4f, 0x65, 0xbf, 0x85, 0x1f, 0xd7, 0x72, 0xa9, 0x3a, 0x11, + 0x42, 0x00, 0xfd, 0xad, 0xcd, 0x8d, 0xdd, 0x0d, 0x0b, 0xe7, 0xd4, 0xa0, 0x2b, 0x93, 0x78, 0xce, + 0xd8, 0xcb, 0xcd, 0xa7, 0xa7, 0x32, 0xd4, 0x5f, 0x38, 0x43, 0xd1, 0xdf, 0x82, 0xdf, 0x49, 0x14, + 0xf8, 0x85, 0x77, 0xfa, 0x2b, 0x51, 0xf2, 0x70, 0xab, 0x06, 0xf2, 0x8c, 0x1a, 0x44, 0xbe, 0x75, + 0x0d, 0x2d, 0xf5, 0x91, 0xab, 0xeb, 0xd4, 0x82, 0x45, 0x1f, 0xfb, 0x39, 0xaa, 0x47, 0x26, 0x85, + 0x02, 0x51, 0x9b, 0x94, 0x43, 0xd6, 0x9b, 0x04, 0x0a, 0x1f, 0x46, 0x6f, 0x98, 0xc6, 0x69, 0xd6, + 0x92, 0xca, 0x1f, 0xfe, 0x1b, 0x54, 0x2a, 0x54, 0x13, 0xa4, 0x2b, 0x5a, 0xc5, 0x6a, 0xa0, 0xf9, + 0x8a, 0xb6, 0x24, 0xe9, 0xe1, 0x59, 0x19, 0x59, 0x08, 0xc0, 0xb2, 0x35, 0xd7, 0x89, 0x89, 0x25, + 0x43, 0xd0, 0x6a, 0xc4, 0x18, 0xab, 0xb5, 0xd0, 0x65, 0xb9, 0x4e, 0xac, 0x8c, 0x4a, 0x00, 0xb4, + 0xbf, 0x52, 0xdc, 0xa8, 0x14, 0x53, 0xa5, 0x6b, 0x1e, 0xaf, 0xa2, 0x44, 0x55, 0x90, 0x33, 0x96, + 0x59, 0x7e, 0xa1, 0x12, 0xb0, 0x3d, 0x98, 0x58, 0x1e, 0x5a, 0xe4, 0x84, 0xc7, 0x3b, 0x8a, 0x40, + 0x2a, 0x01, 0x6a, 0x3a, 0x2a, 0xc6, 0xf5, 0x7a, 0xe3, 0x85, 0x91, 0x42, 0x78, 0xd8, 0xc8, 0xa0, + 0x3c, 0xac, 0x18, 0x87, 0x20, 0x41, 0xef, 0x98, 0x4b, 0xad, 0x68, 0x4d, 0xe2, 0x60, 0x9b, 0x7a, + 0xa8, 0x7e, 0x18, 0x43, 0x3a, 0x9b, 0xf5, 0xb5, 0x29, 0x0f, 0x54, 0xc7, 0x52, 0x51, 0x52, 0xb5, + 0x32, 0x21, 0xed, 0xb6, 0x03, 0xb7, 0xa2, 0x4d, 0x75, 0x20, 0x85, 0xc5, 0xd4, 0xd5, 0x87, 0xf4, + 0x47, 0x20, 0xd2, 0x24, 0x37, 0x7d, 0x04, 0x69, 0x71, 0x7a, 0xd9, 0x65, 0xe4, 0x07, 0xe4, 0xc4, + 0x84, 0x8c, 0xec, 0xd0, 0x95, 0x48, 0x71, 0xff, 0x67, 0xd2, 0xf6, 0x70, 0x8f, 0x3b, 0x1c, 0x82, + 0x3f, 0x3b, 0xb1, 0x7b, 0x9d, 0x08, 0xfe, 0x93, 0x4c, 0x95, 0x27, 0x48, 0xe7, 0x32, 0x4d, 0xaf, + 0x0e, 0xbd, 0x87, 0x3e, 0xff, 0xec, 0x12, 0x2e, 0xed, 0x28, 0x87, 0xc3, 0x92, 0xb1, 0x18, 0x0e, + 0x4b, 0x3a, 0xa6, 0x8d, 0xc7, 0x99, 0x3f, 0x4a, 0x45, 0xe0, 0x45, 0x64, 0x53, 0x96, 0xe2, 0x83, + 0x87, 0xcc, 0xfb, 0x9b, 0xdd, 0xbf, 0xb8, 0x38, 0x19, 0xa9, 0xea, 0x54, 0x47, 0x34, 0xe9, 0x32, + 0x64, 0x3b, 0x6c, 0xae, 0xca, 0x03, 0x9d, 0x31, 0x51, 0x19, 0x33, 0x95, 0xb1, 0xa1, 0x19, 0x82, + 0x4a, 0x3e, 0xa4, 0xdf, 0x82, 0x0a, 0xf6, 0xcd, 0x30, 0x3b, 0x45, 0x3d, 0x31, 0xc5, 0xb9, 0x48, + 0xd1, 0x14, 0x27, 0x22, 0xc5, 0xfd, 0x4a, 0x96, 0xc9, 0xa3, 0x41, 0x98, 0x6d, 0x25, 0xe6, 0x54, + 0x4e, 0xea, 0xf3, 0x4e, 0x89, 0x59, 0x43, 0x2c, 0xf8, 0x9c, 0x0e, 0x10, 0xc0, 0x5f, 0xe5, 0xc3, + 0xae, 0x97, 0x9e, 0xf2, 0x41, 0x7e, 0x1b, 0x91, 0x96, 0x84, 0xdf, 0x1c, 0xc2, 0x9e, 0x6f, 0x95, + 0x1e, 0xaa, 0xc9, 0xa8, 0xc7, 0x40, 0x64, 0x24, 0xc8, 0xb0, 0xa3, 0x4e, 0xe6, 0x9c, 0xa6, 0x6d, + 0x3b, 0x7a, 0x95, 0x0d, 0x2f, 0xbd, 0xae, 0x63, 0x56, 0x88, 0xa8, 0x42, 0xd6, 0x49, 0xa8, 0x42, + 0xdf, 0x2c, 0xc8, 0xa8, 0x20, 0x01, 0xbe, 0x51, 0x70, 0xae, 0x94, 0x33, 0x64, 0xd3, 0x65, 0xf7, + 0x24, 0xfe, 0xec, 0xec, 0x92, 0x44, 0xd4, 0xdf, 0x6c, 0x92, 0x99, 0x21, 0xb0, 0x80, 0xa7, 0x03, + 0x79, 0x25, 0x33, 0x72, 0x95, 0xb1, 0xa9, 0x16, 0xfe, 0xdb, 0x3c, 0xde, 0x8b, 0x16, 0xb9, 0xde, + 0x51, 0xb5, 0xde, 0x99, 0x6f, 0xf7, 0x31, 0x92, 0x93, 0x08, 0x68, 0xc9, 0x5e, 0xf9, 0xbd, 0x61, + 0xe6, 0xf5, 0x3b, 0x19, 0x30, 0xc2, 0x5f, 0xf5, 0x78, 0xe7, 0x0a, 0x53, 0x4e, 0x4e, 0xa2, 0x53, + 0x2d, 0xc0, 0x30, 0x18, 0xd0, 0x4e, 0x35, 0x8e, 0x52, 0x8f, 0x23, 0x46, 0xc6, 0x05, 0x64, 0x79, + 0x73, 0x18, 0xf1, 0xa1, 0xd5, 0xe9, 0x9f, 0xd0, 0x29, 0x02, 0x41, 0x7e, 0x27, 0x76, 0x82, 0x5e, + 0xbb, 0xdd, 0x61, 0xe2, 0xf5, 0xbb, 0x5d, 0xc2, 0x89, 0x1c, 0x55, 0xd2, 0x8e, 0xf4, 0x00, 0xfa, + 0x18, 0x80, 0x8d, 0x77, 0x67, 0xff, 0x20, 0x32, 0x13, 0x19, 0xa2, 0xde, 0x69, 0xdf, 0x1c, 0x87, + 0x64, 0xef, 0x03, 0xac, 0x20, 0x9d, 0x6c, 0xbf, 0xa0, 0x5e, 0xb5, 0x90, 0xce, 0x30, 0xf7, 0xcb, + 0xcb, 0xa1, 0x8d, 0x71, 0x0a, 0x0a, 0xea, 0xf4, 0x2e, 0x2e, 0xdc, 0xfe, 0xc5, 0xf9, 0xcb, 0x8b, + 0x8b, 0xcb, 0xfe, 0x8b, 0xee, 0x55, 0xef, 0xc5, 0x55, 0xc7, 0x3d, 0x3f, 0xbf, 0xb8, 0xba, 0xbc, + 0xba, 0xe8, 0x9e, 0xe3, 0xe7, 0xc5, 0x55, 0xef, 0xec, 0xec, 0x04, 0x5e, 0x25, 0xef, 0xf4, 0x9d, + 0x76, 0xaf, 0x7b, 0xee, 0x9e, 0x5f, 0xf5, 0x7b, 0x97, 0xbd, 0xab, 0xab, 0xb3, 0xab, 0xb3, 0x97, + 0x2f, 0x5f, 0x9e, 0xfc, 0x6e, 0x83, 0x4d, 0xf8, 0xab, 0x7e, 0x17, 0xb3, 0x71, 0x5f, 0xf6, 0x5f, + 0x9c, 0x77, 0xaf, 0x2e, 0xbb, 0x97, 0xe7, 0xdd, 0xee, 0x8b, 0xb3, 0xab, 0x8b, 0x13, 0x08, 0x0f, + 0xde, 0xe9, 0x75, 0x9d, 0x0e, 0xfa, 0x70, 0x5f, 0x5c, 0x5e, 0x9d, 0x5d, 0xf4, 0x5e, 0x9e, 0xf7, + 0xfa, 0xdd, 0xab, 0x6e, 0xbf, 0xdd, 0xeb, 0x5d, 0xb8, 0x97, 0x2f, 0xae, 0xae, 0xce, 0xcf, 0xbb, + 0xbd, 0xee, 0xe5, 0x65, 0xef, 0xfc, 0x05, 0x80, 0x41, 0xc2, 0x78, 0x18, 0x1d, 0xea, 0xb9, 0x57, + 0xa8, 0xdf, 0xbd, 0xb8, 0xbc, 0x7c, 0x89, 0x9f, 0xab, 0xb3, 0xb6, 0xdb, 0xeb, 0x9d, 0xf7, 0x01, + 0xf9, 0xe2, 0xec, 0x05, 0x40, 0x5c, 0x02, 0x76, 0x02, 0xd8, 0xe0, 0x97, 0xce, 0x79, 0x17, 0x53, + 0x38, 0xbb, 0xbc, 0x3c, 0xeb, 0x5e, 0x9d, 0x9d, 0xf5, 0x7b, 0x7d, 0x82, 0x43, 0x7a, 0xdc, 0x3f, + 0xeb, 0x5f, 0x60, 0x32, 0x57, 0xe8, 0xf0, 0xe2, 0x45, 0xaf, 0x77, 0xf5, 0xe2, 0xbc, 0xed, 0x76, + 0x5f, 0x5c, 0x9d, 0x9f, 0x9d, 0x5f, 0x5c, 0x52, 0xfd, 0xcb, 0xfe, 0xd9, 0x79, 0x5f, 0xce, 0xed, + 0x82, 0x86, 0xf8, 0xd2, 0xed, 0xbe, 0xbc, 0xe8, 0x5f, 0x5d, 0x9e, 0x5d, 0xa0, 0xd6, 0xc5, 0x0b, + 0x39, 0x35, 0xc1, 0xa2, 0x82, 0xa9, 0x85, 0xf0, 0x30, 0x98, 0xf8, 0x67, 0x2a, 0x36, 0x78, 0xf8, + 0x67, 0x0c, 0x7e, 0x87, 0x85, 0xff, 0x2e, 0x25, 0xf1, 0x5e, 0xaf, 0x4e, 0x50, 0x49, 0x26, 0xb9, + 0x17, 0x0b, 0xd5, 0xe7, 0x67, 0xa0, 0xcd, 0x73, 0x7e, 0x3e, 0x70, 0xcf, 0x5f, 0x95, 0x1d, 0x3e, + 0x90, 0xeb, 0x98, 0xfa, 0x8d, 0x75, 0xc6, 0xac, 0x5d, 0xcc, 0xbd, 0x6c, 0x93, 0x67, 0x94, 0x82, + 0xed, 0x53, 0x37, 0xfb, 0x2f, 0x3f, 0x3b, 0x8d, 0x86, 0x90, 0x6b, 0x1e, 0x14, 0x78, 0x15, 0x27, + 0xd9, 0xb0, 0x6d, 0x31, 0x0f, 0xcf, 0x07, 0x0e, 0x72, 0xad, 0xba, 0x85, 0xb5, 0x23, 0xf2, 0xc8, + 0xe8, 0xb1, 0x63, 0x36, 0x7a, 0xa0, 0x33, 0x83, 0xb0, 0x02, 0x57, 0x16, 0x83, 0x79, 0xee, 0x19, + 0xa2, 0x58, 0xea, 0x7d, 0x69, 0x05, 0x6c, 0x13, 0x2b, 0x68, 0x1d, 0x14, 0x0a, 0xb6, 0xc3, 0xbc, + 0x73, 0xb3, 0x59, 0xcd, 0x9c, 0xb2, 0x1d, 0x9d, 0x43, 0x92, 0xea, 0x25, 0x61, 0xdb, 0xa6, 0x87, + 0xb6, 0xb2, 0x00, 0x15, 0x4e, 0xda, 0x8a, 0x7e, 0x72, 0xfa, 0x09, 0xf0, 0xb3, 0xa9, 0xfc, 0xd9, + 0x4c, 0x86, 0xe7, 0xa2, 0x51, 0x36, 0xf6, 0xe9, 0x88, 0xc5, 0x11, 0xdc, 0xdd, 0x6c, 0x3c, 0x50, + 0xc6, 0x4e, 0xc1, 0xec, 0xc8, 0x5d, 0xac, 0xd7, 0x91, 0x9b, 0xd3, 0xcf, 0x8a, 0x7e, 0x28, 0x86, + 0xd8, 0xe8, 0x48, 0x3a, 0xb1, 0x91, 0x8a, 0x52, 0xeb, 0x96, 0x18, 0x7a, 0x35, 0xf9, 0x60, 0x67, + 0xf6, 0x0a, 0xa7, 0x0d, 0x4b, 0x68, 0x67, 0xaa, 0xb5, 0xe6, 0xae, 0x81, 0xc1, 0x62, 0x38, 0x00, + 0x4b, 0x82, 0x71, 0x17, 0x8f, 0xc0, 0x21, 0x04, 0x6f, 0x2a, 0x58, 0x79, 0x80, 0x76, 0x81, 0xf0, + 0x16, 0x1f, 0x07, 0x99, 0x3f, 0x06, 0x32, 0x37, 0x41, 0x4a, 0x93, 0xe6, 0x71, 0x68, 0xab, 0xc7, + 0xa0, 0xad, 0x4c, 0x68, 0x41, 0xbc, 0x5c, 0x04, 0x4f, 0x40, 0x0b, 0x1e, 0x81, 0x56, 0x63, 0x17, + 0x6f, 0xec, 0x21, 0x00, 0x6c, 0xa7, 0x02, 0xae, 0xac, 0x9c, 0x03, 0xd0, 0x1b, 0xdc, 0x66, 0x6b, + 0x3b, 0xeb, 0xd0, 0x1a, 0x91, 0x09, 0xd6, 0x64, 0xaa, 0xa2, 0xee, 0x28, 0xe3, 0xd3, 0x47, 0xe7, + 0x80, 0xc6, 0x6e, 0xf6, 0x08, 0xe0, 0x7a, 0x16, 0x78, 0x23, 0xc9, 0x61, 0xce, 0x62, 0x9e, 0x71, + 0x9e, 0x3c, 0x09, 0x7e, 0xfe, 0x7c, 0xf0, 0xf3, 0x06, 0xf8, 0xc9, 0x53, 0xcb, 0x49, 0xd0, 0x27, + 0xcf, 0x87, 0x3e, 0x69, 0x40, 0x27, 0xff, 0x72, 0xbf, 0x58, 0xa8, 0x2d, 0x32, 0x5b, 0x71, 0x07, + 0x13, 0x76, 0x8a, 0xb4, 0x51, 0xa4, 0x84, 0xab, 0xed, 0xa3, 0x6f, 0x48, 0x90, 0xce, 0xf1, 0x88, + 0x48, 0x70, 0x7e, 0x63, 0x53, 0x6c, 0xec, 0x09, 0xaa, 0xa8, 0x0d, 0x22, 0x72, 0x59, 0x40, 0x1a, + 0xd5, 0xd9, 0xeb, 0xc2, 0x0d, 0x86, 0x3d, 0xc8, 0x9e, 0xa0, 0x39, 0xd0, 0x67, 0xb0, 0xb0, 0x98, + 0x21, 0x88, 0x8c, 0xd2, 0x82, 0x74, 0x37, 0x8f, 0xd1, 0x8b, 0x29, 0x20, 0xe2, 0xc7, 0xd1, 0x00, + 0xfb, 0xa4, 0x81, 0x86, 0x85, 0x34, 0x07, 0x04, 0x1a, 0xe2, 0x5a, 0x76, 0x0a, 0x34, 0xe4, 0x12, + 0x0d, 0xf1, 0x73, 0xd1, 0xa0, 0x2d, 0x8f, 0xe7, 0xa0, 0x41, 0x38, 0xf3, 0x4f, 0xa2, 0x01, 0xb5, + 0x9e, 0x89, 0x06, 0xe1, 0x6f, 0x98, 0x48, 0x96, 0x5e, 0xd5, 0x63, 0xaa, 0x02, 0x95, 0xf4, 0x61, + 0x6a, 0x24, 0x29, 0x7e, 0xe1, 0x66, 0x6d, 0x8b, 0xb5, 0x28, 0x31, 0xd7, 0x89, 0x49, 0xdb, 0x72, + 0xac, 0x03, 0x9a, 0x43, 0xef, 0x14, 0x50, 0xd4, 0x73, 0x06, 0x13, 0xc8, 0xff, 0xcd, 0xe5, 0x77, + 0x3c, 0xa4, 0x18, 0xd1, 0x10, 0x9a, 0xfb, 0xef, 0x36, 0x1d, 0x57, 0x92, 0x3a, 0x38, 0x13, 0x6f, + 0x7d, 0xf5, 0xc6, 0xc5, 0xdb, 0x99, 0x7c, 0x13, 0x36, 0xc5, 0xb7, 0x55, 0x4b, 0x71, 0x14, 0xf0, + 0x99, 0x4d, 0xd1, 0x35, 0xbd, 0x9d, 0x8f, 0x59, 0xcf, 0x71, 0xd8, 0x51, 0x72, 0xd8, 0x4b, 0xc4, + 0x04, 0x5b, 0xca, 0xdd, 0x75, 0x06, 0x15, 0xdd, 0x80, 0xde, 0x23, 0x10, 0x7b, 0x06, 0x4a, 0xe7, + 0xd0, 0x65, 0xe5, 0xa6, 0x41, 0xa5, 0xcf, 0xc2, 0x60, 0x60, 0xa0, 0x30, 0x38, 0x8c, 0x43, 0x99, + 0x08, 0xf6, 0x23, 0x73, 0xdb, 0x59, 0xae, 0x89, 0x44, 0xbb, 0xd8, 0xcf, 0x5c, 0xc6, 0x4f, 0xac, + 0xf6, 0x3f, 0x60, 0x43, 0x64, 0x8e, 0x78, 0xcc, 0xe5, 0x63, 0x9f, 0xa4, 0x6d, 0x2e, 0x1f, 0x50, + 0xa9, 0x16, 0xf0, 0xcb, 0xc6, 0x02, 0xf6, 0x5e, 0x9c, 0xfc, 0xb7, 0x58, 0x08, 0x5a, 0x04, 0xf5, + 0xd2, 0x1f, 0xd3, 0x1a, 0xa8, 0x97, 0xb3, 0xb1, 0x5c, 0xbd, 0xaf, 0xff, 0x74, 0x33, 0x22, 0x1a, + 0xf9, 0x72, 0xae, 0x60, 0x7c, 0xd7, 0x80, 0x51, 0x03, 0xa8, 0x5b, 0x9b, 0x3d, 0x7e, 0xd1, 0xa4, + 0x97, 0x47, 0xaa, 0xa3, 0x27, 0xdd, 0xcd, 0xa3, 0x14, 0x02, 0x5c, 0x3f, 0x83, 0x42, 0xc8, 0x91, + 0xdb, 0x98, 0xeb, 0xf3, 0xf2, 0xcf, 0x51, 0xc9, 0x81, 0x15, 0xc2, 0xe3, 0x67, 0xe1, 0x67, 0x82, + 0x46, 0x9c, 0x83, 0x2c, 0x5e, 0x07, 0x5c, 0x4c, 0x31, 0xf2, 0xf4, 0x00, 0x50, 0x49, 0xf7, 0x8f, + 0xa4, 0x20, 0xd2, 0x85, 0x26, 0xc9, 0xbc, 0x6d, 0x7d, 0x26, 0x53, 0x31, 0x52, 0xcf, 0x67, 0xf5, + 0x1f, 0xf6, 0xb1, 0x3a, 0x7c, 0x45, 0x83, 0x5f, 0x85, 0x73, 0x54, 0xf1, 0x2b, 0xbd, 0x89, 0xa5, + 0xfb, 0x74, 0x2f, 0xab, 0x3f, 0xd5, 0xf4, 0xf9, 0xac, 0x8e, 0x29, 0x6e, 0x2d, 0x24, 0xc9, 0x46, + 0xc8, 0xf4, 0x08, 0x02, 0x1d, 0x7e, 0xff, 0x36, 0xab, 0x93, 0x24, 0x7e, 0x16, 0x0e, 0x03, 0x03, + 0x89, 0xc1, 0x63, 0x58, 0x7c, 0x9a, 0xd9, 0x8d, 0xa8, 0xd7, 0x66, 0x03, 0x1a, 0x8d, 0x37, 0xb6, + 0x53, 0xc7, 0x42, 0xff, 0xc7, 0x40, 0x3b, 0x94, 0xc7, 0x6d, 0x34, 0x2d, 0x16, 0x42, 0x41, 0xe5, + 0x98, 0x1f, 0xcf, 0xde, 0x47, 0x7f, 0x70, 0xf2, 0x1d, 0xdd, 0x49, 0x9a, 0xe1, 0xf5, 0x9f, 0xa2, + 0x98, 0x0e, 0xb6, 0xc2, 0x82, 0x9e, 0xc6, 0xfc, 0xc7, 0x60, 0x1a, 0x95, 0x39, 0x4b, 0xc5, 0x85, + 0xaf, 0xe9, 0x94, 0xf6, 0x17, 0xe2, 0xba, 0xed, 0x22, 0x80, 0x6b, 0x91, 0xfb, 0xd6, 0x22, 0xcd, + 0xa2, 0x3f, 0xd2, 0xa4, 0x08, 0x62, 0x4b, 0x68, 0xa8, 0x38, 0xb8, 0x4f, 0xcb, 0xe2, 0xcb, 0x28, + 0xe3, 0x62, 0x0c, 0xb5, 0x4f, 0xaa, 0x76, 0x25, 0x13, 0x3f, 0x73, 0xc8, 0x57, 0x3e, 0x49, 0xdb, + 0x7d, 0xf8, 0xdf, 0x56, 0x18, 0x65, 0xa1, 0x3c, 0x57, 0x1b, 0x0f, 0x1f, 0x64, 0xc7, 0x22, 0x04, + 0xea, 0x55, 0xbd, 0xb6, 0xb7, 0x06, 0xa4, 0x5f, 0x60, 0xe5, 0x7b, 0x51, 0x07, 0x80, 0x3a, 0x04, + 0x48, 0x4c, 0x0e, 0x4b, 0xb3, 0xe0, 0xd1, 0x7c, 0x51, 0x20, 0x11, 0xde, 0x79, 0x11, 0x1c, 0xe9, + 0xf0, 0x5e, 0x3c, 0x32, 0xd1, 0x96, 0x92, 0x1d, 0x7e, 0xda, 0xdf, 0x78, 0x8d, 0xae, 0x32, 0x54, + 0x68, 0x42, 0xcd, 0x74, 0x03, 0x2a, 0xba, 0xf3, 0xba, 0xec, 0x1e, 0xff, 0x64, 0x17, 0xf9, 0x30, + 0xab, 0xbb, 0xc9, 0x87, 0x91, 0x97, 0x19, 0x9b, 0x7e, 0xff, 0x32, 0x43, 0xc9, 0x84, 0x7e, 0xa0, + 0x3b, 0x52, 0x88, 0xa7, 0x4b, 0x19, 0xb2, 0x19, 0x90, 0x1c, 0xb9, 0x46, 0x87, 0xc0, 0xb1, 0x7e, + 0x17, 0x03, 0xa2, 0x9d, 0x87, 0x27, 0xf0, 0x0a, 0xd4, 0xc7, 0x43, 0x0c, 0xce, 0xe3, 0x34, 0x47, + 0x3f, 0x6d, 0xef, 0xdb, 0x8a, 0x4b, 0x44, 0xb8, 0x8e, 0x82, 0x11, 0x42, 0xb8, 0xab, 0xb8, 0x8f, + 0x5e, 0x42, 0x71, 0x10, 0x52, 0x84, 0x80, 0x84, 0x39, 0xec, 0xe9, 0x95, 0xa2, 0xc8, 0x93, 0x0b, + 0x49, 0x2e, 0x4a, 0xa4, 0x2d, 0x5b, 0x97, 0xcd, 0x8d, 0x32, 0x61, 0x88, 0xd6, 0x45, 0x13, 0xa3, + 0x48, 0x7a, 0x09, 0xba, 0x8c, 0xa2, 0x1c, 0xf0, 0xd1, 0x64, 0x91, 0xb2, 0xf1, 0x3d, 0x79, 0x9d, + 0xad, 0xa0, 0xa0, 0xd5, 0x07, 0x7e, 0xb3, 0xe4, 0x19, 0x79, 0x3e, 0x5c, 0xd0, 0xdf, 0x4d, 0x70, + 0x67, 0x64, 0x89, 0x38, 0x9e, 0x9d, 0x28, 0x53, 0xbe, 0xc3, 0x9d, 0xd3, 0xf2, 0xc4, 0x88, 0x76, + 0x54, 0xb1, 0xb0, 0x2e, 0xab, 0x82, 0x60, 0xa9, 0x94, 0x12, 0xb2, 0xc3, 0x85, 0x31, 0x4a, 0xa0, + 0xf9, 0xd4, 0x3f, 0x73, 0x2f, 0x65, 0x89, 0xe1, 0x6c, 0xd5, 0x15, 0x72, 0x59, 0x26, 0xbd, 0x26, + 0x4f, 0xdd, 0xf3, 0xab, 0x8b, 0xe1, 0xfe, 0x0a, 0x14, 0x8b, 0xd8, 0x7f, 0xa9, 0x07, 0x11, 0x43, + 0x04, 0x85, 0x7e, 0xa7, 0x77, 0x12, 0xb6, 0x4b, 0xd0, 0x75, 0x0a, 0xbb, 0xed, 0xce, 0x8b, 0x87, + 0xb9, 0x17, 0x82, 0x72, 0xe2, 0x61, 0xe8, 0xe5, 0x1b, 0x19, 0x54, 0x47, 0xd3, 0xc2, 0xef, 0x9f, + 0x88, 0x91, 0xfe, 0xf0, 0x86, 0x45, 0x85, 0xbf, 0xef, 0x4a, 0x94, 0x5d, 0x7c, 0x96, 0xb4, 0x13, + 0xe7, 0x33, 0xb8, 0xf7, 0xd9, 0xfe, 0x1a, 0x72, 0xda, 0xf9, 0xef, 0x59, 0x61, 0x17, 0x27, 0x45, + 0x3b, 0x39, 0x49, 0x9c, 0x4d, 0xcd, 0xf8, 0x22, 0xe8, 0x5f, 0xef, 0x31, 0x08, 0xfa, 0x03, 0xe5, + 0x57, 0x2c, 0xd5, 0x69, 0xb2, 0x54, 0xa7, 0x21, 0x02, 0x8c, 0x0b, 0x5d, 0x85, 0x19, 0x79, 0x53, + 0x50, 0xb4, 0x6d, 0x2b, 0xb9, 0xa1, 0x92, 0x2a, 0x92, 0x5d, 0x92, 0x26, 0x28, 0x62, 0xc2, 0x84, + 0x58, 0x30, 0x31, 0x78, 0x24, 0x2d, 0x1a, 0x11, 0x69, 0x82, 0xbc, 0xe0, 0x3c, 0xfe, 0x3c, 0x99, + 0xc7, 0x52, 0x1c, 0x89, 0xd7, 0x1d, 0x09, 0x42, 0xa7, 0xb6, 0xc3, 0x38, 0x0d, 0xaf, 0x6f, 0xa3, + 0x5c, 0xc8, 0x0b, 0x3e, 0x84, 0x2c, 0x69, 0x8b, 0xd3, 0xfc, 0x5b, 0xb9, 0xd0, 0x01, 0x1d, 0x2a, + 0xa1, 0x26, 0x41, 0x52, 0x44, 0x7b, 0x9a, 0xf5, 0x5e, 0x76, 0x3b, 0x68, 0xba, 0x5b, 0x4a, 0x87, + 0xa0, 0x7c, 0x8a, 0x6d, 0x62, 0x6d, 0xec, 0x44, 0xa8, 0x93, 0x7a, 0xe8, 0xf1, 0xd6, 0xd0, 0x05, + 0x82, 0x28, 0xf0, 0xe8, 0x86, 0x77, 0xa0, 0x5c, 0x3c, 0xee, 0x29, 0xf2, 0x2a, 0xb6, 0x89, 0x28, + 0x70, 0x45, 0x72, 0xb7, 0xec, 0x44, 0x62, 0xf3, 0x31, 0xf6, 0xc5, 0xc4, 0xc5, 0xba, 0x05, 0x45, + 0x90, 0xf4, 0xed, 0x4e, 0xc4, 0x28, 0x72, 0x69, 0xa3, 0x8f, 0xd3, 0x04, 0x6a, 0x0c, 0x3c, 0x5d, + 0x51, 0x70, 0x56, 0x88, 0x73, 0xd9, 0x2c, 0x35, 0x82, 0x86, 0xf5, 0xde, 0x97, 0x1d, 0x93, 0x53, + 0x61, 0xbc, 0x83, 0x18, 0x4f, 0xd3, 0x93, 0xdc, 0x3c, 0x7f, 0x90, 0xef, 0x2e, 0x9e, 0xd0, 0x02, + 0x93, 0xf4, 0xee, 0x5b, 0x21, 0x85, 0x9a, 0x0b, 0x99, 0x68, 0x91, 0xa6, 0x6e, 0x1e, 0x42, 0xb0, + 0x25, 0x7a, 0x55, 0x0f, 0x89, 0x62, 0xa3, 0xb7, 0x70, 0x0b, 0x33, 0x79, 0x85, 0x99, 0x5b, 0xa5, + 0x56, 0x32, 0x2d, 0xfc, 0x52, 0x24, 0x25, 0x60, 0x26, 0xa2, 0xc5, 0xa9, 0x73, 0x6a, 0x73, 0x92, + 0xe3, 0x0e, 0xb1, 0x35, 0x90, 0x60, 0x47, 0x22, 0xaf, 0xac, 0xf2, 0xf4, 0x48, 0xd5, 0x94, 0xb7, + 0x98, 0x3d, 0x96, 0xcc, 0xce, 0x56, 0x7b, 0x4b, 0x51, 0xd6, 0xc9, 0x65, 0x0d, 0x63, 0xb8, 0x41, + 0x51, 0xdf, 0xab, 0xad, 0x4e, 0x51, 0xf8, 0xdd, 0x01, 0xaf, 0x4f, 0xb0, 0x70, 0x7d, 0xd8, 0xa2, + 0x14, 0xe7, 0x24, 0xdc, 0x3b, 0xac, 0x66, 0x2a, 0x93, 0xf7, 0x1d, 0x71, 0xfb, 0xb3, 0x66, 0xc1, + 0xf2, 0xa4, 0x6c, 0xa7, 0x18, 0xec, 0xab, 0x26, 0x7e, 0xf4, 0x39, 0x41, 0x6e, 0x1e, 0xd3, 0x33, + 0xee, 0xcd, 0x18, 0x5c, 0xfa, 0x80, 0x65, 0x81, 0x3e, 0x06, 0x8e, 0xe1, 0xe0, 0x0b, 0x06, 0xea, + 0x20, 0xc7, 0x62, 0x32, 0xed, 0x35, 0xb8, 0x8a, 0xee, 0xc7, 0xb4, 0xf2, 0x94, 0x6c, 0x14, 0xe1, + 0x15, 0x88, 0x92, 0xd7, 0xb4, 0x7d, 0x65, 0xcc, 0x70, 0x51, 0x6c, 0x1f, 0xaa, 0x69, 0x5b, 0x9d, + 0x39, 0xe1, 0x9d, 0x4e, 0x11, 0x5a, 0xed, 0x44, 0x9a, 0x1c, 0x11, 0xf0, 0xb5, 0xb4, 0xf7, 0xc4, + 0xf2, 0x46, 0xdd, 0xfa, 0x1a, 0x38, 0xec, 0xaa, 0xb6, 0xd5, 0x12, 0x6d, 0x3e, 0xb3, 0x36, 0x8e, + 0xfb, 0x31, 0x05, 0x62, 0x2d, 0x66, 0x39, 0xc2, 0x32, 0xa9, 0x6f, 0x0e, 0x18, 0xf3, 0xd9, 0xdd, + 0x13, 0x1c, 0x42, 0xa5, 0x8b, 0x9b, 0x3d, 0x42, 0x10, 0x4e, 0x0b, 0x7f, 0x64, 0xdd, 0xa4, 0x65, + 0x2e, 0xae, 0x27, 0x00, 0x56, 0x91, 0x96, 0xe1, 0x42, 0xa5, 0x45, 0x7e, 0xb9, 0xd4, 0xb9, 0x3c, + 0x99, 0x5a, 0x63, 0x36, 0x37, 0x04, 0x61, 0x62, 0xec, 0x84, 0x8a, 0x4e, 0x13, 0x75, 0x4a, 0x91, + 0x82, 0x7e, 0xd5, 0x11, 0x8d, 0x68, 0xea, 0xcb, 0x35, 0xca, 0xb0, 0x26, 0xe9, 0x8d, 0x4d, 0xf7, + 0x87, 0xeb, 0xfd, 0xd6, 0xb3, 0x4b, 0x87, 0x8e, 0xd8, 0xcb, 0x71, 0xda, 0x17, 0xf5, 0xd1, 0x33, + 0x71, 0xb2, 0xed, 0xb7, 0xdf, 0x44, 0x00, 0xf5, 0xb7, 0xdf, 0x7c, 0x48, 0x02, 0xdb, 0x8c, 0xa7, + 0xea, 0xb3, 0x3c, 0xf2, 0x58, 0xa6, 0x2d, 0x2e, 0xc0, 0x57, 0x85, 0x8e, 0xd3, 0x38, 0xd5, 0x5f, + 0x38, 0xbb, 0xe7, 0x42, 0x0e, 0xc4, 0x4d, 0x25, 0xdd, 0x88, 0x7b, 0x30, 0xf2, 0xfc, 0x87, 0xd8, + 0x31, 0x03, 0xc7, 0x3e, 0xa4, 0xc9, 0x5b, 0xc2, 0xc7, 0x97, 0xe9, 0x6d, 0x02, 0x96, 0x84, 0x49, + 0x40, 0x38, 0xc9, 0x85, 0xe1, 0x42, 0xfa, 0xe1, 0x69, 0x53, 0x41, 0x1f, 0xff, 0x13, 0x9a, 0x35, + 0x9b, 0x47, 0xc9, 0x50, 0x9b, 0x00, 0x6f, 0xc5, 0xab, 0xa7, 0xf3, 0xc1, 0xa5, 0x0f, 0x58, 0x81, + 0x6c, 0x16, 0xa7, 0xb7, 0x9e, 0xb5, 0x8a, 0xf2, 0x68, 0x02, 0xd3, 0x8c, 0x4d, 0xa3, 0x7c, 0x09, + 0x98, 0x5e, 0x36, 0xb4, 0xa2, 0x24, 0x8e, 0x12, 0xde, 0x99, 0x90, 0xc8, 0xb4, 0x28, 0x14, 0x45, + 0xcf, 0xea, 0xf6, 0x78, 0x57, 0x6f, 0x9d, 0x8b, 0x13, 0xa7, 0xa8, 0x2e, 0xc1, 0x7e, 0xc7, 0x67, + 0x05, 0x2a, 0xcb, 0x97, 0x0f, 0xe9, 0xd2, 0x1a, 0xfb, 0xdc, 0x61, 0x0b, 0xfb, 0x9d, 0x3c, 0xee, + 0x56, 0x5f, 0x24, 0xb7, 0xf5, 0xba, 0x81, 0x31, 0x4b, 0xb9, 0x0b, 0x69, 0x1e, 0x66, 0xaa, 0x10, + 0x74, 0x00, 0x87, 0x4c, 0x1d, 0x3e, 0x92, 0x87, 0x2d, 0xd3, 0xe4, 0x4d, 0xb2, 0x2c, 0x0b, 0xb2, + 0x77, 0x29, 0x93, 0x8e, 0xae, 0xd1, 0x69, 0x94, 0x2f, 0x48, 0x5e, 0x62, 0xc5, 0x5f, 0xc7, 0xc4, + 0x08, 0x3f, 0x02, 0x45, 0xb0, 0x98, 0xa9, 0x1f, 0x71, 0xa5, 0xe5, 0x4b, 0xa9, 0xee, 0x91, 0xa5, + 0xcd, 0x13, 0x49, 0x81, 0xf9, 0x90, 0x46, 0x49, 0xb6, 0xda, 0xf4, 0x83, 0xcc, 0xa0, 0x6b, 0xba, + 0x64, 0xc8, 0x71, 0x37, 0x14, 0x90, 0xfe, 0xa7, 0x43, 0x92, 0x63, 0x46, 0xe2, 0x4d, 0x67, 0xfd, + 0x0b, 0x59, 0x45, 0xba, 0xac, 0xed, 0xae, 0xa2, 0xb6, 0xb8, 0x04, 0x89, 0x4f, 0xb1, 0xa4, 0x96, + 0x27, 0xde, 0xeb, 0x45, 0xb5, 0xbc, 0xa3, 0x1e, 0x9d, 0xc6, 0x15, 0xb7, 0xbd, 0xe8, 0xf2, 0xf6, + 0xb4, 0xa0, 0x53, 0x4a, 0x5f, 0x05, 0x00, 0x61, 0xce, 0xbb, 0x3a, 0x1f, 0xbc, 0x73, 0xe3, 0x8a, + 0xf8, 0xfe, 0x61, 0x49, 0x67, 0xcd, 0x56, 0x9c, 0xe2, 0xea, 0xce, 0xc6, 0xdc, 0xbd, 0x33, 0xb8, + 0xce, 0xe8, 0x5a, 0xbe, 0xcb, 0x4e, 0x7b, 0xbb, 0xd5, 0xc1, 0x8c, 0x46, 0x65, 0x62, 0x48, 0x55, + 0xb7, 0xef, 0xb0, 0xa7, 0xc6, 0xb7, 0xef, 0x86, 0xd7, 0x9e, 0x21, 0xd2, 0xf6, 0xf5, 0xc6, 0x7e, + 0x63, 0xb8, 0x2f, 0x93, 0x86, 0x06, 0x93, 0x71, 0xc6, 0x32, 0x8b, 0xc9, 0x9e, 0x25, 0x9a, 0xd6, + 0x54, 0xb7, 0xb0, 0xc5, 0x09, 0x72, 0xf6, 0x50, 0xdd, 0xe4, 0xf3, 0xac, 0x37, 0x59, 0xfa, 0xad, + 0xa0, 0x97, 0x56, 0x95, 0xea, 0x74, 0x48, 0x58, 0x0a, 0xf2, 0x14, 0xc2, 0x8c, 0x4e, 0x18, 0xe7, + 0x9f, 0xa3, 0xa7, 0x15, 0x1f, 0x5a, 0x46, 0x2d, 0x9d, 0x09, 0x8a, 0xb5, 0xa0, 0x65, 0xe9, 0x8e, + 0x9f, 0xf7, 0x60, 0x75, 0x6e, 0xf9, 0xe4, 0x3a, 0x2a, 0x3a, 0x45, 0xb0, 0xec, 0x2c, 0xa0, 0xc6, + 0x62, 0x52, 0x65, 0x1d, 0x71, 0x66, 0xc0, 0x92, 0x51, 0x1e, 0x28, 0x9d, 0x96, 0xfa, 0xdf, 0x19, + 0x58, 0xac, 0x80, 0xd0, 0xc9, 0xe9, 0x92, 0x9a, 0x67, 0x89, 0x64, 0x4c, 0xc2, 0xc2, 0x6a, 0x93, + 0x8c, 0x74, 0xef, 0x1c, 0x29, 0x7c, 0xc5, 0xcb, 0xbd, 0x90, 0xa2, 0x70, 0x2e, 0xe2, 0x58, 0x6e, + 0x5d, 0xa8, 0x06, 0xe2, 0x82, 0x1b, 0x03, 0xfd, 0x78, 0xa8, 0x46, 0xf6, 0x08, 0x51, 0x97, 0x4e, + 0x4b, 0x9d, 0x8d, 0x97, 0x3e, 0x0c, 0x3f, 0xad, 0xb9, 0xf5, 0xeb, 0x32, 0xcd, 0x23, 0x42, 0x20, + 0xcc, 0x9b, 0x09, 0xd4, 0x44, 0x59, 0x80, 0x87, 0x77, 0xd9, 0x1a, 0xf8, 0x86, 0x7d, 0x04, 0xe4, + 0x61, 0x79, 0x2d, 0xd6, 0x38, 0xa0, 0x68, 0x3f, 0x88, 0x1b, 0x66, 0xdf, 0xd2, 0x57, 0x25, 0x1a, + 0x2e, 0x27, 0x7c, 0xd8, 0x62, 0xbd, 0xae, 0x56, 0x16, 0xec, 0xa4, 0x8e, 0xa5, 0xe7, 0x5f, 0xdc, + 0x7f, 0x90, 0xf7, 0xf3, 0x6c, 0x8b, 0x38, 0xcd, 0x72, 0xd4, 0x1d, 0xd3, 0x5b, 0x20, 0x3c, 0xbd, + 0x75, 0x93, 0x60, 0x15, 0xcd, 0x03, 0x3a, 0x10, 0x86, 0xde, 0xb2, 0xcf, 0xe7, 0x74, 0xe3, 0x3e, + 0xf2, 0x4f, 0x7f, 0xb5, 0xed, 0xe1, 0x51, 0x08, 0x07, 0xfc, 0x86, 0xaf, 0x81, 0xfe, 0x2c, 0x8d, + 0xa6, 0x8e, 0xeb, 0x9c, 0xe4, 0xc1, 0x2c, 0xc8, 0xa2, 0xd3, 0x48, 0x5f, 0x09, 0xa5, 0x8f, 0x9f, + 0x44, 0x3f, 0x2c, 0xd2, 0x84, 0xaf, 0xa3, 0x1f, 0xd2, 0x29, 0x7e, 0x82, 0xa9, 0x51, 0xca, 0x75, + 0x2f, 0x74, 0xb8, 0xdf, 0x30, 0x19, 0xed, 0x68, 0xbd, 0xce, 0xc0, 0x40, 0x10, 0x4b, 0xfa, 0xde, + 0xee, 0x50, 0x9d, 0xb5, 0xc3, 0xc2, 0xb5, 0xad, 0xd3, 0x53, 0xab, 0x0d, 0xb9, 0x92, 0xe6, 0x05, + 0x1e, 0x4b, 0x28, 0x8a, 0x04, 0xe3, 0x6f, 0xd3, 0xf1, 0xc6, 0x20, 0x0b, 0x17, 0x6d, 0xe8, 0xab, + 0x8d, 0x1d, 0x69, 0x29, 0x44, 0x27, 0x0f, 0xd9, 0x91, 0xc4, 0x98, 0xf2, 0x59, 0x41, 0x71, 0x77, + 0x5e, 0x46, 0xe6, 0x2d, 0x67, 0x19, 0x44, 0xf3, 0x0c, 0x4b, 0xe8, 0x59, 0x09, 0x86, 0x69, 0x89, + 0x5b, 0xa1, 0xe9, 0x35, 0xef, 0x88, 0x85, 0xb2, 0xbc, 0x3e, 0x93, 0xef, 0x9e, 0xf5, 0x49, 0xb7, + 0xdb, 0x85, 0x0e, 0x7d, 0x1c, 0x54, 0xa7, 0x2f, 0x81, 0x81, 0xb3, 0xf0, 0x78, 0x04, 0xd8, 0x6c, + 0x36, 0x03, 0x30, 0xc3, 0x42, 0x5d, 0x16, 0x36, 0xd7, 0xf1, 0x06, 0xee, 0x06, 0x82, 0x92, 0xdf, + 0x10, 0xcd, 0x43, 0x54, 0x55, 0x67, 0xbb, 0xe9, 0x70, 0xd0, 0x2b, 0x48, 0x29, 0xa2, 0xde, 0xbc, + 0xc6, 0x8c, 0x7c, 0x1f, 0x15, 0x63, 0x4f, 0xa5, 0x59, 0x02, 0x1f, 0x96, 0xbe, 0x7d, 0x00, 0x07, + 0x52, 0x9a, 0x71, 0xf4, 0xc1, 0x05, 0x65, 0xc6, 0xd1, 0x57, 0x17, 0x94, 0x19, 0x97, 0xfb, 0xff, + 0xb2, 0xe1, 0xb9, 0xd1, 0x49, 0x8a, 0x83, 0x6e, 0xe8, 0x9f, 0xf3, 0x42, 0x47, 0xa3, 0x2e, 0x13, + 0xc1, 0x64, 0x3c, 0xda, 0x99, 0x88, 0x80, 0xd2, 0x53, 0x04, 0x91, 0xc7, 0x6c, 0x44, 0x76, 0xa2, + 0x28, 0xa6, 0x0d, 0xee, 0xdd, 0x0a, 0xe3, 0x3d, 0xbe, 0x6b, 0x05, 0x91, 0xaa, 0x51, 0x68, 0xb5, + 0xbb, 0x1f, 0xa2, 0x2e, 0xd6, 0x90, 0x1b, 0x10, 0x4d, 0x8f, 0x77, 0x07, 0xa0, 0x1e, 0x47, 0x77, + 0x3f, 0x40, 0x5d, 0x4c, 0xf1, 0xe6, 0x0a, 0x60, 0xc3, 0x4f, 0xd6, 0x10, 0x83, 0x9d, 0x36, 0x6a, + 0x20, 0x4f, 0x81, 0xde, 0x19, 0xaf, 0xf6, 0xb5, 0x6b, 0x13, 0x77, 0x34, 0x96, 0x1e, 0x76, 0xd3, + 0xe9, 0x4e, 0x77, 0x9c, 0x6e, 0x2c, 0x6f, 0xda, 0x29, 0xb1, 0xb2, 0x25, 0x1d, 0x1e, 0x19, 0xe4, + 0xaf, 0xd2, 0x41, 0xde, 0xf6, 0xe3, 0xd3, 0x97, 0x2c, 0x6c, 0xfb, 0x3d, 0x75, 0xa9, 0xd3, 0xff, + 0xa9, 0xb1, 0xbf, 0x06, 0x73, 0x65, 0xe6, 0x07, 0x2e, 0x5d, 0x40, 0x0c, 0xdc, 0x39, 0x5b, 0xe1, + 0x77, 0x32, 0xe0, 0xf2, 0xdc, 0xec, 0xa8, 0xd7, 0x77, 0x2f, 0x4e, 0x42, 0x3d, 0xee, 0x99, 0x18, + 0xed, 0x42, 0xfc, 0xae, 0xc4, 0x88, 0xeb, 0xb3, 0x9c, 0x3b, 0x5e, 0xbb, 0xc0, 0xcb, 0x27, 0x33, + 0xf0, 0x0d, 0x4d, 0xfe, 0xd2, 0xbd, 0xbc, 0xbc, 0xa4, 0xf7, 0x99, 0x78, 0x3f, 0x3b, 0x73, 0xcf, + 0xce, 0xce, 0xf0, 0xde, 0x95, 0xef, 0x17, 0x5d, 0x91, 0x9e, 0x51, 0xfa, 0x52, 0xd7, 0xed, 0x76, + 0xc5, 0xfb, 0x4b, 0x5d, 0x77, 0x26, 0xdf, 0x05, 0x22, 0x25, 0xe0, 0xf1, 0x6e, 0x44, 0x40, 0x98, + 0xa9, 0x98, 0x62, 0xb5, 0xdd, 0x43, 0xa1, 0x3f, 0xb1, 0x15, 0x4e, 0xe7, 0xec, 0x28, 0x12, 0xe0, + 0xb0, 0xf9, 0xde, 0x72, 0x82, 0xab, 0x6a, 0x0c, 0x8c, 0x29, 0xc8, 0x98, 0xe9, 0xd4, 0x95, 0xd3, + 0x9e, 0xca, 0x60, 0x1f, 0x25, 0x64, 0xc4, 0x54, 0x0f, 0x48, 0x56, 0x9b, 0xab, 0x6a, 0x73, 0x5d, + 0x6d, 0xae, 0xab, 0x8d, 0xf7, 0x06, 0x28, 0x68, 0xb4, 0x93, 0xbd, 0xa3, 0x89, 0xc4, 0xc6, 0x3d, + 0x40, 0x37, 0x47, 0x23, 0x04, 0x51, 0xb3, 0xcf, 0x89, 0xea, 0x73, 0xa2, 0xfb, 0x9c, 0x54, 0x7d, + 0x6e, 0x36, 0x82, 0xc3, 0x6b, 0x9d, 0x3b, 0x2f, 0xb6, 0x75, 0xc6, 0x86, 0x71, 0xf6, 0xa0, 0xac, + 0x2e, 0x6f, 0xef, 0xe1, 0xb3, 0xfd, 0x47, 0xd2, 0x20, 0xbf, 0x45, 0xa4, 0x8c, 0x6c, 0xa9, 0x66, + 0x50, 0x8c, 0x9b, 0x41, 0xb1, 0xc1, 0xd3, 0x96, 0xee, 0xb0, 0xd3, 0x3b, 0x89, 0xda, 0x70, 0xc5, + 0x20, 0x41, 0x53, 0x08, 0x9c, 0xca, 0x1f, 0xac, 0xdd, 0x6a, 0x92, 0x52, 0x5d, 0x47, 0x39, 0xe4, + 0x5b, 0x0e, 0x74, 0x79, 0x92, 0x39, 0x8f, 0x88, 0x27, 0x33, 0x70, 0x95, 0xef, 0xf0, 0x90, 0xc6, + 0x4c, 0xde, 0x8e, 0x45, 0x68, 0xc8, 0xde, 0x09, 0x63, 0xe5, 0xce, 0xbe, 0xd8, 0x98, 0xa8, 0xbd, + 0x1b, 0xa8, 0x3a, 0x73, 0x2f, 0x4f, 0xe2, 0x41, 0x2d, 0x16, 0x0d, 0xf1, 0xb3, 0x27, 0x2e, 0x47, + 0x81, 0x37, 0x54, 0xdf, 0x0a, 0x56, 0xc5, 0x62, 0xd1, 0x48, 0x10, 0x13, 0x17, 0x8a, 0x2f, 0x96, + 0xc0, 0xfe, 0xc1, 0xea, 0x48, 0x03, 0x87, 0x4e, 0xb0, 0x97, 0x23, 0x6e, 0x4c, 0x74, 0xec, 0x63, + 0x31, 0xb4, 0xe1, 0x4c, 0x9f, 0xbd, 0x71, 0xa1, 0x8f, 0x37, 0x1b, 0x67, 0xe7, 0x8c, 0x49, 0x6d, + 0x79, 0x4d, 0xa3, 0x95, 0xb5, 0x4b, 0x08, 0xc9, 0xb6, 0x31, 0xf6, 0x5e, 0xf4, 0x61, 0x29, 0x73, + 0x6a, 0xa7, 0xfe, 0x43, 0x6d, 0xb2, 0x64, 0x1c, 0x96, 0x12, 0x99, 0x5f, 0xb5, 0x8d, 0x93, 0x99, + 0x16, 0x4e, 0xea, 0x28, 0x9f, 0x58, 0xba, 0xd8, 0x94, 0x15, 0x23, 0x2b, 0x08, 0xaf, 0xe7, 0x62, + 0x25, 0x3d, 0x0b, 0x0e, 0x58, 0x14, 0xd6, 0xae, 0xee, 0x27, 0x61, 0x18, 0xb6, 0xfa, 0x17, 0x9f, + 0xb1, 0x16, 0x69, 0xcb, 0x56, 0xb7, 0x75, 0xd1, 0xa5, 0x34, 0xe5, 0x76, 0x5b, 0x2f, 0xea, 0x7c, + 0xd8, 0x60, 0x35, 0x14, 0x0a, 0x8d, 0x7b, 0xd6, 0x4b, 0xf8, 0xd9, 0x2f, 0xc9, 0x63, 0xa5, 0x83, + 0x24, 0xe4, 0xb1, 0xc8, 0xe9, 0xee, 0x9d, 0xdb, 0x37, 0xaa, 0xbf, 0xe7, 0xcc, 0xb1, 0x36, 0xcb, + 0xc8, 0xbc, 0xeb, 0x4a, 0xcb, 0x4e, 0xc7, 0x97, 0x2d, 0xd0, 0xc3, 0x67, 0x96, 0x9e, 0xb1, 0x7a, + 0x7b, 0x7c, 0xca, 0xf0, 0xf5, 0x2d, 0xf2, 0xcb, 0x02, 0x60, 0x78, 0x8b, 0x47, 0xf8, 0x0e, 0x8f, + 0xc0, 0x84, 0x6f, 0xa1, 0x5b, 0x58, 0xad, 0x48, 0x64, 0xd4, 0x89, 0xc5, 0x42, 0x58, 0x39, 0x33, + 0xb2, 0x1c, 0xe4, 0x34, 0x27, 0x05, 0x7b, 0xd0, 0x66, 0xb0, 0x07, 0x2a, 0x11, 0x46, 0xb3, 0xa7, + 0x6e, 0x10, 0xc2, 0x3e, 0xe1, 0xcd, 0x80, 0x3c, 0x6c, 0xf2, 0x2a, 0xeb, 0xfd, 0x6a, 0xae, 0x3e, + 0x4d, 0xa6, 0x73, 0xc4, 0xad, 0x68, 0x76, 0xe7, 0xe5, 0xee, 0x1d, 0xbb, 0xc7, 0xef, 0x3d, 0xd9, + 0x2b, 0x86, 0xc5, 0x72, 0x6f, 0x58, 0x2c, 0x79, 0x21, 0xcd, 0x8d, 0xa2, 0x8a, 0x1a, 0x15, 0x75, + 0xd4, 0xa8, 0xa8, 0xa3, 0x46, 0xda, 0x56, 0xa1, 0xc3, 0x8d, 0x8a, 0xb2, 0x61, 0xab, 0x34, 0xed, + 0x9d, 0xb0, 0xb6, 0x77, 0xc8, 0x47, 0x3f, 0x6c, 0xef, 0x24, 0xb5, 0xbd, 0x13, 0xec, 0x8d, 0xb5, + 0x8e, 0x94, 0x02, 0x9a, 0xcd, 0xb6, 0xe4, 0x25, 0x0c, 0x9b, 0x92, 0x43, 0x62, 0xd2, 0x1a, 0x31, + 0x90, 0x15, 0x09, 0x4a, 0x56, 0x6b, 0xf1, 0x2e, 0x13, 0xff, 0xd5, 0x92, 0x5d, 0x8a, 0x5c, 0x08, + 0x53, 0x14, 0xd1, 0x5d, 0x5e, 0x3f, 0xde, 0x89, 0xbe, 0x28, 0xce, 0xda, 0x67, 0x46, 0xc9, 0x90, + 0xda, 0xc1, 0xfd, 0x84, 0x4c, 0x6f, 0xd7, 0x48, 0x73, 0x2b, 0xf6, 0x49, 0x7b, 0x67, 0x62, 0x6f, + 0x24, 0xa4, 0x83, 0x7d, 0x55, 0xb0, 0x9a, 0x22, 0xd3, 0xed, 0x54, 0x1e, 0xa4, 0x3c, 0xc9, 0x29, + 0x3e, 0xdd, 0xb6, 0xc3, 0x4e, 0x2a, 0xcf, 0x53, 0x9e, 0x80, 0x14, 0x84, 0xd4, 0x70, 0x36, 0x7f, + 0x5d, 0xd8, 0x47, 0x33, 0x9b, 0x8e, 0x2b, 0x54, 0x62, 0x3f, 0x28, 0xa4, 0x1c, 0x62, 0x33, 0x7d, + 0x05, 0xcc, 0xcf, 0x86, 0xe2, 0x6c, 0xba, 0xa4, 0x31, 0x11, 0xbb, 0x02, 0x9b, 0x7b, 0x76, 0xbe, + 0x2d, 0xa0, 0x42, 0x71, 0x2e, 0x22, 0x54, 0x00, 0x9c, 0x5d, 0xf1, 0xa4, 0x4e, 0x78, 0x89, 0xcf, + 0x5d, 0x1c, 0x1f, 0x3f, 0x09, 0x80, 0x44, 0xe1, 0xbf, 0x43, 0xc0, 0x7d, 0x41, 0xc1, 0xba, 0x43, + 0x9c, 0xbf, 0x5f, 0x92, 0x95, 0xa6, 0xa7, 0x56, 0x89, 0xbd, 0xc7, 0x65, 0xcd, 0x33, 0xba, 0x79, + 0x96, 0xfc, 0x48, 0x35, 0xc3, 0xb3, 0x87, 0x43, 0x72, 0x04, 0xd2, 0x61, 0x92, 0x16, 0x45, 0x0a, + 0x27, 0x34, 0xa0, 0x3d, 0x71, 0x69, 0xb1, 0x6d, 0xd5, 0x50, 0xf2, 0x23, 0x18, 0x75, 0xc7, 0xc4, + 0xd4, 0x70, 0x52, 0xc9, 0x63, 0x29, 0xe8, 0x56, 0xfe, 0x2e, 0x51, 0x17, 0x58, 0xf7, 0x70, 0xe3, + 0x1c, 0xa2, 0xf9, 0x6d, 0xb9, 0xd3, 0x53, 0x72, 0x47, 0x39, 0xed, 0xda, 0x23, 0xaa, 0xb6, 0x3c, + 0xff, 0xaa, 0x20, 0x9a, 0x8d, 0x14, 0xc4, 0xb1, 0x90, 0x48, 0xc6, 0xeb, 0xfd, 0xe6, 0x31, 0xf9, + 0x17, 0x9a, 0xe3, 0x08, 0xb7, 0xc6, 0x21, 0x25, 0xcf, 0xb7, 0xc6, 0x68, 0xd6, 0xeb, 0xbf, 0x38, + 0xba, 0xb0, 0x39, 0xba, 0xd0, 0x18, 0x1d, 0xe1, 0x78, 0xd2, 0xfc, 0xf8, 0xa4, 0xff, 0x60, 0xba, + 0x9d, 0x7a, 0xab, 0x32, 0xf3, 0x5e, 0x8a, 0xfe, 0x44, 0x80, 0x4d, 0x76, 0xf4, 0xa0, 0xca, 0xe0, + 0xf7, 0x2f, 0xb7, 0x40, 0xec, 0x92, 0x92, 0xb1, 0xc9, 0xeb, 0xc1, 0xa1, 0x27, 0xcd, 0x5d, 0x59, + 0x09, 0xd5, 0x89, 0xc2, 0xa6, 0x01, 0xe4, 0xf5, 0xfb, 0x90, 0x71, 0x4d, 0x9b, 0xc7, 0xeb, 0xf5, + 0xf8, 0xd9, 0xc6, 0x08, 0xe9, 0xdc, 0x56, 0xc2, 0x5e, 0x6c, 0x9e, 0x70, 0xa7, 0x12, 0xf4, 0x95, + 0x48, 0x4f, 0x7d, 0x9b, 0x37, 0xf6, 0x90, 0xb4, 0x84, 0x77, 0x6a, 0xc1, 0x4f, 0xbb, 0x9f, 0x42, + 0xca, 0xd1, 0x77, 0xc8, 0xfe, 0x84, 0x08, 0xd5, 0x9e, 0xe8, 0x23, 0xfb, 0x36, 0xb4, 0x27, 0xd1, + 0x7b, 0xd9, 0x6d, 0x8b, 0xcd, 0x1a, 0x32, 0xa0, 0x8f, 0xba, 0x8e, 0x73, 0x62, 0x27, 0xc5, 0xa9, + 0x38, 0x59, 0x90, 0x43, 0xd2, 0x4a, 0xc9, 0x99, 0xa2, 0xf3, 0xe6, 0xd6, 0xd3, 0xf6, 0xce, 0x15, + 0xcc, 0x51, 0xaf, 0x57, 0x0b, 0x5d, 0xde, 0xce, 0xe5, 0xbe, 0x5f, 0x98, 0xe6, 0x50, 0xe0, 0x27, + 0xb4, 0x35, 0x58, 0xea, 0xbc, 0x9c, 0x36, 0x32, 0x90, 0x57, 0x09, 0x5f, 0xe8, 0xa2, 0xff, 0x3d, + 0x8b, 0xc1, 0xba, 0x90, 0x59, 0xfb, 0xb6, 0x18, 0x36, 0xff, 0x0b, 0x62, 0x1e, 0xa9, 0xa3, 0xfd, + 0x16, 0xff, 0x7e, 0x4c, 0x6f, 0x6f, 0x26, 0xb6, 0x32, 0xd0, 0x42, 0x27, 0x61, 0x65, 0x27, 0x72, + 0x5e, 0xa5, 0xda, 0x6a, 0x55, 0xdb, 0x27, 0x47, 0xbd, 0xc1, 0x96, 0x1a, 0x09, 0x0d, 0x35, 0x92, + 0xee, 0x53, 0x23, 0xe9, 0xb6, 0x16, 0x10, 0xd4, 0xe2, 0xc7, 0xcf, 0x56, 0x23, 0x4f, 0x01, 0xf8, + 0x77, 0xa9, 0x91, 0x7f, 0x12, 0x01, 0xfd, 0x59, 0x45, 0x92, 0xfd, 0x05, 0x45, 0x22, 0x3a, 0xfa, + 0x96, 0xb8, 0xf8, 0x40, 0x5f, 0x01, 0x7b, 0x30, 0x42, 0x98, 0x59, 0x4a, 0xb7, 0x37, 0x7f, 0x86, + 0xcd, 0x03, 0xe6, 0xac, 0xb7, 0x6b, 0xdb, 0x57, 0x5d, 0xe8, 0x85, 0x29, 0x9f, 0x37, 0x8c, 0xe7, + 0xed, 0x1d, 0xd9, 0x6d, 0xb6, 0xd8, 0xb6, 0xd0, 0xe1, 0xe0, 0xb0, 0xd6, 0x3d, 0x8f, 0xe3, 0xf4, + 0x96, 0xb5, 0xe2, 0xe8, 0x86, 0xb3, 0x56, 0xf0, 0x7b, 0x19, 0xb0, 0x16, 0xf9, 0x3b, 0xac, 0x75, + 0x13, 0x50, 0xb8, 0x10, 0xaf, 0xa8, 0xe7, 0x58, 0xde, 0xde, 0xd6, 0x55, 0x1d, 0xd9, 0x44, 0x36, + 0x97, 0xa0, 0x34, 0x60, 0xd1, 0x7a, 0xf3, 0x14, 0x4a, 0xde, 0x1b, 0xe7, 0xaf, 0x0f, 0x63, 0xc6, + 0x9c, 0x2b, 0x8d, 0x23, 0x88, 0xeb, 0xf1, 0xc8, 0x88, 0x5e, 0x0b, 0x28, 0xc8, 0x79, 0x5e, 0x74, + 0x72, 0x88, 0x4d, 0xe9, 0x62, 0xb0, 0x96, 0x40, 0xa8, 0x12, 0x66, 0x72, 0x28, 0x0a, 0x37, 0xdf, + 0xd1, 0x52, 0x26, 0x3c, 0xcf, 0x45, 0x4c, 0xf0, 0xe0, 0xe0, 0xaa, 0x6a, 0xcf, 0x1d, 0x9b, 0x30, + 0x38, 0x59, 0xba, 0xa4, 0x2f, 0x47, 0xdf, 0x7b, 0xbd, 0x4e, 0x2e, 0x8d, 0xbc, 0x27, 0xb1, 0xf0, + 0x85, 0x10, 0x0f, 0x8f, 0xf4, 0x52, 0x39, 0x0b, 0xe5, 0x13, 0xfa, 0x3e, 0xfe, 0xff, 0xaf, 0xef, + 0xc3, 0xa6, 0xbe, 0x0f, 0x9f, 0xab, 0xef, 0x63, 0x73, 0x1c, 0xf1, 0xbf, 0x4b, 0xdf, 0x87, 0xa3, + 0xb8, 0x39, 0xba, 0x78, 0x4b, 0xdf, 0x93, 0xec, 0xbb, 0x36, 0xb6, 0xbc, 0xa2, 0xed, 0xbd, 0xcf, + 0x7a, 0x03, 0x6c, 0x10, 0xed, 0xd9, 0x06, 0x95, 0x6a, 0x95, 0x02, 0x7f, 0xea, 0xda, 0x9e, 0x21, + 0xd7, 0x7a, 0x32, 0x4f, 0x6c, 0x7d, 0xe5, 0xbe, 0x3e, 0x2c, 0x2c, 0xe7, 0xf6, 0x55, 0x33, 0x13, + 0x06, 0x03, 0xcf, 0xc0, 0x43, 0x5b, 0xd9, 0xd1, 0x94, 0xbe, 0x7d, 0x3c, 0x65, 0x36, 0xed, 0xfd, + 0x35, 0x1d, 0x2c, 0xfd, 0xee, 0x8d, 0x54, 0x6a, 0xec, 0xec, 0xdd, 0x28, 0xaa, 0x3e, 0xf9, 0x11, + 0x4c, 0xa7, 0x52, 0x84, 0x0b, 0xfd, 0xa7, 0xaf, 0xaf, 0x9a, 0xb2, 0xbd, 0xeb, 0x18, 0x1f, 0x0d, + 0xd8, 0x35, 0x5b, 0xb0, 0x8c, 0xa2, 0x23, 0xaf, 0x9e, 0x38, 0x53, 0x63, 0x30, 0x50, 0xc1, 0xa4, + 0x47, 0xec, 0xe9, 0xf0, 0xd1, 0xa6, 0x0a, 0x3d, 0x46, 0x5b, 0x5b, 0xbf, 0xd1, 0xe3, 0x5b, 0xbf, + 0x91, 0xf8, 0x8c, 0xe8, 0x73, 0xb6, 0x7e, 0xf5, 0xcc, 0xb6, 0xc3, 0xe3, 0xd5, 0xb7, 0xc5, 0xc5, + 0xa9, 0x13, 0x63, 0x8c, 0xfa, 0x03, 0xe8, 0x03, 0x65, 0xb7, 0xf0, 0xdb, 0xd6, 0x4f, 0x76, 0x51, + 0xdd, 0xa5, 0xa1, 0x3a, 0x72, 0xb3, 0xc8, 0xd8, 0x2b, 0x56, 0x87, 0x14, 0x15, 0x80, 0x7c, 0x29, + 0xbe, 0xcc, 0x98, 0xc0, 0x03, 0x8d, 0x1a, 0x94, 0xb0, 0x67, 0x0d, 0x8c, 0x63, 0x45, 0x8a, 0x37, + 0xfc, 0x64, 0x63, 0xa2, 0xba, 0xbe, 0x4c, 0x2c, 0xef, 0xf8, 0x3f, 0xec, 0x62, 0x55, 0xd7, 0xaf, + 0xe8, 0xe4, 0x26, 0x82, 0xaf, 0x20, 0x97, 0x83, 0xae, 0x8a, 0x5a, 0x2c, 0xda, 0xda, 0xd0, 0x55, + 0x5f, 0x8d, 0xdf, 0xc2, 0x4b, 0x63, 0x53, 0xbc, 0x39, 0x97, 0x82, 0xf5, 0x1c, 0x3a, 0x96, 0x90, + 0xa8, 0x4b, 0xa4, 0xf6, 0xff, 0xa3, 0x79, 0xa9, 0x4f, 0x90, 0xfb, 0xbe, 0x31, 0x28, 0xbd, 0xcf, + 0xfd, 0x18, 0x95, 0x72, 0x03, 0x05, 0x99, 0x3a, 0xe7, 0x90, 0x6c, 0x21, 0xa1, 0xd9, 0x74, 0xf7, + 0x13, 0x14, 0xa2, 0xb5, 0x89, 0x0c, 0xba, 0xae, 0xfb, 0xd4, 0x04, 0x8c, 0xf1, 0x6f, 0xf6, 0x8c, + 0xa5, 0xea, 0xd3, 0x32, 0xf0, 0xb7, 0x3b, 0xae, 0xd7, 0x52, 0x70, 0x1c, 0xfa, 0xc6, 0xc7, 0x60, + 0x8b, 0x7a, 0xbb, 0x4f, 0xad, 0x86, 0xb1, 0x16, 0x7a, 0xfd, 0x36, 0xbb, 0x32, 0xea, 0xb1, 0x86, + 0xd1, 0x33, 0x44, 0x44, 0x72, 0x60, 0xc2, 0x71, 0x6c, 0xce, 0x36, 0xdf, 0x9a, 0x6e, 0x9a, 0x1c, + 0x9c, 0xa7, 0x3e, 0x40, 0x20, 0x85, 0xe4, 0x60, 0xe7, 0x0b, 0xc1, 0xc3, 0x02, 0x02, 0xee, 0x80, + 0x68, 0xb3, 0x33, 0x14, 0xad, 0xd7, 0xe2, 0x41, 0x5f, 0x62, 0x74, 0xaa, 0xaf, 0x91, 0x44, 0x5b, + 0x02, 0x15, 0x15, 0xc4, 0xa7, 0x99, 0x77, 0x72, 0xf7, 0xc2, 0x4d, 0xd4, 0x97, 0x5a, 0xe4, 0xf9, + 0x09, 0x67, 0xb3, 0x17, 0x1e, 0x75, 0xb8, 0xd9, 0x9e, 0xe7, 0x6c, 0x66, 0x4e, 0xb4, 0x3e, 0x83, + 0x47, 0x0b, 0xfa, 0xa7, 0xa6, 0x26, 0xb9, 0x34, 0x53, 0x68, 0x41, 0x35, 0xf9, 0xa7, 0x20, 0xb4, + 0xd8, 0xa9, 0x3e, 0xa1, 0x00, 0xe1, 0xd3, 0xdb, 0x19, 0x06, 0x2d, 0xce, 0xfe, 0xeb, 0x92, 0xfa, + 0x20, 0x87, 0xf8, 0x80, 0xfb, 0xf6, 0x07, 0x70, 0x3a, 0xbd, 0x41, 0xf7, 0x55, 0x46, 0x9f, 0x93, + 0x16, 0x57, 0x03, 0xab, 0xe2, 0x51, 0xd6, 0xee, 0x8d, 0xf5, 0x89, 0x8d, 0x6d, 0x0d, 0x36, 0x38, + 0x3a, 0xda, 0xfe, 0x12, 0x8e, 0xf8, 0x6c, 0x00, 0x97, 0x4b, 0x43, 0x0f, 0x32, 0xf0, 0x6d, 0x63, + 0x91, 0x45, 0xc9, 0xe8, 0x71, 0x75, 0x55, 0xe8, 0x25, 0x90, 0x66, 0xb6, 0x04, 0xd3, 0xdb, 0x9a, + 0xa7, 0x29, 0x0c, 0x0f, 0x5e, 0x0f, 0xfd, 0xbf, 0x9b, 0x6a, 0x73, 0xd9, 0x07, 0x4a, 0x8d, 0x38, + 0x02, 0xc5, 0xd5, 0x10, 0x89, 0x8e, 0xa0, 0x9c, 0x42, 0x78, 0x50, 0x91, 0x03, 0xa5, 0x66, 0xce, + 0xbd, 0x26, 0xcb, 0x68, 0x57, 0x06, 0xbc, 0x13, 0x5f, 0x1d, 0xcf, 0x77, 0xe5, 0x52, 0x25, 0x71, + 0x8a, 0x1d, 0xa9, 0x9e, 0x47, 0x7f, 0xf0, 0xbd, 0x0d, 0x14, 0x30, 0x5b, 0x1f, 0x1d, 0xdd, 0xec, + 0x36, 0xdd, 0xf3, 0xc5, 0x81, 0xe7, 0xc8, 0x13, 0xd1, 0xd2, 0x36, 0x45, 0xc2, 0x23, 0xf2, 0x7c, + 0x9b, 0xf3, 0xc5, 0x07, 0xc3, 0x76, 0x07, 0xcc, 0x63, 0xfd, 0xc7, 0x2b, 0x9a, 0x2a, 0x4d, 0x7c, + 0x1f, 0x4b, 0xca, 0x92, 0x1d, 0x50, 0x86, 0x62, 0xde, 0x92, 0x27, 0xcf, 0x14, 0xd3, 0x86, 0x75, + 0xa6, 0x3f, 0xf3, 0xb0, 0xd7, 0x60, 0x13, 0x23, 0x11, 0x25, 0x9e, 0x3c, 0x96, 0x64, 0x49, 0xb7, + 0x74, 0x57, 0xf6, 0x99, 0xc5, 0xbb, 0x4c, 0x28, 0x3c, 0xd8, 0xed, 0x33, 0x55, 0x34, 0x5e, 0x71, + 0xdd, 0x6c, 0xb8, 0xd3, 0x99, 0x3c, 0xa1, 0x64, 0x08, 0x52, 0xd4, 0xf5, 0x7a, 0xfb, 0xeb, 0x4a, + 0x85, 0xd7, 0xac, 0xda, 0xf7, 0xfd, 0xea, 0x83, 0x4d, 0x66, 0x5d, 0x3a, 0x51, 0xd4, 0xac, 0xba, + 0x4d, 0x1a, 0x5b, 0x67, 0xe7, 0xaa, 0xaf, 0xfe, 0x49, 0x81, 0x91, 0x54, 0x1b, 0x1d, 0x3a, 0xa2, + 0xd0, 0x94, 0x65, 0xf4, 0x65, 0x6f, 0xb0, 0xc6, 0xe8, 0xa1, 0xfa, 0x8a, 0x90, 0x77, 0x5b, 0x6c, + 0x98, 0xf1, 0xba, 0x2c, 0x36, 0x63, 0xb8, 0x63, 0xb5, 0x8b, 0x16, 0xde, 0x8b, 0x93, 0x7d, 0xc4, + 0x18, 0x8d, 0x6a, 0x4c, 0x7e, 0x89, 0x3f, 0xf7, 0x1e, 0xcc, 0x90, 0x98, 0xdc, 0xd4, 0xdb, 0x6c, + 0x9c, 0x6d, 0xc7, 0x4a, 0x38, 0x55, 0x82, 0x2e, 0x7e, 0x88, 0xc2, 0x6b, 0x72, 0xaa, 0xa2, 0xa9, + 0x47, 0x01, 0x03, 0x7d, 0xb4, 0x48, 0x9f, 0xae, 0x83, 0xb4, 0x90, 0xa9, 0xcd, 0x06, 0x16, 0x47, + 0xd3, 0x67, 0x32, 0x34, 0x52, 0xfd, 0x1d, 0x24, 0xb1, 0x4f, 0xa2, 0x06, 0x53, 0xc7, 0x70, 0xa2, + 0x7d, 0x21, 0x9c, 0x8c, 0x3d, 0xd0, 0x59, 0x1e, 0xf5, 0x57, 0x34, 0x74, 0x3c, 0xa7, 0xdc, 0x22, + 0x01, 0x69, 0x4b, 0x52, 0x58, 0x5a, 0xb8, 0xa8, 0x5e, 0xa9, 0x1c, 0xa1, 0x44, 0x38, 0x22, 0x8e, + 0x3e, 0x9f, 0x75, 0xfd, 0x64, 0xf4, 0x50, 0xf2, 0xf7, 0x59, 0xb7, 0xab, 0x83, 0x14, 0x94, 0x94, + 0xe4, 0x28, 0xb7, 0x51, 0xb4, 0x55, 0x0e, 0xd1, 0xa7, 0x4e, 0x16, 0x7b, 0x97, 0x6c, 0x6b, 0xa3, + 0xca, 0xb3, 0x56, 0x90, 0xd5, 0x11, 0x1c, 0x1a, 0x1d, 0xcc, 0x7a, 0x6d, 0x42, 0x30, 0x22, 0x84, + 0x5e, 0x97, 0x99, 0x8e, 0x97, 0xf7, 0x92, 0xed, 0x7a, 0x67, 0x32, 0x12, 0x5a, 0x39, 0x63, 0xe6, + 0xeb, 0x0f, 0xcd, 0xf0, 0x28, 0x6b, 0x7a, 0xe4, 0xe4, 0x0f, 0xd6, 0x01, 0x0f, 0x4f, 0xbd, 0x18, + 0xa3, 0x6c, 0x1e, 0x21, 0x67, 0xf5, 0xd5, 0x1a, 0xd9, 0x47, 0xe3, 0xa0, 0x65, 0xaf, 0xcf, 0xaa, + 0xe3, 0xd7, 0xf2, 0x4f, 0xcb, 0xb0, 0x87, 0x1a, 0x15, 0x9a, 0x12, 0xd4, 0xb1, 0x4a, 0x22, 0x14, + 0xf9, 0xd7, 0x30, 0xa4, 0xbf, 0x62, 0x29, 0xac, 0x5b, 0x4c, 0x9e, 0xa4, 0x94, 0x00, 0xa4, 0x8b, + 0x70, 0x53, 0xb0, 0xbb, 0x82, 0x7d, 0x2c, 0xd8, 0xdb, 0x82, 0xbd, 0x2b, 0x7c, 0xfb, 0x8d, 0xe9, + 0xb5, 0xd8, 0x37, 0x85, 0x7f, 0x6d, 0xfa, 0x23, 0x5b, 0x24, 0xf2, 0xa6, 0x60, 0x37, 0xf0, 0x16, + 0xd1, 0xe6, 0x8d, 0x0f, 0x40, 0x6f, 0x0a, 0x23, 0x60, 0xfb, 0x86, 0xce, 0xa1, 0x2b, 0x75, 0x1f, + 0x81, 0xe2, 0x0e, 0x7c, 0x95, 0x54, 0x50, 0xbc, 0xd1, 0x8c, 0x57, 0x97, 0x9e, 0x12, 0xf3, 0x43, + 0x3a, 0xaa, 0xfa, 0x30, 0xa9, 0xbf, 0x6e, 0xfa, 0x7b, 0xc9, 0xb3, 0xfb, 0xf7, 0x3c, 0xe6, 0xe2, + 0x43, 0x7f, 0xf4, 0x65, 0xef, 0xc6, 0xdf, 0xd2, 0x88, 0xc4, 0xc1, 0x4d, 0xf1, 0xd7, 0x29, 0xa4, + 0xa0, 0x36, 0xbe, 0xfa, 0x75, 0xe0, 0x34, 0x01, 0x2b, 0x07, 0x37, 0x6e, 0x44, 0xdf, 0x59, 0x8c, + 0xe4, 0xb7, 0x9e, 0xb8, 0x2f, 0xfe, 0x86, 0x8c, 0xef, 0xa7, 0xf2, 0xbb, 0xed, 0x9e, 0x70, 0xdc, + 0x92, 0xf5, 0x3a, 0x71, 0x31, 0x37, 0xbf, 0x3a, 0x88, 0x4a, 0xb6, 0x0e, 0x2b, 0x49, 0x1f, 0xd3, + 0x5f, 0x26, 0xa1, 0x8f, 0x78, 0x43, 0x99, 0x7a, 0x74, 0x8a, 0x2c, 0x71, 0x28, 0xc9, 0x38, 0xfd, + 0xa1, 0x92, 0x3b, 0x66, 0x7c, 0xac, 0x7c, 0xfb, 0xaf, 0xc8, 0xd0, 0x5f, 0x33, 0xc8, 0x86, 0xa3, + 0x68, 0xec, 0x71, 0xd9, 0x55, 0xe3, 0x23, 0xaa, 0x89, 0xf9, 0x11, 0x55, 0x56, 0x32, 0x0a, 0x79, + 0x88, 0xbf, 0x7d, 0x42, 0x7f, 0x65, 0xa6, 0x24, 0x43, 0xce, 0x5e, 0x60, 0xb1, 0x76, 0x39, 0xf8, + 0x61, 0xfb, 0x24, 0x9e, 0x36, 0x88, 0xc5, 0x0d, 0x3e, 0xba, 0x3a, 0x00, 0x10, 0x56, 0x9c, 0x06, + 0xc4, 0x4a, 0xf4, 0x05, 0x51, 0xe3, 0xf0, 0x65, 0x30, 0xbd, 0x17, 0x4a, 0x67, 0x88, 0x35, 0xf1, + 0x0e, 0x1f, 0x1a, 0xb5, 0xbe, 0x7c, 0xf7, 0x56, 0x7c, 0xc4, 0x18, 0x79, 0x80, 0x43, 0xf7, 0xe3, + 0x09, 0xed, 0x0a, 0xd7, 0xf6, 0xc7, 0xc2, 0xbf, 0xa3, 0xff, 0xd7, 0xeb, 0x87, 0x8d, 0xe3, 0x82, + 0x33, 0x73, 0x8c, 0xc4, 0xb7, 0xc4, 0x5f, 0x71, 0xb3, 0x40, 0x78, 0xae, 0xf4, 0x5f, 0x7e, 0xaa, + 0x92, 0x52, 0xda, 0xf9, 0xef, 0x0a, 0x66, 0xbf, 0x2d, 0x7c, 0xe4, 0x96, 0x98, 0xaa, 0x2d, 0x9e, + 0xe2, 0x0f, 0x00, 0xb8, 0x0b, 0x7f, 0x01, 0x6a, 0x75, 0xab, 0x2f, 0x5f, 0x7f, 0x41, 0xdf, 0x96, + 0x9c, 0x13, 0x05, 0xbb, 0x92, 0x71, 0xfd, 0x89, 0x78, 0x91, 0xdb, 0xeb, 0xfe, 0x52, 0xbc, 0x88, + 0xe8, 0x94, 0x7f, 0x2b, 0xd2, 0x5f, 0xa4, 0x77, 0xfe, 0x3d, 0xd1, 0x3d, 0x78, 0xe0, 0xff, 0x00, + 0x6f, 0x95, 0xe5, 0xa4, 0x5e, 0x6e, 0x00, 0x00 +}; + + +// Autogenerated from wled00/data/rangetouch.js, do not edit!! +const uint16_t rangetouchJs_length = 1833; +const uint8_t rangetouchJs[] PROGMEM = { + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0a, 0xb5, 0x58, 0xdf, 0x8f, 0xdb, 0xb8, + 0x11, 0x7e, 0x3f, 0xe0, 0xfe, 0x07, 0x59, 0x6d, 0x7d, 0xe4, 0x2e, 0x57, 0xb6, 0x17, 0xc8, 0x8b, + 0x1c, 0xc6, 0x48, 0x73, 0x39, 0xa0, 0x68, 0xb6, 0x29, 0xb2, 0x39, 0xb4, 0x80, 0xcf, 0x0f, 0xb2, + 0x44, 0xdb, 0xbc, 0xc8, 0xa4, 0x8e, 0xa4, 0xbc, 0x31, 0x76, 0xf5, 0xbf, 0x77, 0x86, 0x94, 0x6c, + 0x39, 0x6b, 0x27, 0x79, 0xb8, 0x2e, 0x16, 0xb6, 0x44, 0x0d, 0x87, 0x33, 0xdf, 0x7c, 0xf3, 0x43, + 0x1e, 0x8d, 0x22, 0xfe, 0xa7, 0xfd, 0xfd, 0xf8, 0xc3, 0x68, 0x14, 0x99, 0x4c, 0xad, 0x85, 0xd3, + 0x75, 0xbe, 0x49, 0x7e, 0xb7, 0xd1, 0xee, 0x36, 0x19, 0x27, 0x13, 0xff, 0xe0, 0x2e, 0xfb, 0x24, + 0xd5, 0x3a, 0x7a, 0x29, 0x55, 0x55, 0xbb, 0xc8, 0xed, 0x2b, 0xc1, 0x63, 0x2f, 0x1c, 0xbf, 0x8a, + 0x1e, 0xb4, 0xf9, 0x14, 0x69, 0x15, 0xf9, 0x7d, 0x51, 0x21, 0x76, 0x32, 0x17, 0xd6, 0xef, 0xda, + 0x38, 0x57, 0xd9, 0x74, 0x34, 0x5a, 0x4b, 0xb7, 0xa9, 0x97, 0x49, 0xae, 0xb7, 0x23, 0x9b, 0x6d, + 0x2b, 0xed, 0x9c, 0x1d, 0x1d, 0x8f, 0xf2, 0xa2, 0xef, 0x60, 0x93, 0xb2, 0x22, 0x8d, 0x3e, 0x6e, + 0x44, 0x74, 0xf7, 0x8f, 0x8f, 0xdd, 0x42, 0x44, 0xe0, 0x86, 0x7a, 0x91, 0x3f, 0xd3, 0xd7, 0xc1, + 0xaa, 0x56, 0xb9, 0x93, 0x5a, 0x11, 0xc1, 0x1c, 0x7d, 0x8c, 0xf5, 0xf2, 0x77, 0x91, 0xbb, 0x98, + 0x73, 0x74, 0x4d, 0xaf, 0x22, 0xf1, 0xb9, 0xd2, 0xc6, 0xd9, 0xe1, 0x30, 0xae, 0x55, 0x21, 0x56, + 0x52, 0x89, 0x22, 0x1e, 0x74, 0x0f, 0xb7, 0xba, 0xa8, 0x4b, 0x31, 0x0b, 0x5f, 0x49, 0x2b, 0xca, + 0x1d, 0xa1, 0x69, 0xdc, 0xa9, 0x3d, 0x6a, 0x0a, 0xbb, 0x87, 0xc3, 0xf0, 0x9d, 0x64, 0xdb, 0x62, + 0x16, 0x2e, 0x49, 0xfc, 0x01, 0x21, 0xf8, 0x88, 0x10, 0xc4, 0x60, 0x44, 0x4a, 0x04, 0x17, 0x4f, + 0x4f, 0x56, 0x94, 0x2b, 0x9a, 0x1c, 0x1f, 0xa1, 0xde, 0x86, 0xb8, 0x8d, 0xb4, 0x8c, 0x1c, 0x8c, + 0x06, 0x8b, 0x6b, 0xc0, 0xc6, 0x3a, 0x23, 0xc1, 0xea, 0x69, 0xb7, 0x1e, 0x89, 0xe0, 0xce, 0x4a, + 0x1b, 0xb2, 0xcb, 0x4c, 0xa4, 0xf8, 0x78, 0xaa, 0x5e, 0xba, 0xa4, 0x14, 0x6a, 0xed, 0x36, 0x53, + 0x75, 0x7d, 0x4d, 0x1f, 0x71, 0xdd, 0x70, 0x37, 0x57, 0x8b, 0xa9, 0x49, 0x84, 0xaa, 0xb7, 0xc2, + 0x64, 0xcb, 0x52, 0xf0, 0xfe, 0xcd, 0xd3, 0xd3, 0x60, 0xc2, 0x0c, 0xc4, 0x4b, 0xad, 0xe4, 0xba, + 0x0e, 0xcf, 0x07, 0x63, 0x16, 0xef, 0xb2, 0xb2, 0x16, 0xb1, 0x54, 0x91, 0x19, 0x0e, 0x89, 0x49, + 0x1e, 0x8c, 0x74, 0xed, 0x33, 0xca, 0xde, 0x7b, 0x04, 0x93, 0xe0, 0xdb, 0xbf, 0x8d, 0xae, 0x84, + 0x71, 0x7b, 0x30, 0xc7, 0x24, 0x9f, 0xc4, 0x9e, 0x19, 0xda, 0x34, 0x07, 0x2b, 0x1d, 0x5a, 0xc9, + 0x14, 0x7d, 0x34, 0xc2, 0xd5, 0x06, 0xee, 0x23, 0x50, 0x29, 0x66, 0x97, 0x34, 0x38, 0xf6, 0xe8, + 0x0f, 0x4e, 0x15, 0x3b, 0x9a, 0x98, 0x82, 0x3d, 0x7d, 0xfb, 0xf0, 0xbe, 0xb3, 0x07, 0xae, 0x1b, + 0x9a, 0x8a, 0xb9, 0x5b, 0x70, 0xd8, 0x72, 0x3c, 0xb7, 0x0d, 0x76, 0x40, 0xa6, 0x3d, 0x0d, 0x8c, + 0xb3, 0x44, 0xd0, 0xa9, 0x5c, 0x91, 0x76, 0x05, 0x58, 0xf9, 0xfe, 0x41, 0x75, 0xe7, 0xdf, 0xef, + 0xb7, 0x4b, 0x5d, 0xda, 0x0e, 0xb7, 0xaf, 0xc9, 0xa0, 0x1a, 0x87, 0xc0, 0x00, 0x96, 0x2b, 0x59, + 0x3a, 0x61, 0xc8, 0x31, 0x62, 0xee, 0xe0, 0xed, 0x59, 0x15, 0x3f, 0x0b, 0x9b, 0x1b, 0x59, 0x39, + 0x08, 0x1c, 0xda, 0xd8, 0x8b, 0x45, 0x43, 0x29, 0x65, 0x2a, 0xa9, 0x6a, 0xbb, 0x49, 0xb2, 0xaa, + 0x2a, 0xf7, 0x44, 0x21, 0x9a, 0xad, 0x32, 0x75, 0xf4, 0x0e, 0x76, 0x1e, 0x23, 0x6f, 0xf8, 0x64, + 0x6a, 0x5e, 0x66, 0x66, 0x0d, 0x6a, 0x94, 0xb3, 0x1d, 0x03, 0x4c, 0xc7, 0x00, 0xc9, 0x55, 0x5d, + 0x96, 0x03, 0x7e, 0x90, 0x98, 0x9b, 0xc5, 0xac, 0x7f, 0x93, 0x3e, 0x36, 0x53, 0xf3, 0xb7, 0xdb, + 0x99, 0x6a, 0x61, 0x21, 0x92, 0x32, 0x08, 0x73, 0x02, 0x07, 0xbc, 0xcd, 0xf2, 0x4d, 0xcf, 0x33, + 0x88, 0x23, 0x06, 0x54, 0x31, 0x09, 0x9c, 0xa2, 0x60, 0x6e, 0xfa, 0x0d, 0x0f, 0xed, 0xd9, 0x48, + 0x4b, 0x01, 0x00, 0xb2, 0x6f, 0x6d, 0x05, 0x33, 0x68, 0xda, 0xb3, 0xe9, 0x9c, 0x41, 0x00, 0xf5, + 0x65, 0x2a, 0x7d, 0x0b, 0x7d, 0x09, 0xe8, 0xa3, 0x13, 0x1d, 0xc0, 0xa2, 0x09, 0x70, 0x3d, 0x66, + 0x45, 0xf1, 0xe6, 0xfe, 0x1e, 0x69, 0x06, 0xb5, 0x6c, 0xbb, 0xfc, 0x8f, 0x2c, 0xdc, 0x26, 0x9d, + 0xbc, 0x60, 0x0f, 0x99, 0xcb, 0x37, 0xc8, 0xb8, 0x63, 0x16, 0xd6, 0x81, 0x67, 0xad, 0x86, 0x5e, + 0xd2, 0xb6, 0x2b, 0xaf, 0x8d, 0xc9, 0xf6, 0xc9, 0xca, 0xe8, 0x2d, 0x29, 0x74, 0xee, 0x21, 0x4f, + 0xfe, 0xa8, 0x85, 0xd9, 0xdf, 0x8b, 0x12, 0x8c, 0xd3, 0xe6, 0x75, 0x59, 0x82, 0x17, 0x34, 0x91, + 0x2a, 0x2f, 0xeb, 0x02, 0x80, 0xc1, 0xf4, 0xa7, 0x4d, 0x92, 0x67, 0xf0, 0x00, 0x75, 0x7b, 0xa3, + 0x34, 0x3f, 0x16, 0xb1, 0x83, 0xee, 0x10, 0x56, 0x31, 0x13, 0x98, 0xbd, 0x50, 0x1f, 0x6a, 0xd4, + 0x97, 0xe2, 0x6a, 0xc3, 0x72, 0x7e, 0x5a, 0xf5, 0xc2, 0x96, 0xc1, 0x80, 0x40, 0x79, 0x02, 0xe2, + 0x0a, 0x48, 0x43, 0xeb, 0x32, 0x95, 0x63, 0xd9, 0x82, 0x33, 0x58, 0x79, 0xe9, 0x00, 0xce, 0x45, + 0xc3, 0xb2, 0x73, 0x4f, 0x35, 0x5c, 0x43, 0x81, 0x0d, 0x28, 0x37, 0xcc, 0x7e, 0x45, 0xe6, 0x1e, + 0x8a, 0x97, 0x5a, 0x37, 0x6c, 0x75, 0x4e, 0x26, 0x40, 0x24, 0xad, 0xff, 0x86, 0xf5, 0x86, 0x6d, + 0xce, 0x89, 0xe5, 0xe0, 0xc8, 0xbf, 0x74, 0x21, 0xde, 0x49, 0x8b, 0x06, 0x17, 0xdc, 0xb2, 0x3d, + 0x5f, 0xb1, 0x25, 0xdf, 0xb0, 0xed, 0x25, 0xf9, 0xb7, 0xa5, 0x40, 0xc4, 0x41, 0x7c, 0x7d, 0x51, + 0x64, 0x17, 0x04, 0xaa, 0x73, 0x02, 0x10, 0x01, 0xfa, 0xf4, 0x44, 0xac, 0xff, 0x5a, 0xf9, 0xcf, + 0x0d, 0x7c, 0xd2, 0xe1, 0x70, 0x20, 0xda, 0x3c, 0x7b, 0x7a, 0xca, 0x60, 0x05, 0x16, 0x4e, 0x8b, + 0x4c, 0xfb, 0xb4, 0xc7, 0x94, 0x5d, 0x08, 0x04, 0x14, 0x9f, 0xc9, 0xab, 0x43, 0x65, 0xea, 0x9f, + 0x89, 0x2b, 0x8e, 0xc7, 0x31, 0x46, 0x33, 0xcf, 0x1c, 0x2a, 0xd9, 0x22, 0xe1, 0xc8, 0x88, 0xcc, + 0xd2, 0xdf, 0x12, 0xf2, 0x5b, 0x71, 0x4d, 0xe9, 0x0c, 0xae, 0xe7, 0xe2, 0xed, 0x82, 0xcc, 0xaf, + 0x6f, 0x16, 0xb3, 0xb0, 0xf4, 0xd7, 0x11, 0x9d, 0x76, 0xe5, 0x75, 0x76, 0x97, 0xb9, 0x0d, 0xec, + 0xfb, 0x4c, 0xc6, 0x8c, 0xb8, 0xf9, 0x64, 0x31, 0xc3, 0x8f, 0xd6, 0x9a, 0x74, 0x4c, 0x6f, 0x60, + 0xf1, 0x76, 0x31, 0xbb, 0xc6, 0x4f, 0xb8, 0xa5, 0xe9, 0x18, 0x1a, 0xce, 0x61, 0x7b, 0x95, 0x19, + 0x2b, 0x7e, 0x29, 0x35, 0x9e, 0x9e, 0x38, 0xfd, 0x8b, 0xfc, 0x2c, 0x0a, 0x48, 0xfa, 0x43, 0x86, + 0x78, 0xe5, 0x46, 0x43, 0x9f, 0x24, 0x62, 0xe4, 0xe8, 0x95, 0x6b, 0x9e, 0x13, 0xff, 0xa4, 0xf4, + 0x43, 0xc1, 0x20, 0xa7, 0x44, 0x04, 0xff, 0x81, 0x84, 0xa7, 0xf4, 0xa3, 0x6e, 0x63, 0xf4, 0x43, + 0xa4, 0xc4, 0x43, 0xf4, 0x11, 0x3a, 0xe9, 0x5b, 0x63, 0x20, 0x3b, 0xe3, 0x37, 0x99, 0x52, 0xda, + 0x45, 0x98, 0x08, 0x51, 0x16, 0xe5, 0x65, 0x66, 0x6d, 0x94, 0xc1, 0xff, 0xe1, 0xb0, 0x18, 0x92, + 0x37, 0x74, 0x4b, 0x47, 0xd9, 0x16, 0xf0, 0x9a, 0xe1, 0x4d, 0x22, 0x42, 0xd0, 0xb9, 0x48, 0x0b, + 0x1f, 0x19, 0x72, 0xb2, 0x7a, 0x3e, 0x0b, 0x31, 0xa8, 0xa0, 0xa2, 0x2f, 0x09, 0x3b, 0xab, 0x93, + 0x85, 0xc4, 0x1c, 0x1a, 0xf5, 0x41, 0x6b, 0x68, 0x4b, 0xdc, 0x90, 0xc7, 0x86, 0x49, 0x06, 0x1f, + 0x8a, 0x32, 0xff, 0x40, 0x2a, 0x09, 0xcd, 0xfc, 0x58, 0xbb, 0xb9, 0x83, 0x94, 0x9c, 0x3f, 0x02, + 0x3b, 0xd2, 0xd8, 0xc2, 0x5a, 0x15, 0xb3, 0xd0, 0xe8, 0xbe, 0x64, 0x80, 0xe2, 0x93, 0x67, 0xb5, + 0x7c, 0x38, 0xdc, 0x69, 0x59, 0x44, 0xe3, 0x01, 0xef, 0x15, 0xf1, 0x49, 0xbf, 0x88, 0x4f, 0xb0, + 0x88, 0x33, 0xed, 0x2b, 0x3d, 0xf6, 0xb7, 0xca, 0x73, 0x15, 0xfd, 0x9f, 0x69, 0xfe, 0x5d, 0x35, + 0xc8, 0xcb, 0x8a, 0xf4, 0x27, 0x3f, 0xf5, 0xcd, 0xfb, 0x53, 0xdf, 0xe2, 0x27, 0xa0, 0xc9, 0x36, + 0xa8, 0x9a, 0x8b, 0x45, 0xba, 0x7c, 0xa6, 0x55, 0xd0, 0x74, 0x1f, 0xa0, 0xd6, 0x5c, 0x74, 0xfd, + 0x70, 0x0b, 0x88, 0x56, 0x44, 0x53, 0xda, 0x2b, 0x26, 0x53, 0xf4, 0x30, 0x3f, 0x81, 0x0b, 0xad, + 0x0d, 0x71, 0xca, 0x13, 0x5f, 0x5f, 0x03, 0x0c, 0x25, 0x47, 0x36, 0xdc, 0xd5, 0x2e, 0x43, 0x70, + 0xde, 0x2f, 0xad, 0x30, 0xbb, 0x93, 0x26, 0x0b, 0xcc, 0xea, 0x59, 0xa0, 0x2e, 0x74, 0xab, 0xbe, + 0x48, 0x02, 0x65, 0x5d, 0x14, 0x58, 0x42, 0xec, 0x05, 0x69, 0xd4, 0x33, 0x1c, 0xd6, 0xd0, 0x75, + 0xd1, 0x1e, 0x34, 0xc0, 0xc1, 0x75, 0xee, 0x7b, 0x84, 0xff, 0x9f, 0x96, 0x89, 0x0e, 0xa6, 0x1c, + 0x91, 0x5c, 0xea, 0x62, 0xcf, 0x1e, 0xf3, 0x8d, 0x2c, 0x0b, 0x2c, 0x4d, 0xd8, 0x35, 0x6c, 0xbd, + 0x74, 0x46, 0x84, 0xd9, 0xa4, 0x63, 0x80, 0x86, 0xa4, 0xac, 0x7a, 0xc7, 0xf5, 0xaa, 0xac, 0x3f, + 0x07, 0x73, 0x05, 0x0f, 0x69, 0x58, 0x20, 0x89, 0x50, 0x38, 0x11, 0x14, 0x31, 0x83, 0xf6, 0x95, + 0x3e, 0xeb, 0x2b, 0xb1, 0x56, 0x7e, 0x9e, 0x86, 0x24, 0x32, 0x0e, 0xa7, 0xb4, 0x83, 0x39, 0xdd, + 0x45, 0x5b, 0xfa, 0x9a, 0x66, 0xc1, 0x20, 0x2a, 0x2d, 0xf3, 0x90, 0x95, 0xcf, 0x88, 0x07, 0x3d, + 0x3d, 0x69, 0x4f, 0x3b, 0xa5, 0x75, 0x12, 0xfa, 0xe0, 0x17, 0x19, 0x94, 0x58, 0xb7, 0x87, 0x51, + 0x18, 0x66, 0x52, 0x13, 0xf8, 0xc3, 0x63, 0xa5, 0x95, 0x88, 0xd9, 0x19, 0xa1, 0x07, 0xb1, 0xfc, + 0xa7, 0x74, 0xbf, 0x7e, 0x8f, 0xa8, 0x77, 0xe7, 0xb5, 0xb7, 0x88, 0xc7, 0xdb, 0x4c, 0xc9, 0xaa, + 0x2e, 0xb3, 0x90, 0xe5, 0x41, 0xbc, 0x04, 0x70, 0x85, 0x12, 0x30, 0x17, 0xe0, 0x08, 0x7a, 0x21, + 0x31, 0x79, 0xe8, 0x9e, 0x1d, 0x88, 0x10, 0x6a, 0x67, 0xf4, 0xfe, 0xff, 0xe0, 0xf2, 0x77, 0xba, + 0xfb, 0x6d, 0x57, 0xcf, 0xb8, 0x37, 0xb9, 0xec, 0x1e, 0x26, 0xd1, 0xd1, 0xbd, 0xc3, 0x9e, 0x4b, + 0xc5, 0xc4, 0x79, 0x3c, 0x98, 0x82, 0x21, 0x21, 0x06, 0xcf, 0x7c, 0xaf, 0x7b, 0xd7, 0x6e, 0x8a, + 0xd3, 0xd8, 0x88, 0xad, 0xde, 0x89, 0xd3, 0xd5, 0xe9, 0x3c, 0xee, 0x31, 0x8b, 0x85, 0x1b, 0x14, + 0xeb, 0xae, 0x85, 0x2a, 0xe2, 0xc5, 0x99, 0xfc, 0x11, 0x1e, 0xd4, 0x60, 0x32, 0x4c, 0x87, 0x40, + 0xe8, 0x73, 0x64, 0x07, 0x0c, 0x04, 0xb6, 0xb6, 0x06, 0x46, 0xcc, 0xc9, 0x09, 0xe1, 0x81, 0xe7, + 0xe7, 0xdc, 0xc0, 0x7e, 0x71, 0x08, 0x16, 0xbc, 0xac, 0xac, 0xb1, 0x54, 0x7f, 0x59, 0x52, 0x60, + 0x4c, 0x86, 0xc2, 0x03, 0x16, 0x83, 0x12, 0x26, 0xe1, 0x32, 0xdf, 0x20, 0x66, 0x85, 0x07, 0x4d, + 0xd8, 0xf9, 0x78, 0xc1, 0x6a, 0xde, 0x6b, 0x6e, 0x06, 0x87, 0xc2, 0xd7, 0x0e, 0xa6, 0x92, 0x65, + 0xed, 0xe0, 0xed, 0x6c, 0x2b, 0x81, 0x66, 0x50, 0x30, 0xc7, 0x50, 0x42, 0xbf, 0x26, 0x96, 0x7d, + 0xf6, 0x62, 0x93, 0x31, 0xbc, 0x90, 0x7c, 0x4d, 0x10, 0xb0, 0xac, 0x82, 0x24, 0x0c, 0x56, 0xfe, + 0xe1, 0xdf, 0xb1, 0x69, 0xc2, 0x0c, 0xf4, 0xa6, 0x94, 0x80, 0xcf, 0x07, 0x9c, 0x68, 0x29, 0x8c, + 0x55, 0xa0, 0x69, 0x54, 0x26, 0x0f, 0x38, 0x60, 0x5e, 0x9d, 0xf0, 0xf0, 0x38, 0x78, 0x8e, 0x6e, + 0xe9, 0x08, 0xc4, 0xba, 0x16, 0x3d, 0x7e, 0x45, 0xd4, 0xe9, 0x36, 0x99, 0xe4, 0x5e, 0xe9, 0x7f, + 0x6f, 0x4a, 0xe8, 0x17, 0x2b, 0x68, 0xa7, 0x33, 0x78, 0x19, 0x4c, 0x41, 0xe6, 0xa5, 0x02, 0x22, + 0x7b, 0x69, 0xca, 0x5e, 0x8c, 0x5f, 0xa9, 0x99, 0xba, 0xe1, 0x04, 0xee, 0x6e, 0x6e, 0xaf, 0x14, + 0xbd, 0xca, 0xd2, 0x17, 0xad, 0xc4, 0x35, 0xbf, 0xbd, 0x22, 0xea, 0xe6, 0xc5, 0x18, 0x16, 0x29, + 0xab, 0xaf, 0x77, 0x44, 0xe1, 0x91, 0x57, 0x44, 0xdf, 0xd4, 0x14, 0xeb, 0x5f, 0x17, 0x23, 0x7b, + 0x3e, 0x46, 0xbd, 0x64, 0x5a, 0x87, 0x61, 0xa8, 0x0b, 0x46, 0x52, 0x48, 0xdb, 0xa5, 0x99, 0x48, + 0x2a, 0x23, 0x90, 0x6b, 0x3f, 0x8b, 0x55, 0x56, 0x97, 0x08, 0xc0, 0x41, 0xcc, 0xeb, 0xf4, 0x64, + 0x45, 0xb0, 0x40, 0x07, 0x7b, 0x36, 0x35, 0xe0, 0xe0, 0xda, 0xb5, 0x48, 0x2c, 0x99, 0x9e, 0xb6, + 0x04, 0x5e, 0x18, 0x97, 0xf5, 0x12, 0x4e, 0xb0, 0xbe, 0xda, 0x4e, 0x05, 0x9e, 0x58, 0x61, 0x1f, + 0x09, 0xcf, 0xa1, 0xa8, 0x36, 0xe4, 0x40, 0x8d, 0x23, 0x83, 0x61, 0x28, 0x85, 0x55, 0xe8, 0x72, + 0xb3, 0x38, 0x70, 0x25, 0xc6, 0xea, 0x08, 0xad, 0x2f, 0x46, 0x4a, 0x2e, 0xc0, 0x07, 0x01, 0x1d, + 0xa3, 0x32, 0xda, 0x69, 0x94, 0x62, 0x1a, 0x70, 0xf0, 0x6b, 0x00, 0x07, 0x73, 0x2d, 0xe9, 0x34, + 0xcb, 0x1b, 0xe2, 0xfb, 0x02, 0xfe, 0x68, 0xf1, 0x97, 0xc8, 0xea, 0xda, 0xe4, 0xe2, 0x0e, 0x5e, + 0xdd, 0x20, 0xd2, 0xbf, 0x7e, 0x78, 0xc7, 0x4f, 0x7e, 0x63, 0xc1, 0x06, 0xf0, 0xe3, 0x0f, 0xff, + 0x03, 0x24, 0x8e, 0x90, 0x87, 0xc9, 0x11, 0x00, 0x00 +}; + diff --git a/wled00/html_settings.h b/wled00/html_settings.h index 508edba1c..5c0d8bd07 100644 --- a/wled00/html_settings.h +++ b/wled00/html_settings.h @@ -6,476 +6,1905 @@ */ // Autogenerated from wled00/data/style.css, do not edit!! -const char PAGE_settingsCss[] PROGMEM = R"=====()====="; +const uint16_t PAGE_settingsCss_length = 824; +const uint8_t PAGE_settingsCss[] PROGMEM = { + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0a, 0xad, 0x55, 0x5d, 0x8b, 0x9c, 0x30, + 0x14, 0xfd, 0x2b, 0x96, 0x61, 0x61, 0x0b, 0xa3, 0xa8, 0xa3, 0xb3, 0xd3, 0x48, 0xa1, 0xf4, 0xbd, + 0x6f, 0xa5, 0x14, 0xca, 0x3e, 0x44, 0x73, 0x1d, 0xc3, 0xe4, 0x43, 0x92, 0xd8, 0x75, 0x2a, 0xfe, + 0xf7, 0x26, 0x7e, 0xac, 0xce, 0xac, 0x6c, 0x5f, 0xca, 0xe0, 0xa0, 0xde, 0x98, 0x7b, 0xee, 0xb9, + 0xf7, 0x9c, 0x54, 0x86, 0xb3, 0xce, 0xc8, 0xa6, 0xa8, 0x7c, 0x5c, 0x18, 0x2a, 0x05, 0xe2, 0x58, + 0xd0, 0xba, 0x61, 0xd8, 0x3d, 0xf4, 0xb9, 0x24, 0xd7, 0xae, 0x94, 0xc2, 0xf8, 0x25, 0xe6, 0x94, + 0x5d, 0xd1, 0x0f, 0x50, 0x04, 0x0b, 0xbc, 0xd7, 0x58, 0x68, 0x5f, 0x83, 0xa2, 0x65, 0x36, 0x84, + 0x35, 0xfd, 0x03, 0x28, 0x52, 0xc0, 0x33, 0x03, 0xad, 0xf1, 0x31, 0xa3, 0x67, 0x81, 0x0a, 0x10, + 0x06, 0x54, 0x96, 0xe3, 0xe2, 0x72, 0x56, 0xb2, 0x11, 0x04, 0xed, 0xe2, 0x38, 0xce, 0x0a, 0xc9, + 0xa4, 0x42, 0xbb, 0xb2, 0x2c, 0x33, 0x46, 0x05, 0xf8, 0x15, 0xd0, 0x73, 0x65, 0x50, 0x1c, 0x86, + 0x0f, 0x19, 0xc7, 0xea, 0x4c, 0x05, 0x0a, 0xfb, 0x4a, 0x75, 0xb9, 0x54, 0x04, 0x94, 0x3f, 0x2d, + 0x3f, 0x1e, 0x8f, 0x3d, 0xde, 0x63, 0x54, 0xc9, 0xdf, 0xa0, 0xba, 0xe9, 0x65, 0x7c, 0x2a, 0xc7, + 0x84, 0x04, 0x0a, 0xa9, 0x06, 0xcc, 0x48, 0x48, 0x01, 0x7d, 0x90, 0x1b, 0xb1, 0xcf, 0x1b, 0x63, + 0xa4, 0xe8, 0xd6, 0xf9, 0x0f, 0x87, 0xc3, 0x3a, 0xff, 0x3f, 0x4a, 0x1b, 0x11, 0xa0, 0xe0, 0x50, + 0x54, 0x9e, 0x96, 0x8c, 0x12, 0x6f, 0xd8, 0x60, 0x02, 0xa6, 0x30, 0xa1, 0x8d, 0x46, 0x71, 0x52, + 0xb7, 0x19, 0xa1, 0xba, 0x66, 0xf8, 0x8a, 0xa8, 0x18, 0x4a, 0xca, 0x99, 0x2c, 0x2e, 0x2b, 0x66, + 0xe2, 0xd0, 0xae, 0x99, 0x6a, 0x8b, 0xe2, 0xba, 0xf5, 0x4e, 0xe3, 0x95, 0xd5, 0x98, 0x10, 0x2a, + 0xce, 0xc8, 0x3d, 0xbb, 0x40, 0xc6, 0xa9, 0xf0, 0x5f, 0x28, 0x31, 0x15, 0x4a, 0x5c, 0xbc, 0x68, + 0x94, 0xb6, 0x60, 0x6b, 0x49, 0x07, 0x2a, 0x37, 0x6b, 0x1d, 0xcb, 0x0c, 0xb4, 0xed, 0xe4, 0x6a, + 0xbb, 0x7b, 0x94, 0x0e, 0xc1, 0xaa, 0x55, 0xe9, 0x6d, 0xae, 0x15, 0xbe, 0xd0, 0x73, 0xbf, 0xc8, + 0xbe, 0xe9, 0x03, 0x23, 0x6b, 0x25, 0x5f, 0xec, 0x80, 0xd4, 0x28, 0xcc, 0x6a, 0xa9, 0xe9, 0x90, + 0x55, 0x1b, 0x5a, 0x5c, 0xae, 0xab, 0xbe, 0xce, 0x3d, 0x72, 0xdd, 0xfd, 0xe3, 0x53, 0x41, 0xa0, + 0x45, 0x51, 0x1f, 0x30, 0x71, 0x99, 0x9a, 0x68, 0x1b, 0x1a, 0x54, 0xc0, 0xea, 0xaf, 0xdd, 0x6a, + 0x3c, 0x18, 0x94, 0x66, 0xd9, 0x14, 0xe7, 0x96, 0xe1, 0xc6, 0x40, 0x36, 0x22, 0x3a, 0xba, 0xfc, + 0x54, 0xd4, 0x8d, 0xf9, 0x0f, 0xfd, 0x4b, 0x6f, 0xfa, 0x37, 0x6e, 0x8b, 0x6c, 0xcb, 0x70, 0xce, + 0x80, 0xcc, 0xb3, 0x74, 0x3a, 0x9d, 0xc6, 0xc8, 0x2f, 0x73, 0xad, 0xe1, 0xb3, 0x68, 0x78, 0x0e, + 0xea, 0x79, 0xbf, 0x7a, 0xe5, 0xb0, 0x3f, 0xef, 0x35, 0x30, 0x28, 0x4c, 0xb7, 0x70, 0xc9, 0xc1, + 0x32, 0xcc, 0x67, 0xfa, 0xe2, 0x19, 0xf7, 0xcd, 0x36, 0xdd, 0xc4, 0x33, 0xf0, 0x8d, 0x60, 0xd0, + 0xb6, 0x6c, 0x5a, 0x10, 0x85, 0xe1, 0xe6, 0xf7, 0xc1, 0xeb, 0x8a, 0x53, 0xba, 0xbd, 0x60, 0x8e, + 0x1f, 0x93, 0xed, 0x38, 0x9f, 0xe2, 0xe9, 0x71, 0x3b, 0xae, 0xbb, 0x65, 0xec, 0x36, 0x01, 0xbc, + 0x2e, 0xb8, 0x43, 0x58, 0x54, 0x50, 0x5c, 0x72, 0xd9, 0x3e, 0x77, 0x46, 0x59, 0xea, 0x4b, 0xa9, + 0x38, 0xd2, 0x05, 0x66, 0xf0, 0x18, 0x05, 0xe9, 0xc7, 0x89, 0x16, 0x5f, 0x0d, 0x1a, 0x1f, 0xa6, + 0xca, 0x10, 0x6f, 0xf3, 0xf3, 0x9b, 0x95, 0x0a, 0xac, 0xcc, 0xcd, 0x3a, 0x4f, 0x49, 0x19, 0x3c, + 0xaf, 0x68, 0x8f, 0x5c, 0x21, 0x53, 0x33, 0x16, 0xee, 0xb3, 0xff, 0x3e, 0x2d, 0x46, 0x75, 0x6b, + 0x9b, 0xb2, 0x1d, 0x7a, 0xb0, 0x25, 0xbc, 0x4a, 0xcd, 0xf5, 0x3b, 0x20, 0xe9, 0x4c, 0x8e, 0xb5, + 0xc0, 0x0f, 0x94, 0xd7, 0x52, 0x19, 0x2c, 0x4c, 0x1f, 0x58, 0x1e, 0xd6, 0x90, 0x83, 0xd4, 0x59, + 0xe4, 0xad, 0xa8, 0xfb, 0xdd, 0xf7, 0x6f, 0xdf, 0x3d, 0xe3, 0x66, 0x71, 0x19, 0x82, 0x87, 0x7e, + 0xc7, 0xf5, 0xb9, 0x9b, 0x6d, 0x65, 0xd0, 0xf9, 0xce, 0x48, 0xac, 0x4d, 0x27, 0x6b, 0x5c, 0x50, + 0x73, 0xb5, 0x82, 0x7c, 0x2b, 0xc0, 0x24, 0x49, 0xee, 0x74, 0x9f, 0x0e, 0x4e, 0x60, 0x0d, 0x82, + 0x0f, 0x93, 0xf1, 0x86, 0x8e, 0x11, 0xd7, 0xd3, 0xca, 0x8a, 0x1c, 0xaf, 0xd9, 0x84, 0xcd, 0xb7, + 0x5d, 0x10, 0x46, 0x0f, 0xf9, 0x17, 0xa9, 0x96, 0xb4, 0x05, 0xb2, 0x61, 0xf4, 0xb3, 0xf4, 0xd3, + 0x6c, 0x99, 0x84, 0xe1, 0xce, 0x9e, 0x26, 0xf0, 0xf3, 0xd1, 0x4f, 0xc3, 0x07, 0x37, 0x0f, 0xed, + 0x64, 0x3a, 0x9f, 0xac, 0xdf, 0x3b, 0x0f, 0x40, 0xa9, 0x2b, 0x77, 0x28, 0x2e, 0xd0, 0x95, 0xb5, + 0x9b, 0xb9, 0xc2, 0x68, 0xcb, 0x62, 0x8e, 0x49, 0x66, 0x4f, 0x28, 0x3e, 0xfa, 0x5f, 0x89, 0x09, + 0x50, 0xe1, 0x05, 0xa9, 0xde, 0x2f, 0xb7, 0x5e, 0xec, 0xfe, 0x86, 0x01, 0xd2, 0x33, 0x6b, 0x01, + 0x28, 0x25, 0xd5, 0xbb, 0x3b, 0xe7, 0x71, 0xb4, 0xb9, 0x73, 0xff, 0xc5, 0x09, 0x1c, 0x7b, 0xba, + 0x50, 0x00, 0xc2, 0xc3, 0x82, 0x78, 0x8f, 0x4b, 0x11, 0x4f, 0x47, 0xcb, 0xdd, 0xc7, 0x6e, 0x35, + 0xa7, 0xc0, 0x31, 0x65, 0x37, 0xbe, 0x31, 0x4c, 0xee, 0xfe, 0x7d, 0x6f, 0xa9, 0xb1, 0xd6, 0x2f, + 0xb6, 0x73, 0x77, 0x86, 0xc3, 0xde, 0x1a, 0xd0, 0xbd, 0x04, 0xde, 0xc7, 0x97, 0x9c, 0xc2, 0x3b, + 0x7c, 0x6f, 0x04, 0x1f, 0xfe, 0x43, 0xf0, 0x87, 0x3b, 0x4b, 0x1b, 0x85, 0x38, 0xc6, 0x62, 0x77, + 0xfa, 0xf5, 0x3b, 0x7b, 0xda, 0x6a, 0x6f, 0xd2, 0xe2, 0x34, 0xc3, 0x89, 0x0b, 0xf4, 0x7f, 0x01, + 0x66, 0xa8, 0xe5, 0x2e, 0x60, 0x08, 0x00, 0x00 +}; // Autogenerated from wled00/data/settings.htm, do not edit!! -const char PAGE_settings[] PROGMEM = R"=====(WLED Settings -
-
-
%DMXMENU%
)====="; +const uint16_t PAGE_settings_length = 985; +const uint8_t PAGE_settings[] PROGMEM = { + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0a, 0xad, 0x56, 0x6d, 0x6f, 0xdb, 0x36, + 0x10, 0xfe, 0xee, 0x5f, 0xc1, 0xb0, 0x58, 0x23, 0xa1, 0xb2, 0xec, 0x38, 0xc3, 0xb0, 0xc9, 0x96, + 0x8b, 0x35, 0x2f, 0x9d, 0x87, 0x04, 0x0d, 0x90, 0xa4, 0xdd, 0x80, 0x7d, 0xa1, 0xc9, 0x93, 0xcc, + 0x46, 0x22, 0x05, 0xf2, 0xe4, 0xc4, 0x73, 0xf3, 0xdf, 0x77, 0x94, 0x9d, 0xb7, 0x36, 0xd8, 0x8a, + 0x21, 0x5f, 0x6c, 0xf3, 0x78, 0x7c, 0x78, 0xf7, 0x3c, 0xcf, 0x11, 0x9e, 0xec, 0x1c, 0x7e, 0x38, + 0xb8, 0xf8, 0xf3, 0xec, 0x88, 0x2d, 0xb0, 0xae, 0xa6, 0x93, 0xf0, 0xc9, 0x2a, 0x61, 0xca, 0x9c, + 0x83, 0xe1, 0xb4, 0x06, 0xa1, 0xa6, 0x93, 0x1a, 0x50, 0x30, 0xb9, 0x10, 0xce, 0x03, 0xe6, 0xfc, + 0xf2, 0xe2, 0xb8, 0xff, 0x33, 0xdf, 0x46, 0x7b, 0xd2, 0x1a, 0x04, 0x43, 0xe1, 0x6b, 0xad, 0x70, + 0x91, 0x2b, 0x58, 0x6a, 0x09, 0xfd, 0x6e, 0x91, 0x68, 0xa3, 0x51, 0x8b, 0xaa, 0xef, 0xa5, 0xa8, + 0x20, 0xdf, 0x4b, 0x6a, 0x71, 0xa3, 0xeb, 0xb6, 0xbe, 0x5f, 0xb7, 0x1e, 0x5c, 0xb7, 0x10, 0x73, + 0x5a, 0x1b, 0xcb, 0x59, 0xcf, 0x88, 0x1a, 0x72, 0xbe, 0xd4, 0x70, 0xdd, 0x58, 0x87, 0x74, 0x0b, + 0x6a, 0xac, 0x60, 0xfa, 0xe9, 0xe4, 0xe8, 0x90, 0x9d, 0x03, 0xa2, 0x36, 0xa5, 0x9f, 0x0c, 0x36, + 0xc1, 0x89, 0x97, 0x4e, 0x37, 0x38, 0xed, 0x2d, 0x85, 0x63, 0x95, 0x95, 0xba, 0x49, 0x54, 0xae, + 0xac, 0x6c, 0x6b, 0x2a, 0x28, 0xa1, 0x40, 0xbe, 0xb3, 0x37, 0x2e, 0x5a, 0x23, 0x51, 0x5b, 0xc3, + 0xca, 0x99, 0x8a, 0x30, 0x5e, 0x3b, 0xc0, 0xd6, 0x19, 0xa6, 0xd2, 0x12, 0xf0, 0xa8, 0x82, 0x90, + 0xfa, 0x6e, 0xd5, 0x6d, 0xdd, 0xde, 0xa7, 0x56, 0x56, 0xa8, 0xdf, 0xcf, 0x23, 0x4c, 0x20, 0xdf, + 0x19, 0xc6, 0xeb, 0x0a, 0x90, 0xd9, 0x5c, 0xa5, 0xd2, 0x81, 0x40, 0xd8, 0x1e, 0x8a, 0xf8, 0xe6, + 0x76, 0x1e, 0x8f, 0x6d, 0x4a, 0xbc, 0xfc, 0x8a, 0xe8, 0xf4, 0xbc, 0x45, 0xa0, 0x0d, 0x27, 0x79, + 0x82, 0x71, 0xf2, 0x75, 0x1c, 0x57, 0x0d, 0xf0, 0x84, 0x23, 0xdc, 0xe0, 0xe0, 0xb3, 0x58, 0x8a, + 0x3b, 0x80, 0x6f, 0x12, 0x85, 0x5f, 0x19, 0x82, 0x80, 0x38, 0x51, 0xe9, 0xdc, 0xaa, 0x55, 0x2a, + 0x9a, 0x06, 0x8c, 0x3a, 0x58, 0xe8, 0x4a, 0x45, 0x36, 0xe4, 0x0b, 0xa5, 0x8e, 0x96, 0x54, 0xc5, + 0x89, 0xf6, 0xc4, 0x3e, 0xb8, 0x88, 0x87, 0x9a, 0x79, 0x12, 0xc5, 0xf9, 0x74, 0xfd, 0x1e, 0xf0, + 0x63, 0x14, 0xdf, 0x3e, 0x9f, 0x07, 0xce, 0x59, 0x47, 0xe5, 0x51, 0x1e, 0x49, 0xe7, 0x6d, 0x05, + 0x69, 0x65, 0xcb, 0x88, 0x1f, 0x85, 0x38, 0xdb, 0x36, 0x4f, 0x2c, 0xb3, 0x42, 0x57, 0xd0, 0xb5, + 0x41, 0x5a, 0x39, 0x6a, 0xf7, 0x64, 0x1b, 0xb7, 0x05, 0xa3, 0x83, 0x85, 0x2e, 0x5b, 0x27, 0x3a, + 0xb6, 0x36, 0x6d, 0xb0, 0x42, 0xd0, 0x01, 0x95, 0xfe, 0x65, 0x66, 0x46, 0xda, 0xba, 0x21, 0xd2, + 0x80, 0x35, 0xa2, 0x04, 0xa6, 0x04, 0x8a, 0x1d, 0x4e, 0xf5, 0x3c, 0x10, 0x7c, 0x1e, 0xc5, 0x6b, + 0x1e, 0x2e, 0xc8, 0x78, 0x9e, 0x5f, 0x6b, 0xa3, 0xec, 0x35, 0x55, 0x21, 0x3b, 0xbc, 0xb4, 0x71, + 0x16, 0xad, 0xb4, 0xd5, 0xeb, 0xd7, 0x51, 0xa7, 0xe1, 0x30, 0x89, 0x3a, 0x71, 0xf3, 0x90, 0x51, + 0x9d, 0xa3, 0x75, 0x84, 0x1a, 0xe4, 0x9b, 0x21, 0xd4, 0xa1, 0x71, 0x39, 0x6b, 0x78, 0x1c, 0x7f, + 0xf9, 0xb2, 0x4d, 0xa3, 0xf3, 0x75, 0x43, 0x05, 0x1f, 0x13, 0x3e, 0x3b, 0xb5, 0x0a, 0x52, 0x76, + 0x56, 0x81, 0xf0, 0xc0, 0x88, 0x08, 0x70, 0xac, 0xf3, 0xd2, 0xec, 0x8c, 0x4a, 0x4a, 0x9e, 0x20, + 0xfa, 0xa7, 0x88, 0x49, 0x87, 0x16, 0xc7, 0x21, 0xab, 0xb3, 0x43, 0x80, 0x7f, 0xcb, 0x17, 0x88, + 0x4d, 0x36, 0x18, 0xf0, 0x37, 0xdd, 0x76, 0xc6, 0x79, 0xfc, 0x86, 0x0f, 0xfc, 0xd6, 0x99, 0x03, + 0x9f, 0x7e, 0xf6, 0x6f, 0x9b, 0x7c, 0xc8, 0x93, 0x9d, 0xbd, 0xf8, 0xb6, 0x37, 0x19, 0x6c, 0x2d, + 0x3a, 0xf1, 0xb8, 0x22, 0xc7, 0xf6, 0x82, 0x9a, 0xeb, 0x60, 0x80, 0xbe, 0xa8, 0x74, 0x69, 0x32, + 0xd9, 0x95, 0x34, 0x9e, 0x0b, 0x79, 0x55, 0x3a, 0xdb, 0x1a, 0x95, 0xbd, 0x1a, 0x8d, 0x46, 0xe3, + 0x05, 0xe8, 0x72, 0x81, 0xd9, 0xde, 0x70, 0xd8, 0xdc, 0x8c, 0x6b, 0xe1, 0x4a, 0x6d, 0xb2, 0xe1, + 0x6d, 0x18, 0xce, 0x75, 0xbf, 0xbf, 0xc8, 0x7e, 0x59, 0x2e, 0x6e, 0xc9, 0x29, 0x68, 0xcd, 0xfa, + 0xf1, 0xc9, 0xfd, 0xfd, 0xfd, 0x31, 0xf1, 0x66, 0x5d, 0xf6, 0xaa, 0x28, 0x8a, 0x71, 0x41, 0x93, + 0xd9, 0x2f, 0x44, 0xad, 0xab, 0x55, 0xf6, 0x11, 0x9c, 0x12, 0x46, 0x24, 0xbf, 0x41, 0xb5, 0x04, + 0xd4, 0x52, 0x24, 0x5e, 0x18, 0xdf, 0xa7, 0xf9, 0xd3, 0xc5, 0x58, 0x69, 0xdf, 0x54, 0x62, 0x95, + 0xcd, 0xa9, 0xa5, 0xab, 0xf1, 0xdc, 0x3a, 0x05, 0x2e, 0xdb, 0x6b, 0x6e, 0x18, 0xb9, 0x43, 0x2b, + 0xd6, 0xe1, 0x6e, 0xa2, 0x7d, 0x47, 0x1e, 0x68, 0x7d, 0x46, 0x03, 0x17, 0x51, 0x21, 0xf1, 0xe6, + 0x0e, 0xaf, 0xff, 0x86, 0xec, 0xa7, 0x65, 0xad, 0xcd, 0x5d, 0xe1, 0xf7, 0xfb, 0xdd, 0x3b, 0x90, + 0x11, 0xc9, 0x32, 0xa2, 0x66, 0x7e, 0x60, 0x7d, 0xf6, 0x23, 0xb5, 0x14, 0xdf, 0xf5, 0x34, 0x5a, + 0x2e, 0x98, 0x68, 0xd1, 0xb2, 0xe1, 0x58, 0xb6, 0xce, 0x53, 0xe5, 0x8d, 0xd5, 0x81, 0x91, 0x8e, + 0xba, 0x8e, 0xb2, 0xc9, 0x60, 0xf3, 0x06, 0x05, 0xe6, 0xc8, 0x9e, 0x41, 0x8b, 0x9c, 0x93, 0x7f, + 0xe8, 0x69, 0xd8, 0x50, 0xc0, 0xc2, 0x58, 0xe5, 0xdc, 0xb7, 0xf3, 0x5a, 0x23, 0x67, 0x9a, 0xb6, + 0xe7, 0xf4, 0x90, 0x58, 0x23, 0x2b, 0x2d, 0xaf, 0xf2, 0xdd, 0xaf, 0xec, 0x95, 0xf3, 0x01, 0xdf, + 0x9d, 0xbe, 0x23, 0xda, 0x26, 0x83, 0x0d, 0xc0, 0x94, 0x3d, 0x8f, 0xf4, 0x2f, 0x10, 0xe9, 0x83, + 0xe2, 0xd7, 0xba, 0xd0, 0x04, 0xf8, 0x49, 0x1f, 0xeb, 0xf0, 0x40, 0xb5, 0xcd, 0xb7, 0xb0, 0xbd, + 0xa7, 0xb8, 0xdf, 0x05, 0x4b, 0xa3, 0xe4, 0x09, 0x36, 0x58, 0xf5, 0xcc, 0x41, 0x01, 0x0e, 0x8c, + 0x04, 0xdf, 0xfb, 0xaf, 0x9a, 0xbf, 0x0b, 0x7b, 0x74, 0x48, 0xc8, 0xbd, 0xd1, 0x21, 0x3b, 0x78, + 0x3c, 0xc7, 0x2f, 0x43, 0x47, 0x1b, 0xc8, 0xb8, 0x24, 0x5f, 0xb1, 0x59, 0xd0, 0xb1, 0x10, 0x12, + 0x9e, 0x21, 0x24, 0x88, 0xa4, 0xea, 0x9b, 0x39, 0x1a, 0xce, 0x3a, 0x99, 0x69, 0xb9, 0x35, 0xa1, + 0xb1, 0x06, 0xf8, 0xff, 0xba, 0x9a, 0x00, 0xe9, 0xee, 0xc3, 0xd3, 0x3f, 0xd8, 0x87, 0x16, 0x9b, + 0x16, 0x5f, 0x46, 0x88, 0xee, 0x29, 0xde, 0x9d, 0x9e, 0xd3, 0xd7, 0x43, 0x4b, 0x2f, 0x24, 0x04, + 0xea, 0x1a, 0x82, 0x14, 0x17, 0xf4, 0xcd, 0x5e, 0xb3, 0x53, 0x21, 0x9d, 0xf5, 0x2f, 0xa4, 0x43, + 0xbd, 0xd5, 0xa1, 0xb6, 0xca, 0xbf, 0x10, 0x13, 0xd0, 0x11, 0x01, 0x34, 0xa9, 0x1a, 0x57, 0x54, + 0xef, 0x65, 0x43, 0x4f, 0xfb, 0x63, 0x2e, 0xe8, 0x07, 0x4d, 0x69, 0x18, 0xd9, 0xf0, 0x67, 0xe2, + 0x1f, 0xb2, 0x41, 0x30, 0x32, 0x5c, 0x08, 0x00, 0x00 +}; // Autogenerated from wled00/data/settings_wifi.htm, do not edit!! -const char PAGE_settings_wifi[] PROGMEM = R"=====(WiFi Settings")); + if (subPage == 9) // update + { + sappends('m',SET_F("(\"sip\")[0]"),(char*)F("WLED ")); + olen -= 2; //delete "; + oappend(versionString); + #ifdef ARDUINO_ARCH_ESP32 + oappend(SET_F("
(ESP32")); + #else + oappend(SET_F("
(ESP8266")); + #endif + oappend(SET_F(" build ")); + oappendi(VERSION); + oappend(SET_F(")\";")); + } + + if (subPage == 10) // 2D matrices + { + sappend('v',SET_F("SOMP"),strip.isMatrix); + #ifndef WLED_DISABLE_2D + oappend(SET_F("resetPanels();")); + if (strip.isMatrix) { + sappend('v',SET_F("PH"),strip.panelH); + sappend('v',SET_F("PW"),strip.panelW); + sappend('v',SET_F("MPH"),strip.hPanels); + sappend('v',SET_F("MPV"),strip.vPanels); + sappend('v',SET_F("PB"),strip.matrix.bottomStart); + sappend('v',SET_F("PR"),strip.matrix.rightStart); + sappend('v',SET_F("PV"),strip.matrix.vertical); + sappend('c',SET_F("PS"),strip.matrix.serpentine); + // panels + for (uint8_t i=0; i